Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: PrismarineJS/prismarine-web-client
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: zardoy/minecraft-web-client
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: next
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.

Commits on Aug 9, 2023

  1. remove not needed files

    zardoy committed Aug 9, 2023
    Copy the full SHA
    5300f0c View commit details
  2. Copy the full SHA
    e8dba7e View commit details
  3. speed webpack builds

    zardoy committed Aug 9, 2023
    Copy the full SHA
    08b9f48 View commit details
  4. Copy the full SHA
    2e55b63 View commit details
  5. [pick pr?] fix some bugs?

    zardoy committed Aug 9, 2023
    Copy the full SHA
    73ef18f View commit details
  6. [pick pr] display user gpu in debug overlay. Useful for laptops with …

    …iGPU and a dedicated one
    zardoy committed Aug 9, 2023
    Copy the full SHA
    1a0432d View commit details
  7. Copy the full SHA
    8c77ce8 View commit details
  8. minor fixes...

    zardoy committed Aug 9, 2023
    Copy the full SHA
    439aa8f View commit details
  9. update deps & use PNPM!

    zardoy committed Aug 9, 2023
    Copy the full SHA
    4ccb0ef View commit details
  10. dirt is boring...

    zardoy committed Aug 9, 2023
    Copy the full SHA
    6570e34 View commit details
  11. Copy the full SHA
    1e50347 View commit details
  12. fix build agan...

    zardoy committed Aug 9, 2023
    Copy the full SHA
    5b8ea2c View commit details
  13. Add debug config!

    zardoy committed Aug 9, 2023
    Copy the full SHA
    e8a18da View commit details
  14. use chunks in dev

    zardoy committed Aug 9, 2023
    Copy the full SHA
    fc0dbad View commit details
  15. Copy the full SHA
    036e82c View commit details
  16. oops. install pnpm first.

    zardoy committed Aug 9, 2023
    Copy the full SHA
    d8a69a3 View commit details

Commits on Aug 10, 2023

  1. Copy the full SHA
    8bbedef View commit details
  2. Copy the full SHA
    fa258e2 View commit details
  3. load click sound on page load

    as bonus resources num in devtools is more realistic right after page load
    zardoy committed Aug 10, 2023
    Copy the full SHA
    5077327 View commit details
  4. Copy the full SHA
    d0ddd45 View commit details
  5. feat: allow to pass connect config in search params and auto reconnec…

    …t for faster development!
    
    remove old impl, which was duplicating & bloading code a didnt allow to particially pass data to join screen such as username
    zardoy committed Aug 10, 2023
    Copy the full SHA
    e958d01 View commit details

Commits on Aug 11, 2023

  1. NEW escape stack management! Super scalable impl that shouldnt have b…

    …ugs at all! (already debugged some)
    
    remove displayScreen in favor of new fns
    zardoy committed Aug 11, 2023
    Copy the full SHA
    797bd06 View commit details
  2. Copy the full SHA
    6b7c135 View commit details
  3. Copy the full SHA
    7006fa7 View commit details
  4. Copy the full SHA
    a716e58 View commit details
  5. a bit better typechecking

    zardoy committed Aug 11, 2023
    Copy the full SHA
    80806fe View commit details
  6. feat: manage pointerLock api cleanly introduce fullscreen modes and n…

    …ew settings (also refactor them)
    
    fix: apply gui scale after release
    camera move broken for now...
    zardoy committed Aug 11, 2023
    Copy the full SHA
    631bf20 View commit details
  7. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    634a01c View commit details
  8. Copy the full SHA
    4d349bf View commit details
  9. dx: always show stats for now, clear console on l, rejoin when errore…

    …d on r, use live reload (though its buggy) instead of slow hot
    zardoy committed Aug 11, 2023
    Copy the full SHA
    ac5e03e View commit details
  10. add valtio dep

    zardoy committed Aug 11, 2023
    Copy the full SHA
    55ded81 View commit details

Commits on Aug 12, 2023

  1. mostly fix bad performance of mouse move (still work to do)

    Implement Frame Limit.
    fix boolean settings, refactor pointerlock, making it cleaner
    zardoy committed Aug 12, 2023
    Copy the full SHA
    2613551 View commit details
  2. fix incorrect ranges commited

    zardoy committed Aug 12, 2023
    Copy the full SHA
    7ceddde View commit details
  3. add new components to html

    zardoy committed Aug 12, 2023
    Copy the full SHA
    0d643be View commit details
  4. Copy the full SHA
    b683395 View commit details
  5. add proxy note, improve inputs on mobile & desktop

    dont crash on block placement error
    zardoy committed Aug 12, 2023
    Copy the full SHA
    6805c74 View commit details
  6. experimental way to handle reconnect without page reload, which doesn…

    …'t work only when player is joined (though R should work!)
    zardoy committed Aug 12, 2023
    Copy the full SHA
    b0b3279 View commit details
  7. build should be fixed

    zardoy committed Aug 12, 2023
    Copy the full SHA
    289c048 View commit details
  8. Copy the full SHA
    3a76f9e View commit details
  9. ci: commit possible build fix

    zardoy committed Aug 12, 2023
    Copy the full SHA
    7d93ee9 View commit details
  10. Copy the full SHA
    ee75eea View commit details

Commits on Aug 14, 2023

  1. Copy the full SHA
    9451c26 View commit details

Commits on Aug 15, 2023

  1. increase build size for now

    zardoy committed Aug 15, 2023
    Copy the full SHA
    e4fcf10 View commit details
  2. refactor chat providing scalability and more precise text rendering

    fix reconnect qs removal
    zardoy committed Aug 15, 2023
    Copy the full SHA
    692ec89 View commit details
  3. Copy the full SHA
    c221c00 View commit details
  4. Copy the full SHA
    1d7db28 View commit details
  5. Copy the full SHA
    fab5309 View commit details
  6. Copy the full SHA
    4fe10fc View commit details
  7. Copy the full SHA
    01b3291 View commit details

Commits on Aug 16, 2023

  1. speed debugging a bit

    zardoy committed Aug 16, 2023
    Copy the full SHA
    454c789 View commit details
Showing 596 changed files with 110,312 additions and 9,342 deletions.
2 changes: 0 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# we dont want default config to be loaded in the dockerfile, but rather using a volume
config.json
# build stuff
node_modules
public
9 changes: 9 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
rsbuild.config.ts
*.module.css.d.ts
*.generated.ts
generated
dist
public
**/*/rsbuildSharedConfig.ts
src/mcDataTypes.ts
221 changes: 221 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
{
"extends": [
"zardoy",
"plugin:@stylistic/disable-legacy"
],
"ignorePatterns": [
"!*.js"
],
"plugins": [
"@stylistic"
],
"rules": {
// style
"@stylistic/space-infix-ops": "error",
"@stylistic/no-multi-spaces": "error",
"@stylistic/no-trailing-spaces": "error",
"@stylistic/space-before-function-paren": "error",
"@stylistic/array-bracket-spacing": "error",
// would be great to have but breaks TS code like (url?) => ...
// "@stylistic/arrow-parens": [
// "error",
// "as-needed"
// ],
"@stylistic/arrow-spacing": "error",
"@stylistic/block-spacing": "error",
"@stylistic/brace-style": [
"error",
"1tbs",
{
"allowSingleLine": true
}
],
// too annoying to be forced to multi-line, probably should be enforced to never
// "@stylistic/comma-dangle": [
// "error",
// "always-multiline"
// ],
"@stylistic/computed-property-spacing": "error",
"@stylistic/dot-location": [
"error",
"property"
],
"@stylistic/eol-last": "error",
"@stylistic/function-call-spacing": "error",
"@stylistic/function-paren-newline": [
"error",
"consistent"
],
"@stylistic/generator-star-spacing": "error",
"@stylistic/implicit-arrow-linebreak": "error",
"@stylistic/indent-binary-ops": [
"error",
2
],
"@stylistic/function-call-argument-newline": [
"error",
"consistent"
],
"@stylistic/space-in-parens": [
"error",
"never"
],
"@stylistic/object-curly-spacing": [
"error",
"always"
],
"@stylistic/comma-spacing": "error",
"@stylistic/semi": [
"error",
"never"
],
"@stylistic/indent": [
"error",
2,
{
"SwitchCase": 1,
"ignoredNodes": [
"TemplateLiteral"
]
}
],
"@stylistic/quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"@stylistic/key-spacing": "error",
"@stylistic/keyword-spacing": "error",
// "@stylistic/line-comment-position": "error", // not needed
// "@stylistic/lines-around-comment": "error", // also not sure if needed
// "@stylistic/max-len": "error", // also not sure if needed
// "@stylistic/linebreak-style": "error", // let git decide
"@stylistic/max-statements-per-line": [
"error",
{
"max": 5
}
],
// "@stylistic/member-delimiter-style": "error",
// "@stylistic/multiline-ternary": "error", // not needed
// "@stylistic/newline-per-chained-call": "error", // not sure if needed
"@stylistic/new-parens": "error",
"@stylistic/no-confusing-arrow": "error",
"@stylistic/wrap-iife": "error",
"@stylistic/space-before-blocks": "error",
"@stylistic/type-generic-spacing": "error",
"@stylistic/template-tag-spacing": "error",
"@stylistic/template-curly-spacing": "error",
"@stylistic/type-annotation-spacing": "error",
"@stylistic/jsx-child-element-spacing": "error",
// buggy
// "@stylistic/jsx-closing-bracket-location": "error",
// "@stylistic/jsx-closing-tag-location": "error",
"@stylistic/jsx-curly-brace-presence": "error",
"@stylistic/jsx-curly-newline": "error",
"@stylistic/jsx-curly-spacing": "error",
"@stylistic/jsx-equals-spacing": "error",
"@stylistic/jsx-first-prop-new-line": "error",
"@stylistic/jsx-function-call-newline": "error",
"@stylistic/jsx-max-props-per-line": [
"error",
{
"maximum": 7
}
],
"@stylistic/jsx-pascal-case": "error",
"@stylistic/jsx-props-no-multi-spaces": "error",
"@stylistic/jsx-self-closing-comp": "error",
// "@stylistic/jsx-sort-props": [
// "error",
// {
// "callbacksLast": false,
// "shorthandFirst": true,
// "shorthandLast": false,
// "multiline": "ignore",
// "ignoreCase": true,
// "noSortAlphabetically": true,
// "reservedFirst": [
// "key",
// "className"
// ],
// "locale": "auto"
// }
// ],
// perf
"import/no-deprecated": "off",
// ---
"@typescript-eslint/prefer-nullish-coalescing": "off",
"@typescript-eslint/naming-convention": "off",
"prefer-template": "off",
// intentional: improve readability in some cases
"no-else-return": "off",
"@typescript-eslint/padding-line-between-statements": "off",
"@typescript-eslint/no-dynamic-delete": "off",
"arrow-body-style": "off",
"unicorn/prefer-ternary": "off",
"unicorn/switch-case-braces": "off",
"@typescript-eslint/consistent-type-definitions": "off",
"unicorn/explicit-length-check": "off",
"unicorn/prefer-dom-node-append": "off",
"typescript-eslint/no-confusing-void-expression": "off",
"unicorn/no-lonely-if": "off",
"no-multi-assign": "off",
"sonarjs/no-duplicate-string": "off",
"new-cap": "off",
"unicorn/consistent-destructuring": "off",
"unicorn/no-await-expression-member": "off",
"unicorn/prefer-add-event-listener": "off",
"unicorn/prefer-top-level-await": "off",
"default-case": "off",
// I guess it would better to fix
"node/prefer-global/buffer": "off",
"unicorn/prefer-optional-catch-binding": "off", // still useful for debugging
"no-alert": "off", // todo once replaced with ui, enable
"@typescript-eslint/restrict-plus-operands": "off",
// ---
"@typescript-eslint/no-throw-literal": "off", // disabling because of "rule expansion"
"no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"import/no-extraneous-dependencies": "off",
"@typescript-eslint/ban-types": "off",
"unicorn/prefer-query-selector": "off",
"@typescript-eslint/dot-notation": "off", // trick prop type-checking
"@typescript-eslint/consistent-type-imports": "off",
"no-negated-condition": "off",
"@typescript-eslint/no-require-imports": "off",
"unicorn/prefer-number-properties": "off",
"@typescript-eslint/no-confusing-void-expression": "off",
"unicorn/no-empty-file": "off",
"unicorn/prefer-event-target": "off",
"@typescript-eslint/member-ordering": "off",
// needs to be fixed actually
"complexity": "off",
"@typescript-eslint/no-floating-promises": "warn",
"no-async-promise-executor": "off",
"no-bitwise": "off",
"unicorn/filename-case": "off",
"max-depth": "off",
"unicorn/no-typeof-undefined": "off"
},
"overrides": [
{
"files": [
"*.js"
],
"rules": {
"@stylistic/space-before-function-paren": [
"error",
{
"anonymous": "always",
"named": "never",
"asyncArrow": "always"
}
]
}
}
],
"root": true
}
7 changes: 0 additions & 7 deletions .github/dependabot.yml

This file was deleted.

43 changes: 43 additions & 0 deletions .github/workflows/build-zip.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Make Self Host Zip

on:
workflow_dispatch:

jobs:
build-and-bundle:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Checkout repository
uses: actions/checkout@master

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Install dependencies
run: pnpm install

- name: Build project
run: pnpm build

- name: Bundle server.js
run: |
pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="'production'"
- name: Create distribution package
run: |
mkdir -p package
cp -r dist package/
cp bundled-server.js package/server.js
cd package
zip -r ../self-host.zip .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: self-host
path: self-host.zip
109 changes: 56 additions & 53 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,61 +1,64 @@
name: CI

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build
- run: npm test
DeployPages:
build-and-deploy:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' }}
permissions: write-all
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
- name: Checkout repository
uses: actions/checkout@master
- name: Setup Java JDK
uses: actions/setup-java@v1.4.3
with:
java-version: 17
java-package: jre
- uses: actions/setup-node@v4
with:
node-version: 22
# cache: "pnpm"
- name: Install pnpm
uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm check-build
- run: pnpm build-playground
- run: pnpm build-storybook
- run: pnpm test-unit
- run: pnpm lint
# - run: pnpm tsx scripts/buildNpmReact.ts
- run: nohup pnpm prod-start &
- run: nohup pnpm test-mc-server &
- uses: cypress-io/github-action@v5
with:
persist-credentials: false
fetch-depth: 0
- name: Edit config
run: |
sed -i -E 's/^ "defaultProxy": ""/ "defaultProxy": "pproxy.rom1504.fr"/g' config.json
sed -i -E 's/^ "defaultProxyPort": 0/ "defaultProxyPort": 443/g' config.json
- name: Build
run: |
npm install
npm run build
cp -R public/ ../
rm -Rf ./*
git checkout gh-pages
rm -Rf ./*
rm -Rf .github .gitignore .gitpod .gitpod.DockerFile .npmignore .npmrc
cp -R ../public/* ./
- name: Create commits
run: |
git config user.name 'rom1504bot'
git config user.email 'rom1504bot@users.noreply.github.com'
git add --all
git commit --amend -m "Update gh-pages"
- name: Deploy 🚀
uses: ad-m/github-push-action@master
install: false
- uses: actions/upload-artifact@v4
if: failure()
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages
force: true
name: cypress-images
path: cypress/screenshots/
# - run: node scripts/outdatedGitPackages.mjs
# if: ${{ github.event.pull_request.base.ref == 'release' }}
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# dedupe-check:
# runs-on: ubuntu-latest
# if: github.event.pull_request.head.ref == 'next'
# steps:
# - name: Checkout repository
# uses: actions/checkout@v2

# - name: Install pnpm
# run: npm install -g pnpm@9.0.4

# - name: Run pnpm dedupe
# run: pnpm dedupe

# - name: Check for changes
# run: |
# if ! git diff --exit-code --quiet pnpm-lock.yaml; then
# echo "pnpm dedupe introduced changes:"
# git diff --color=always pnpm-lock.yaml
# exit 1
# else
# echo "No changes detected after pnpm dedupe in pnpm-lock.yaml"
# fi
29 changes: 29 additions & 0 deletions .github/workflows/fix-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Fix Lint Command
on:
issue_comment:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
if: >-
github.event.issue.pull_request != '' &&
(
contains(github.event.comment.body, '/fix')
)
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v2
with:
ref: refs/pull/${{ github.event.issue.number }}/head
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm lint --fix
- name: Push Changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
28 changes: 28 additions & 0 deletions .github/workflows/merge-next.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Update Base Branch Command
on:
issue_comment:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
if: >-
github.event.issue.pull_request != '' &&
(
contains(github.event.comment.body, '/update')
)
permissions:
pull-requests: write
contents: write
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Fetch all history so we can merge branches
ref: refs/pull/${{ github.event.issue.number }}/head
- name: Fetch All Branches
run: git fetch --all
# - name: Checkout PR
# run: git checkout ${{ github.event.issue.pull_request.head.ref }}
- name: Merge From Next
run: git merge origin/next --strategy-option=theirs
- name: Push Changes
run: git push
91 changes: 91 additions & 0 deletions .github/workflows/next-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Vercel Deploy Next
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
ALIASES: ${{ vars.ALIASES }}
MAIN_MENU_LINKS: ${{ vars.MAIN_MENU_LINKS }}
on:
push:
branches:
- next
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install Global Dependencies
run: pnpm add -g vercel
- name: Install Dependencies
run: pnpm install
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Write Release Info
run: |
echo "{\"latestTag\": \"$(git rev-parse --short $GITHUB_SHA)\", \"isCommit\": true}" > assets/release.json
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
CONFIG_JSON_SOURCE: BUNDLED
- run: pnpm build-storybook
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
pnpm build-playground
cp -r renderer/dist/* .vercel/output/static/playground/
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Deploy Project Artifacts to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
id: deploy
- name: Start servers for testing
run: |
nohup pnpm prod-start &
nohup pnpm test-mc-server &
- name: Run Cypress smoke tests
uses: cypress-io/github-action@v5
with:
install: false
spec: cypress/e2e/smoke.spec.ts
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-smoke-test-screenshots
path: cypress/screenshots/
- name: Set deployment aliases
run: |
for alias in $(echo ${{ secrets.TEST_PREVIEW_DOMAIN }} | tr "," "\n"); do
vercel alias set ${{ steps.deploy.outputs.stdout }} $alias --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
done
- name: Create Release Pull Request
uses: actions/github-script@v6
with:
script: |
const { data: pulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${context.repo.owner}:next`,
base: 'release',
state: 'open'
});
if (pulls.length === 0) {
await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Release',
head: 'next',
base: 'release',
body: 'PR was created automatically by the release workflow, hope you release it as soon as possible!',
});
}
101 changes: 101 additions & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Vercel Deploy Preview
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
ALIASES: ${{ vars.ALIASES }}
on:
issue_comment:
types: [created]
pull_request_target:
jobs:
deploy:
runs-on: ubuntu-latest
if: >-
(
(
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '/deploy') &&
github.event.issue.pull_request != null
) ||
(
github.event_name == 'pull_request_target' &&
contains(fromJson(vars.AUTO_DEPLOY_PRS), github.event.pull_request.number)
)
)
permissions:
pull-requests: write
steps:
- name: Checkout Base To Temp
uses: actions/checkout@v2
with:
path: temp-base-repo
- name: Get deployment alias
run: node temp-base-repo/scripts/githubActions.mjs getAlias
id: alias
env:
ALIASES: ${{ env.ALIASES }}
PULL_URL: ${{ github.event.issue.pull_request.url || github.event.pull_request.url }}
- name: Checkout PR (comment)
uses: actions/checkout@v2
if: github.event_name == 'issue_comment'
with:
ref: refs/pull/${{ github.event.issue.number }}/head
- name: Checkout PR (pull_request)
uses: actions/checkout@v2
if: github.event_name == 'pull_request_target'
with:
ref: refs/pull/${{ github.event.pull_request.number }}/head

- name: Install pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install Global Dependencies
run: pnpm add -g vercel
- name: Pull Vercel Environment Information
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
- name: Write Release Info
run: |
echo "{\"latestTag\": \"$(git rev-parse --short ${{ github.event.pull_request.head.sha }})\", \"isCommit\": true}" > assets/release.json
- name: Build Project Artifacts
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
CONFIG_JSON_SOURCE: BUNDLED
- run: pnpm build-storybook
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
pnpm build-playground
cp -r renderer/dist/* .vercel/output/static/playground/
- name: Write pr redirect index.html
run: |
mkdir -p .vercel/output/static/pr
echo "<meta http-equiv='refresh' content='0;url=https://github.com/${{ github.repository }}/pull/${{ github.event.issue.number || github.event.pull_request.number }}'>" > .vercel/output/static/pr/index.html
- name: Write commit redirect index.html
run: |
mkdir -p .vercel/output/static/commit
echo "<meta http-equiv='refresh' content='0;url=https://github.com/${{ github.repository }}/pull/${{ github.event.issue.number || github.event.pull_request.number }}/commits/${{ github.event.pull_request.head.sha }}'>" > .vercel/output/static/commit/index.html
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Deploy Project Artifacts to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
id: deploy
- uses: mshick/add-pr-comment@v2
# if: github.event_name == 'issue_comment'
with:
allow-repeats: true
message: |
Deployed to Vercel Preview: ${{ steps.deploy.outputs.stdout }}
[Playground](${{ steps.deploy.outputs.stdout }}/playground/)
[Storybook](${{ steps.deploy.outputs.stdout }}/storybook/)
# - run: git checkout next scripts/githubActions.mjs
- name: Set deployment alias
if: ${{ steps.alias.outputs.alias != '' && steps.alias.outputs.alias != 'mcraft.fun' && steps.alias.outputs.alias != 's.mcraft.fun' }}
run: |
for alias in $(echo ${{ steps.alias.outputs.alias }} | tr "," "\n"); do
vercel alias set ${{ steps.deploy.outputs.stdout }} $alias --token=${{ secrets.VERCEL_TOKEN }} --scope=zaro
done
108 changes: 80 additions & 28 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,85 @@
name: npm-publish
name: Release
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
MAIN_MENU_LINKS: ${{ vars.MAIN_MENU_LINKS }}
on:
push:
branches:
- master # Change this to your default branch
branches: [release]
jobs:
npm-publish:
name: npm-publish
build-and-deploy:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Checkout repository
uses: actions/checkout@master
- name: Set up Node.js
uses: actions/setup-node@master
with:
node-version: 18.0.0
- run: npm install
- id: publish
uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_AUTH_TOKEN }}
- name: Create Release
if: steps.publish.outputs.type != 'none'
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.publish.outputs.version }}
release_name: Release ${{ steps.publish.outputs.version }}
body: ${{ steps.publish.outputs.version }}
draft: false
prerelease: false
- name: Checkout repository
uses: actions/checkout@master
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install Global Dependencies
run: pnpm add -g vercel
# - run: pnpm install
# - run: pnpm build
- run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- run: node scripts/replaceFavicon.mjs ${{ secrets.FAVICON_MAIN }}
# will install + build to .vercel/output/static
- name: Get Release Info
run: pnpx zardoy-release empty --skip-github --output-file assets/release.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
env:
CONFIG_JSON_SOURCE: BUNDLED
- run: pnpm build-storybook
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
pnpm build-playground
cp -r renderer/dist/* .vercel/output/static/playground/
- name: Download Generated Sounds map
run: node scripts/downloadSoundsMap.mjs
- name: Deploy Project to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} --prod
id: deploy
# publish to github
- run: cp vercel.json .vercel/output/static/vercel.json
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .vercel/output/static
force_orphan: true

- name: Build self-host version
run: pnpm build
- name: Bundle server.js
run: |
pnpm esbuild server.js --bundle --platform=node --outfile=bundled-server.js --define:process.env.NODE_ENV="'production'"
- name: Create zip package
run: |
mkdir -p package
cp -r dist package/
cp bundled-server.js package/server.js
cd package
zip -r ../self-host.zip .
- run: |
pnpx zardoy-release node --footer "This release URL: ${{ steps.deploy.outputs.stdout }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# has possible output: tag
id: release
# has output
- name: Set publishing config
run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}"
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# - run: pnpm tsx scripts/buildNpmReact.ts ${{ steps.release.outputs.tag }}
# if: steps.release.outputs.tag
# env:
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
20 changes: 18 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
node_modules/
package-lock.json
.vscode
public
**/public
*.log
.env.local
Thumbs.db
build
localSettings.mjs
dist*
.DS_Store
.idea/
*.iml
world
data*.json
out
*.iml
.vercel
generated
storybook-static
server-jar
config.local.json

src/react/npmReactComponents.ts
2 changes: 0 additions & 2 deletions .gitpod

This file was deleted.

4 changes: 3 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
package-lock=false
public-hoist-pattern=*
ignore-workspace-root-check=true
shell-emulator=true
14 changes: 14 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
framework: {
name: "@storybook/react-vite",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;
29 changes: 29 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'

import type { Preview } from "@storybook/react"

import './storybook.css'
import '../src/styles.css'
import '../src/scaleInterface'

const preview: Preview = {
decorators: [
(Story, c) => {
const noScaling = c.parameters.noScaling
return <div id={noScaling ? '' : 'ui-root'}>
<Story />
</div>
},
],
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
}

export default preview
18 changes: 18 additions & 0 deletions .storybook/storybook.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#storybook-root::before {
content: "";
position: fixed;
inset: 0;
background-image: url("../assets/storybook-bg.jpg");
background-size: cover;
background-position: center;
}

@font-face {
font-family: minecraft;
src: url(../assets/minecraftia.woff);
}

@font-face {
font-family: mojangles;
src: url(../assets/mojangles.ttf);
}
60 changes: 60 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"configurations": [
// recommended as much faster
{
// to launch "C:\Program Files\Google\Chrome Beta\Application\chrome.exe" --remote-debugging-port=9222
"type": "chrome",
"address": "localhost",
"name": "Attach Chrome",
"request": "attach",
// comment if using webpack
"pathMapping": {
"/": "${workspaceFolder}/dist"
},
"outFiles": [
"${workspaceFolder}/dist/**/*.js",
// "!${workspaceFolder}/dist/**/*vendors*",
"!${workspaceFolder}/dist/**/*mc-data*",
"!**/node_modules/**"
],
"skipFiles": [
// "<node_internals>/**/*vendors*"
"<node_internals>/**/*mc-data*"
],
"port": 9222,
},
{
// not recommended as in most cases it will slower as it launches from extension host so it slows down extension host, not sure why
"type": "chrome",
"name": "Launch Chrome",
"request": "launch",
"url": "http://localhost:3000/",
"pathMapping": {
"/": "${workspaceFolder}/dist"
},
"outFiles": [
"${workspaceFolder}/dist/**/*.js",
// "!${workspaceFolder}/dist/**/*vendors*",
"!${workspaceFolder}/dist/**/*mc-data*",
"!**/node_modules/**"
],
"skipFiles": [
// "<node_internals>/**/*vendors*"
"<node_internals>/**/*mc-data*"
],
},
{
// to launch "C:\Program Files\Mozilla Firefox\firefox.exe" -start-debugger-server
"type": "firefox",
"name": "Attach Firefox",
"request": "attach",
// comment if using webpack
"url": "http://localhost:3000/",
"webRoot": "${workspaceFolder}/",
"skipFiles": [
// "<node_internals>/**/*vendors*"
"<node_internals>/**/*mc-data*"
],
},
]
}
36 changes: 36 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "viewer-server",
"command": "live-server --port=9090",
"type": "shell",
"args": [],
"problemMatcher": [],
// set cwd
"options": {
"cwd": "${workspaceFolder}/prismarine-viewer/public/"
},
"presentation": {
"reveal": "silent"
},
},
{
"label": "viewer-esbuild",
"type": "shell",
"command": "node prismarine-viewer/esbuild.mjs -w",
"problemMatcher": "$esbuild-watch",
"presentation": {
"reveal": "silent"
},
},
{
"label": "viewer server+esbuild",
"dependsOn": [
"viewer-server",
"viewer-esbuild"
],
"dependsOrder": "parallel",
}
]
}
188 changes: 188 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Contributing Guide

After forking the repository, run the following commands to get started:

0. Ensure you have [Node.js](https://nodejs.org) installed. Enable corepack with `corepack enable` *(1).
1. Install dependencies: `pnpm i`
2. Start the project in development mode: `pnpm start` or build the project for production: `pnpm build`
3. Read the [Tasks Categories](#tasks-categories) and [Workflow](#workflow) sections below
4. Let us know if you are working on something and be sure to open a PR if you got any changes. Happy coding!

*(1): If you are getting `Cannot find matching keyid` update corepack to the latest version with `npm i -g corepack`.
*(2): If still something doesn't work ensure you have the right nodejs version with `node -v` (tested on 22.x)
*(3): For GitHub codespaces (cloud ide): Run `pnpm i @rsbuild/core@1.2.4 @rsbuild/plugin-node-polyfill@1.3.0 @rsbuild/plugin-react@1.1.0 @rsbuild/plugin-typed-css-modules@1.0.2` command to avoid crashes because of limited ram

## Project Structure

There are 3 main parts of the project:

### Core (`src`)

This is the main app source code which reuses all the other parts of the project.

> The first version used Webpack, then was migrated to Esbuild and now is using Rsbuild!
- Scripts:
- Start: `pnpm start`, `pnpm dev-rsbuild` (if you don't need proxy server also running)
- Build: `pnpm build` (note that `build` script builds only the core app, not the whole project!)

Paths:

- `src` - main app source code
- `src/react` - React components - almost all UI is in this folder. Almost every component has its base (reused in app and storybook) and `Provider` - which is a component that provides context to its children. Consider looking at DeathScreen component to see how it's used.

### Renderer: Playground & Mesher (`renderer`)

- Playground Scripts:
- Start: `pnpm run-playground` (playground, mesher + server) or `pnpm watch-playground`
- Build: `pnpm build-playground` or `node renderer/esbuild.mjs`

- Mesher Scripts:
- Start: `pnpm watch-mesher`
- Build: `pnpm build-mesher`

Paths:

- `renderer` - Improved and refactored version of <https://github.com/PrismarineJS/prismarine-viewer>. Here is everything related to rendering the game world itself (no ui at all). Two most important parts here are:
- `renderer/viewer/lib/worldrenderer.ts` - adding new objects to three.js happens here (sections)
- `renderer/viewer/lib/models.ts` - preparing data for rendering (blocks) - happens in worker: out file - `worker.js`, building - `renderer/buildWorker.mjs`
- `renderer/playground/playground.ts` - Playground (source of <mcraft.fun/playground.html>) Use this for testing any rendering changes. You can also modify the playground code.

### Storybook (`.storybook`)

Storybook is a tool for easier developing and testing React components.
Path of all Storybook stories is `src/react/**/*.stories.tsx`.

- Scripts:
- Start: `pnpm storybook`
- Build: `pnpm build-storybook`

## Core-related

How different modules are used:

- `mineflayer` - provider `bot` variable and as mineflayer states it is a wrapper for the `node-minecraft-protocol` module and is used to connect and interact with real Java Minecraft servers. However not all events & properties are exposed and sometimes you have to use `bot._client.on('packet_name', data => ...)` to handle packets that are not handled via mineflayer API. Also you can use almost any mineflayer plugin.

## Running Main App + Playground

To start the main web app and playground, run `pnpm run-all`. Note is doesn't start storybook and tests.

## Cypress Tests (E2E)

Cypress tests are located in `cypress` folder. To run them, run `pnpm test-mc-server` and then `pnpm test:cypress` when the `pnpm prod-start` is running (or change the port to 3000 to test with the dev server). Usually you don't need to run these until you get issues on the CI.

## Unit Tests

There are not many unit tests for now (which we are trying to improve).
Location of unit tests: `**/*.test.ts` files in `src` folder and `renderer` folder.
Start them with `pnpm test-unit`.

## Making protocol-related changes

You can get a description of packets for the latest protocol version from <https://wiki.vg/Protocol> and for previous protocol versions from <https://wiki.vg/Protocol_version_numbers> (look for *Page* links that have *Protocol* in URL).

Also there are [src/generatedClientPackets.ts](src/generatedClientPackets.ts) and [src/generatedServerPackets.ts](src/generatedServerPackets.ts) files that have definitions of packets that come from the server and the client respectively. These files are generated from the protocol files. Protocol, blocks info and other data go from <https://github.com/prismarineJS/minecraft-data> repository.

## A few other notes

- To link dependency locally e.g. flying-squid add this to `pnpm` > `overrides` of root package.json: `"flying-squid": "file:../space-squid",` (with some modules `pnpm link` also works)

- Press `Y` to reload application into the same world (server, local world or random singleplayer world)
- To start React profiling disable `REACT_APP_PROFILING` code first.
- It's recommended to use debugger for debugging. VSCode has a great debugger built-in. If debugger is slow, you can use `--no-sources` flag that would allow browser to speedup .map file parsing.
- Some data are cached between restarts. If you see something doesn't work after upgrading dependencies, try to clear the by simply removing the `dist` folder.
- The same folder `dist` is used for both development and production builds, so be careful when deploying the project.
- Use `start-prod` script to start the project in production mode after running the `build` script to build the project.
- If CI is failing on the next branch for some reason, feel free to use the latest commit for release branch. We will update the base branch asap. Please, always make sure to allow maintainers do changes when opening PRs.

## Tasks Categories

(most important for now are on top).

## 1. Client-side Logic (most important right now)

Everything related to the client side packets. Investigate issues when something goes wrong with some server. It's much easier to work on these types of tasks when you have experience in Java with Minecraft, a deep understanding of the original client, and know how to debug it (which is not hard actually). Right now the client is easily detectable by anti-cheat plugins, and the main goal is to fix it (mostly because of wrong physics implementation).

Priority tasks:

- Rewrite or fix the physics logic (Botcraft or Grim can be used as a reference as well)
- Implement basic minecart / boat / horse riding
- Fix auto jump module (false triggers, performance issues)
- Investigate connection issues to some servers
- Setup a platform for automatic cron testing against the latest version of the anti-cheat plugins
- ...

Goals:

- Make more servers playable. Right now on hypixel-like servers (servers with minigames), only tnt run (and probably ) is fully playable.

Notes:

- You can see the incoming/outgoing packets in the console (F12 in Chrome) by enabling `options.debugLogNotFrequentPackets = true`. However, if you need a FULL log of all packets, you can start recording the packets by going into `Settings` > `Advanced` > `Enable Packets Replay` and then you can download the file and use it to replay the packets.
- You can use mcraft-e2e studio to send the same packets over and over again (which is useful for testing) or use the packets replayer (which is useful for debugging).

## 2. Three.js Renderer

Example tasks:

- Improve / fix entity rendering
- Better update entities on specific packets
- Investigate performance issues under different conditions (instructions provided)
- Work on the playground code

Goals:

- Fix a lot of entity rendering issues (including position updates)
- Implement switching camera mode (first person, third person, etc)
- Animated blocks
- Armor rendering
- ...

Note:

- It's useful to know how to use helpers & additional cameras (e.g. setScissor)

## 3. Server-side Logic

Flying squid fork (space-squid).
Example tasks:

- Add missing commands (e.g. /scoreboard)
- Basic physics (player fall damage, falling blocks & entities)
- Basic entities AI (spawning, attacking)
- Pvp
- Emit more packets on some specific events (e.g. when a player uses an item)
- Make more maps playable (e.g. fix when something is not implemented in both server and client and blocking map interaction)
- ...

Long Term Goals:

- Make most adventure maps playable
- Make a way to complete the game from the scratch (crafting, different dimensions, terrain generation, etc)
- Make bedwars playable!
Most of the tasks are straightforward to implement, just be sure to use a debugger ;). If you feel you are stuck, ask for help on Discord. Absolutely any tests / refactor suggestions are welcome!

## 4. Frontend

New React components, improve UI (including mobile support).

## Workflow

1. Locate the problem on the public test server & make an easily reproducible environment (you can also use local packets replay server or your custom server setup). Dm me for details on public test server / replay server
2. Debug the code, find an issue in the code, isolate the problem
3. Develop, try to fix and test. Finally we should find a way to fix it. It's ideal to have an automatic test but it's not necessary for now
3. Repeat step 1 to make sure the task is done and the problem is fixed (or the feature is implemented)

## Updating Dependencies

1. Ensure mineflayer fork is up to date with the latest version of mineflayer original repo
2. Update PrismarineJS dependencies to the latest version: `minecraft-data` (be sure to replace the version twice in the package.json), `mineflayer`, `minecraft-protocol`, `prismarine-block`, `prismarine-chunk`, `prismarine-item`, ...
3. If `minecraft-protocol` patch fails, do this:
1. Remove the patch from `patchedDependencies` in `package.json`
2. Run `pnpm patch minecraft-protocol`, open patch directory
3. Apply the patch manually in this directory: `patch -p1 < minecraft-protocol@<version>.patch`
4. Run the suggested command from `pnpm patch ...` (previous step) to update the patch

### Would be useful to have

- cleanup folder & modules structure, cleanup playground code
44 changes: 39 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
FROM node:14-alpine
# ---- Build Stage ----
FROM node:18-alpine AS build
# 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"]
# install pnpm
RUN npm i -g pnpm@9.0.4
# Build arguments
ARG DOWNLOAD_SOUNDS=false
ARG DISABLE_SERVICE_WORKER=false
ARG CONFIG_JSON_SOURCE=REMOTE
# TODO need flat --no-root-optional
RUN node ./scripts/dockerPrepare.mjs
RUN pnpm i
# Download sounds if flag is enabled
RUN if [ "$DOWNLOAD_SOUNDS" = "true" ] ; then node scripts/downloadSoundsMap.mjs ; fi

# TODO for development
# EXPOSE 9090
# VOLUME /app/src
# VOLUME /app/renderer
# ENTRYPOINT ["pnpm", "run", "run-all"]

# only for prod
RUN DISABLE_SERVICE_WORKER=$DISABLE_SERVICE_WORKER \
CONFIG_JSON_SOURCE=$CONFIG_JSON_SOURCE \
pnpm run build

# ---- Run Stage ----
FROM node:18-alpine
RUN apk add git
WORKDIR /app
# Copy build artifacts from the build stage
COPY --from=build /app/dist /app/dist
COPY server.js /app/server.js
# Install express
RUN npm i -g pnpm@9.0.4
RUN npm init -yp
RUN pnpm i express github:zardoy/prismarinejs-net-browserify compression cors
EXPOSE 8080
VOLUME /app/public
ENTRYPOINT ["node", "server.js", "--prod"]
11 changes: 11 additions & 0 deletions Dockerfile.proxy
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# ---- Run Stage ----
FROM node:18-alpine
RUN apk add git
WORKDIR /app
COPY server.js /app/server.js
# Install server dependencies
RUN npm i -g pnpm@9.0.4
RUN npm init -yp
RUN pnpm i express github:zardoy/prismarinejs-net-browserify compression cors
EXPOSE 8080
ENTRYPOINT ["node", "server.js"]
34 changes: 0 additions & 34 deletions HISTORY.md

This file was deleted.

214 changes: 214 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Minecraft Web Client

![banner](./docs-assets/banner.jpg)

Minecraft **clone** rewritten in TypeScript using the best modern web technologies. Minecraft vanilla-compatible client and integrated server packaged into a single web app.

You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm.gg) (short link), [mcon.vercel.app](https://mcon.vercel.app/) or the GitHub pages deploy. Every commit from the default (`develop`) branch is deployed to [s.mcraft.fun](https://s.mcraft.fun/) and [s.pcm.gg](https://s.pcm.gg/) - so it's usually newer, but might be less stable.

Don't confuse with [Eaglercraft](https://git.eaglercraft.rip/eaglercraft/eaglercraft-1.8) which is a REAL vanilla Minecraft Java edition port to the web (but with its own limitations). Eaglercraft is a fully playable solution, but this project is more in position of a "technical demo" to show how it's possible to build games for web at scale entirely with the JS ecosystem. Have fun!

For building the project yourself / contributing, see [Development, Debugging & Contributing](#development-debugging--contributing). For reference at what and how web technologies / frameworks are used, see [TECH.md](./TECH.md).

### Big Features

- Open any zip world file or even folder in read-write mode!
- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below)
- Integrated JS server clone capable of opening Java world saves in any way (folders, zip, web chunks streaming, etc)
- Singleplayer mode with simple world generations!
- Works offline
- First-class touch (mobile) & controller support
- First-class keybindings configuration
- Advanced Resource pack support: Custom GUI, all textures. Server resource packs are supported with proper CORS configuration.
- Builtin JEI with recipes & descriptions for almost every item (JEI is creative inventory replacement)
- Custom protocol channel extensions (eg for custom block models in the world)
- Play with friends over internet! (P2P is powered by Peer.js discovery servers)
- ~~Google Drive support for reading / saving worlds back to the cloud~~
- even even more!

All components that are in [Storybook](https://mcraft.fun/storybook) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react)

### Recommended Settings

- Controls -> **Touch Controls Type** -> **Joystick**
- Controls -> **Auto Full Screen** -> **On** - To avoid ctrl+w issue
- Controls -> **Raw Input** -> **On** - This will make the controls more precise (UPD: already enabled by default)
- Interface -> **Chat Select** -> **On** - To select chat messages (UPD: already enabled by default)

### Browser Notes

These browsers have issues with capturing pointer:

**Opera Mini**: Disable *mouse gestures* in browsre settings to avoid opening new tab on right click hold
**Vivaldi**: Disable Controls -> *Raw Input* in game settings if experiencing issues

### World Loading

Zip files and folders are supported. Just drag and drop them into the browser window. You can open folders in readonly and read-write mode. New chunks may be generated incorrectly for now.
In case of opening zip files they are stored in your ram entirely, so there is a ~300mb file limit on IOS.
Whatever offline mode you used (zip, folder, just single player), you can always export world with the `/export` command typed in the game chat.

![docs-assets/singleplayer-future-city-1-10-2.jpg](./docs-assets/singleplayer-future-city-1-10-2.jpg)

### Servers & Proxy

You can play almost on any Java server, vanilla servers are fully supported.
See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for the list of supported versions (should support majority of versions).
There is a builtin proxy, but you can also host your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. Or you can deploy it to the cloud service:

[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F)

Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser.

```mermaid
graph LR
A[Web App - Client] --> C[Proxy Server]
C --> B[Minecraft Server]
style A fill:#f9d,stroke:#333,stroke-width:2px
style B fill:#fc0,stroke:#333,stroke-width:2px
style C fill:#fff,stroke:#333,stroke-width:2px
```

So if the server is located in Europe and you are connecting from Europe, you will have ~40ms ping (~180ms with residential proxy version), however if you are in the US and connecting to the server located in US, you will have >200ms ping, which is the worst case scenario.

Again, the proxy server is not a part of the client, it is a separate service that you can host yourself.

### Docker Files

- [Main Dockerfile](./Dockerfile) - for production build & offline/private usage. Includes full web-app + proxy server for connecting to Minecraft servers.
- [Proxy Dockerfile](./Dockerfile.proxy) - for proxy server only - that one you would be able to specify in the proxy field on the client (`docker build . -f Dockerfile.proxy -t minecraft-web-proxy`)

In case of using docker, you don't have to follow preparation steps from CONTRIBUTING.MD, like installing Node.js, pnpm, etc.

### Rendering

#### Three.js Renderer

- Uses WebGL2. Chunks are rendered using Geometry Buffers prepared by 4 mesher workers.
- Entities & text rendering
- Supports resource packs
- Doesn't support occlusion culling

### Advanced Settings

There are many many settings, that are not exposed in the UI yet. You can find or change them by opening the browser console and typing `options`. You can also change them by typing `options.<setting_name> = <value>`.

### Console

To open the console, press `F12`, or if you are on mobile, you can type `#dev` in the URL (browser address bar), it wont't reload the page, but you will see a button to open the console. This way you can change advanced settings and see all errors or warnings. Also this way you can access global variables (described below).

### Development, Debugging & Contributing

It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info. Also you can look at Dockerfile for reference.

There is world renderer playground ([link](https://mcon.vercel.app/playground/)).

However, there are many things that can be done in online production version (like debugging actual source code). Also you can access some global variables in the console and there are a few useful examples:

- `localStorage.debug = '*'` - Enables all debug messages! Warning: this will start all packets spam.
Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can use `debugTopPackets` (with JSON.stringify) to see what packets were received/sent by name

- `bot` - Mineflayer bot instance. See Mineflayer documentation for more.
- `viewer` - Three.js viewer instance, basically does all the rendering.
- `viewer.world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group.
- `debugSceneChunks` - The same as above, but relative to current bot position (e.g. 0,0 is the current chunk).
- `debugChangedOptions` - See what options are changed. Don't change options here.
- `localServer`/`server` - Only for singleplayer mode/host. Flying Squid server instance, see it's documentation for more.
- `localServer.overworld.storageProvider.regions` - See ALL LOADED region files with all raw data.
- `localServer.levelData.LevelName = 'test'; localServer.writeLevelDat()` - Change name of the world

- `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read.

The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `viewer.camera.position` to see the camera position and so on.

<img src="./docs-assets/watch-expr.png" alt="Watch expression" width="480"/>

You can also drag and drop any .dat or .mca (region files) into the browser window to see it's contents in the console.

### F3 Keybindings

- `F3` - Toggle debug overlay
- `F3 + A` - Reload all chunks (these that are loaded from the server)
<!-- <!-- - `F3 + N` - Restart local server (basically resets the world!) -->
- `F3 + G` - Toggle chunk sections (geometries) border visibility + entities outline (aka Three.js geometry helpers)

world chunks have a *yellow* border, hostile mobs have a *red* outline, passive mobs have a *green* outline, players have a *blue* outline.

### Query Parameters

Press `Y` to set query parameters to url of your current game state.

There are some parameters you can set in the url to archive some specific behaviors:

General:

- **`?setting=<setting_name>:<setting_value>`** - Set and lock the setting on load. You can set multiple settings by separating them with `&` e.g. `?setting=autoParkour:true&setting=renderDistance:4`
- `?modal=<modal>` - Open specific modal on page load eg `keybindings`. Very useful on UI changes testing during dev. For path use `,` as separator. To get currently opened modal type this in the console: `activeModalStack.at(-1).reactType`
- `?replayFileUrl=<url>` - Load and start a packet replay session from a URL with a integrated server. For debugging / previewing recorded sessions. The file must be CORS enabled.

Server specific:

- `?ip=<server_address>` - Display connect screen to the server on load with predefined server ip. `:<port>` is optional and can be added to the ip.
- `?name=<name>` - Set the server name for saving to the server list
- `?version=<version>` - Set the version for the server
- `?proxy=<proxy_address>` - Set the proxy server address to use for the server
- `?username=<username>` - Set the username for the server
- `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes.
- `?autoConnect=true` - Only works then `ip` and `version` parameters are set and `allowAutoConnect` is `true` in config.json! Directly connects to the specified server. Useful for integrates iframes.
- `?serversList=<list_or_url>` - `<list_or_url>` can be a list of servers in the format `ip:version,ip` or a url to a json file with the same format (array) or a txt file with line-delimited list of server IPs.

Single player specific:

- `?loadSave=<save_name>` - Load the save on load with the specified folder name (not title)
- `?singleplayer=1` or `?sp=1` - Create empty world on load. Nothing will be saved
- `?version=<version>` - Set the version for the singleplayer world (when used with `?singleplayer=1`)
- `?noSave=true` - Disable auto save on unload / disconnect / export whenever a world is loaded. Only manual save with `/save` command will work.
- `?serverSetting=<key>:<value>` - Set local server [options](https://github.com/zardoy/space-squid/tree/everything/src/modules.ts#L51). For example `?serverSetting=noInitialChunksSend:true` will disable initial chunks loading on the loading screen.
- `?map=<map_url>` - Load the map from ZIP. You can use any url, but it must be **CORS enabled**.
- `?mapDir=<index_file_url>` - Load the map from a file descriptor. It's recommended and the fastest way to load world but requires additional setup. The file must be in the following format:

```json
{
"baseUrl": "<url>",
"index": {
"level.dat": null,
"region": {
"r.-1.-1.mca": null,
"r.-1.0.mca": null,
"r.0.-1.mca": null,
"r.0.0.mca": null,
}
}
}
```

Note that `mapDir` also accepts base64 encoded JSON like so:
`?mapDir=data:application/json;base64,...` where `...` is the base64 encoded JSON of the index file.
In this case you must use `?mapDirBaseUrl` to specify the base URL to fetch the files from index.

- `?mapDirBaseUrl` - See above.

Only during development:

- `?reconnect=true` - Reconnect to the server on page reloads. Very useful on server testing.

<!-- - `?mapDirGuess=<base_url>` - Load the map from the provided URL and paths will be guessed with a few additional fetch requests. -->

### Notable Things that Power this Project

- [Mineflayer](https://github.com/PrismarineJS/mineflayer) - Handles all client-side communications with the server (including the builtin one) - forked
- [Forked Flying Squid (Space Squid)](https://github.com/zardoy/space-squid) - The builtin offline server that makes single player & P2P possible!
- [Prismarine Provider Anvil](https://github.com/PrismarineJS/prismarine-provider-anvil) - Handles world loading (region format)
- [Prismarine Physics](https://github.com/PrismarineJS/prismarine-physics) - Does all the physics calculations
- [Minecraft Protocol](https://github.com/PrismarineJS/node-minecraft-protocol) - Makes connections to servers possible
- [Peer.js](https://peerjs.com/) - P2P networking (when you open to wan)
- [Three.js](https://threejs.org/) - Helping in 3D rendering

### Things that are not planned yet

- Mods, plugins (basically JARs) support, shaders - since they all are related to specific game pipelines

### Alternatives

- [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true)
- [https://m.eaglercraft.com/](EaglerCraft) - Eaglercraft runnable on mobile (real Minecraft in the browser)
36 changes: 36 additions & 0 deletions README.NPM.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Minecraft React

Minecraft UI components for React extracted from [mcraft.fun](https://mcraft.fun) project.

```bash
pnpm i minecraft-react
```

![demo](./docs-assets/npm-banner.jpeg)

## Usage

```jsx
import { Scoreboard } from 'minecraft-react'

const App = () => {
return (
<Scoreboard
open
title="Scoreboard"
items={[
{ name: 'Player 1', value: 10 },
{ name: 'Player 2', value: 20 },
{ name: 'Player 3', value: 30 },
]}
/>
)
}
```

See [Storybook](https://mcraft.fun/storybook/) or [Storybook (Mirror link)](https://mcon.vercel.app/storybook/) for more examples and full components list. Also take a look at the full [standalone example](https://github.com/zardoy/minecraft-web-client/tree/experiments/UiStandaloneExample.tsx).

There are two types of components:

- Small UI components or HUD components
- Full screen components (like sign editor, worlds selector)
105 changes: 0 additions & 105 deletions README.md

This file was deleted.

104 changes: 0 additions & 104 deletions README_PT.md

This file was deleted.

105 changes: 0 additions & 105 deletions README_RU.md

This file was deleted.

57 changes: 57 additions & 0 deletions TECH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
### Eaglercraft Comparison

This project uses proxies so you can connect to almost any vanilla server. Though proxies have some limitations such as increased latency and servers will complain about using VPN (though we have a workaround for that, but ping will be much higher).
This client generally has better performance but some features reproduction might be inaccurate eg its less stable and more buggy in some cases.

| Feature | This project | Eaglercraft | Description |
| --------------------------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| General | | | |
| Mobile Support (touch) | ✅(+) || |
| Gamepad Support ||| |
| A11Y ||| We have DOM for almost all UI so your extensions and other browser features will work natively like on any other web page (but maybe it's not needed) |
| Game Features | | | |
| Servers Support (quality) ||| Eaglercraft is vanilla Minecraft, while this project tries to emulate original game behavior at protocol level (Mineflayer is used) |
| Servers Support (any version, ip) ||| We support almost all Minecraft versions, only important if you connect to a server where you need new content like blocks or if you play with friends. And you can connect to almost any server using proxy servers! |
| Singleplayer Survival Features ||| Just like Eaglercraft this project can generate and save worlds, but generator is simple and only a few survival features are supported (look here for [supported features list](https://github.com/zardoy/space-squid)) |
| Singleplayer Maps ||| We support any version, but adventure maps won't work, but simple parkour and build maps might be interesting to explore... |
| Singleplayer Maps World Streaming ||| Thanks to Browserfs, saves can be loaded to local singleplayer server using multiple ways: from local folder, server directory (not zip), dropbox or other cloud *backend* etc... |
| P2P Multiplayer ||| A way to connect to other browser running the project. But it's almost useless here since many survival features are not implemented. Maybe only to build / explore maps together... |
| Voice Chat ||| Eaglercraft has custom WebRTC voice chat implementation, though it could also be easily implemented there |
| Online Servers ||| We have custom implementation (including integration on proxy side) for joining to servers |
| Plugin Features ||| We have Mineflayer plugins support, like Auto Jump & Auto Parkour was added here that way |
| Direct Connection ||| We have DOM for almost all UI so your extensions and other browser features will work natively like on any other web page |
| Moding | ❌(roadmap, client-side) || This project will support mods for singleplayer. In theory its possible to implement support for modded servers on protocol level (including all needed mods) |
| Video Recording ||| Don't feel needed |
| Metaverse Features | ❌(roadmap) || Iframes, video streams inside of game world (custom protocol channel) |
| Sounds ||| |
| Resource Packs | ✅(+extras) || This project has very limited support for them (only textures images are loadable for now) |
| Assets Compressing & Splitting ||| We have advanced Minecraft data processing and good code chunk splitting so the web app will open much faster and use less memory |
| Graphics | | | |
| Fancy Graphics ||| While Eaglercraft has top-level shaders we don't even support lighting |
| Fast & Efficient Graphics | ❌(+) || Feels like no one needs to have 64 rendering distance work smoothly |
| VR ||| Feels like not needed feature. UI is missing in this project since DOM can't be rendered in VR so Eaglercraft could be better in that aspect |
| AR ||| Would be the most useless feature |
| Minimap & Waypoints | ✅(-) || We have buggy minimap, which can be enabled in settings and full map is opened by pressing `M` key |

Features available to only this project:

- CSS & JS Customization
- JS Real Time Debugging & Console Scripting (eg Devtools)

### Tech Stack

Bundler: Rsbuild!
UI: powered by React and css modules. Storybook helps with UI development.

### Rare WEB Features

There are a number of web features that are not commonly used but you might be interested in them if you decide to build your own game in the web.

TODO

| API | Usage & Description |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `Crypto` API | Used to make chat features work when joining online servers with authentication. |
| `requestPointerLock({ unadjustedMovement: true })` API | Required for games. Disables system mouse acceleration (important for Mac users). Aka mouse raw input |
| `navigator.keyboard.lock()` | (only in Chromium browsers) When entering fullscreen it allows to use any key combination like ctrl+w in the game |
| `navigator.keyboard.getLayoutMap()` | (only in Chromium browsers) To display the right keyboard symbol for the key keybinding on different keyboard layouts (e.g. QWERTY vs AZERTY) |
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
Binary file added assets/button_click.mp3
Binary file not shown.
Binary file removed assets/click_stereo.ogg
Binary file not shown.
Binary file added assets/destroy_stage_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/destroy_stage_9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Binary file removed assets/favicon.ico
Binary file not shown.
Binary file modified assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion assets/favicon.svg

This file was deleted.

Binary file added assets/generic_91.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/generic_92.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/generic_93.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/generic_94.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/generic_95.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Binary file removed assets/invsprite.png
Diff not rendered.
Binary file added assets/loading-bg.jpg
18 changes: 11 additions & 7 deletions assets/manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
{
"name": "Prismarine Web Client",
"short_name": "Prismarine Web Client",
"scope": "/",
"start_url": "/",
"icons": [{
"src": "favicon.png",
"sizes": "512x512"
}],
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "favicon.png",
"sizes": "720x720"
}
],
"background_color": "#349474",
"theme_color": "#349474",
"lang": "en-US",
"orientation": "landscape",
"display": "standalone"
}
}
4 changes: 4 additions & 0 deletions assets/playground.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!-- just redirect to /playground/ -->
<script>
window.location.href = `/playground/${window.location.search}`
</script>
Binary file added assets/storybook-bg.jpg
38 changes: 33 additions & 5 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
{
"defaultHost": "pjs.deptofcraft.com",
"defaultHostPort": 25565,
"defaultProxy": "",
"defaultProxyPort": 0,
"defaultVersion": "1.18.2"
"version": 1,
"defaultHost": "<from-proxy>",
"defaultProxy": "proxy.mcraft.fun",
"mapsProvider": "https://maps.mcraft.fun/",
"peerJsServer": "",
"peerJsServerFallback": "https://p2p.mcraft.fun",
"promoteServers": [
{
"ip": "ws://mcraft.ryzyn.xyz",
"version": "1.19.4"
},
{
"ip": "ws://play.mcraft.fun"
},
{
"ip": "ws://play2.mcraft.fun"
},
{
"ip": "kaboom.pw",
"version": "1.20.3",
"description": "Very nice a polite server. Must try for everyone!"
}
],
"pauseLinks": [
[
{
"type": "github"
},
{
"type": "discord"
}
]
]
}
38 changes: 38 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { defineConfig } from 'cypress'

export default defineConfig({
video: false,
chromeWebSecurity: false,
screenshotOnRunFailure: true, // Enable screenshots on test failures
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents (on, config) {
// https://medium.com/automation-with-donald/get-memory-consumption-of-web-app-with-cypress-84e2656e5a0f
on('before:browser:launch', (browser = {
name: "",
family: "chromium",
channel: "",
displayName: "",
version: "",
majorVersion: "",
path: "",
isHeaded: false,
isHeadless: false
}, launchOptions) => {
if (browser.family === 'chromium' && browser.name !== 'electron') {
// auto open devtools
launchOptions.args.push('--enable-precise-memory-info')
}

return launchOptions

})

return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'http://localhost:8080',
specPattern: 'cypress/e2e/**/*.spec.ts',
excludeSpecPattern: ['**/__snapshots__/*', '**/__image_snapshots__/*'],
},
})
2 changes: 2 additions & 0 deletions cypress/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
screenshots
fixtures
18 changes: 18 additions & 0 deletions cypress/e2e/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AppOptions } from '../../src/optionsStorage'

export const cleanVisit = (url?) => {
cy.clearLocalStorage()
visit(url)
window.localStorage.options = {
chatOpacity: 0
}
}
export const visit = (url = '/') => {
window.localStorage.cypress = 'true'
cy.visit(url)
}
export const setOptions = (options: Partial<AppOptions>) => {
cy.window().then(win => {
Object.assign(win['options'], options)
})
}
108 changes: 108 additions & 0 deletions cypress/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-disable max-nested-callbacks */
/// <reference types="cypress" />
import supportedVersions from '../../src/supportedVersions.mjs'
import { setOptions, cleanVisit, visit } from './shared'

// todo use ssl

const compareRenderedFlatWorld = () => {
// wait for render
// cy.wait(6000)
// cy.get('body').toMatchImageSnapshot({
// name: 'superflat-world',
// })
}

const testWorldLoad = () => {
return cy.document().then({ timeout: 35_000 }, doc => {
return new Cypress.Promise(resolve => {
doc.addEventListener('cypress-world-ready', resolve)
})
}).then(() => {
compareRenderedFlatWorld()
})
}

it('Loads & renders singleplayer', () => {
cleanVisit('/?singleplayer=1')
setOptions({
localServerOptions: {
generation: {
name: 'superflat',
// eslint-disable-next-line unicorn/numeric-separators-style
options: { seed: 250869072 }
},
},
renderDistance: 2
})
testWorldLoad()
})

it.skip('Joins to local flying-squid server', () => {
visit('/?ip=localhost&version=1.16.1')
window.localStorage.version = ''
// todo replace with data-test
// cy.get('[data-test-id="servers-screen-button"]').click()
// cy.get('[data-test-id="server-ip"]').clear().focus().type('localhost')
// cy.get('[data-test-id="version"]').clear().focus().type('1.16.1') // todo needs to fix autoversion
cy.get('[data-test-id="connect-qs"]').click() // todo! cypress sometimes doesn't click
testWorldLoad()
})

it.skip('Joins to local latest Java vanilla server', () => {
const version = supportedVersions.at(-1)!
cy.task('startServer', [version, 25_590]).then(() => {
visit('/?ip=localhost:25590&username=bot')
cy.get('[data-test-id="connect-qs"]').click()
testWorldLoad().then(() => {
let x = 0
let z = 0
cy.window().then((win) => {
x = win.bot.entity.position.x
z = win.bot.entity.position.z
})
cy.document().trigger('keydown', { code: 'KeyW' })
cy.wait(1500).then(() => {
cy.document().trigger('keyup', { code: 'KeyW' })
cy.window().then(async (win) => {
// eslint-disable-next-line prefer-destructuring
const bot: typeof __type_bot = win.bot
// todo use f3 stats instead
if (bot.entity.position.x === x && bot.entity.position.z === z) {
throw new Error('Player not moved')
}

bot.chat('Hello') // todo assert
bot.chat('/gamemode creative')
// bot.on('message', () => {
void bot.creative.setInventorySlot(bot.inventory.hotbarStart, new win.PrismarineItem(1, 1, 0))
// })
await bot.lookAt(bot.entity.position.offset(1, 0, 1))
}).then(() => {
cy.document().trigger('mousedown', { button: 2, isTrusted: true, force: true }) // right click
cy.document().trigger('mouseup', { button: 2, isTrusted: true, force: true })
cy.wait(1000)
})
})
})
})
})

it('Loads & renders zip world', () => {
cleanVisit()
cy.get('[data-test-id="select-file-folder"]').click({ shiftKey: true })
cy.get('input[type="file"]').selectFile('cypress/superflat.zip', { force: true })
testWorldLoad()
})


it.skip('Loads & renders world from folder', () => {
cleanVisit()
// dragndrop folder
cy.get('[data-test-id="select-file-folder"]').click()
cy.get('input[type="file"]').selectFile('server-jar/world', {
force: true,
// action: 'drag-drop',
})
testWorldLoad()
})
26 changes: 26 additions & 0 deletions cypress/minecraft-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//@ts-check
import mcServer from 'flying-squid'
import defaultOptions from 'flying-squid/config/default-settings.json' with { type: 'json' }

/** @type {Options} */
const serverOptions = {
...defaultOptions,
'online-mode': false,
'logging': false,
'gameMode': 0,
'difficulty': 0,
'worldFolder': undefined,
// todo set sid, disable entities auto-spawn
'generation': {
'name': 'superflat',
options: {}
// 'options': {
// 'worldHeight': 80
// }
},
'modpe': false,
'view-distance': 4,
'everybody-op': true,
'version': '1.16.1'
}
const server = mcServer.createMCServer(serverOptions)
33 changes: 33 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//@ts-check
const { cypressEsbuildPreprocessor } = require('cypress-esbuild-preprocessor')
const { initPlugin } = require('cypress-plugin-snapshots/plugin')
const polyfill = require('esbuild-plugin-polyfill-node')
const { startMinecraftServer } = require('./startServer')

module.exports = (on, config) => {
initPlugin(on, config)
on('file:preprocessor', cypressEsbuildPreprocessor({
esbuildOptions: {
sourcemap: true,
plugins: [
polyfill.polyfillNode({
polyfills: {
crypto: true,
},
})
],
},
}))
on('task', {
log(message) {
console.log(message)
return null
},
})
on('task', {
async startServer([version, port]) {
return startMinecraftServer(version, port)
}
})
return config
}
8 changes: 8 additions & 0 deletions cypress/plugins/ops.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"uuid": "67128b5b-2e6b-3ad1-baa0-1b937b03e5c5",
"name": "bot",
"level": 4,
"bypassesPlayerLimit": false
}
]
61 changes: 61 additions & 0 deletions cypress/plugins/server.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#Minecraft server properties
allow-flight=false
allow-nether=true
broadcast-console-to-ops=true
broadcast-rcon-to-ops=true
difficulty=peaceful
enable-command-block=false
enable-jmx-monitoring=false
enable-query=false
enable-rcon=false
enable-status=true
enforce-secure-profile=true
enforce-whitelist=false
entity-broadcast-range-percentage=100
force-gamemode=false
function-permission-level=2
gamemode=survival
generate-structures=true
generator-settings={}
hardcore=false
hide-online-players=false
initial-disabled-packs=
initial-enabled-packs=vanilla
level-name=world
level-seed=
level-type=flat
log-ips=true
max-build-height=256
max-chained-neighbor-updates=1000000
max-players=20
max-tick-time=60000
max-world-size=29999984
motd=A Minecraft Server
network-compression-threshold=256
online-mode=false
op-permission-level=4
player-idle-timeout=0
prevent-proxy-connections=false
pvp=true
query.port=25565
rate-limit=0
rcon.password=
rcon.port=25575
require-resource-pack=false
resource-pack=
resource-pack-id=
resource-pack-prompt=
resource-pack-sha1=
server-ip=
server-port=25565
simulation-distance=10
snooper-enabled=true
spawn-animals=true
spawn-monsters=true
spawn-npcs=true
spawn-protection=16
sync-chunk-writes=true
text-filtering-config=
use-native-transport=true
view-distance=10
white-list=false
45 changes: 45 additions & 0 deletions cypress/plugins/startServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ChildProcess, spawn } from 'child_process'
import * as fs from 'fs'
import * as path from 'path'
import { promisify } from 'util'
import { downloadServer } from 'minecraft-wrap'
import * as waitOn from 'wait-on'

let prevProcess: ChildProcess | null = null
export const startMinecraftServer = async (version: string, port: number) => {
if (prevProcess) return null
const jar = `./server-jar/${version}.jar`

const start = () => {
// if (prevProcess) {
// prevProcess.kill()
// }

prevProcess = spawn('java', ['-jar', path.basename(jar), 'nogui', '--port', `${port}`], {
stdio: 'inherit',
cwd: path.dirname(jar),
})
}

let coldStart = false
if (fs.existsSync(jar)) {
start()
} else {
coldStart = true
promisify(downloadServer)(version, jar).then(() => {
// add eula.txt
fs.writeFileSync(path.join(path.dirname(jar), 'eula.txt'), 'eula=true')
// copy cypress/plugins/server.properties
fs.copyFileSync(path.join(__dirname, 'server.properties'), path.join(path.dirname(jar), 'server.properties'))
// copy ops.json
fs.copyFileSync(path.join(__dirname, 'ops.json'), path.join(path.dirname(jar), 'ops.json'))
start()
})
}

return new Promise<null>((res) => {
waitOn({ resources: [`tcp:localhost:${port}`] }, () => {
setTimeout(() => res(null), coldStart ? 6500 : 2000) // todo retry instead of timeout
})
})
}
Binary file added cypress/superflat.zip
Binary file not shown.
2 changes: 2 additions & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import 'cypress-plugin-snapshots/commands'
Cypress.Commands.overwrite('log', (_subject, message) => cy.task('log', message))
Binary file added docs-assets/banner.jpg
169 changes: 169 additions & 0 deletions docs-assets/handled-packets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Handled Packets

## Server -> Client

❌ statistics
❌ advancements
❌ face_player
❌ nbt_query_response
❌ chat_suggestions
❌ trade_list
❌ vehicle_move
❌ open_book
❌ craft_recipe_response
❌ end_combat_event
❌ enter_combat_event
❌ unlock_recipes
❌ camera
❌ update_view_position
❌ update_view_distance
❌ entity_sound_effect
❌ stop_sound
❌ feature_flags
❌ select_advancement_tab
❌ declare_recipes
❌ tags
❌ acknowledge_player_digging
❌ initialize_world_border
❌ world_border_center
❌ world_border_lerp_size
❌ world_border_size
❌ world_border_warning_delay
❌ world_border_warning_reach
❌ simulation_distance
❌ chunk_biomes
❌ hurt_animation
✅ damage_event
✅ spawn_entity
✅ spawn_entity_experience_orb
✅ named_entity_spawn
✅ animation
✅ block_break_animation
✅ tile_entity_data
✅ block_action
✅ block_change
✅ boss_bar
✅ difficulty
✅ tab_complete
✅ declare_commands
✅ multi_block_change
✅ close_window
✅ open_window
✅ window_items
✅ craft_progress_bar
✅ set_slot
✅ set_cooldown
✅ custom_payload
✅ hide_message
✅ kick_disconnect
✅ profileless_chat
✅ entity_status
✅ explosion
✅ unload_chunk
✅ game_state_change
✅ open_horse_window
✅ keep_alive
✅ map_chunk
✅ world_event
✅ world_particles
✅ update_light
✅ login
✅ map
✅ rel_entity_move
✅ entity_move_look
✅ entity_look
✅ open_sign_entity
✅ abilities
✅ player_chat
✅ death_combat_event
✅ player_remove
✅ player_info
✅ position
✅ entity_destroy
✅ remove_entity_effect
✅ resource_pack_send
✅ respawn
✅ entity_head_rotation
✅ held_item_slot
✅ scoreboard_display_objective
✅ entity_metadata
✅ attach_entity
✅ entity_velocity
✅ entity_equipment
✅ experience
✅ update_health
✅ scoreboard_objective
✅ set_passengers
✅ teams
✅ scoreboard_score
✅ spawn_position
✅ update_time
✅ sound_effect
✅ system_chat
✅ playerlist_header
✅ collect
✅ entity_teleport
✅ entity_update_attributes
✅ entity_effect
✅ server_data
✅ clear_titles
✅ action_bar
✅ ping
✅ set_title_subtitle
✅ set_title_text
✅ set_title_time
✅ packet

## Client -> Server

❌ query_block_nbt
❌ set_difficulty
❌ query_entity_nbt
❌ pick_item
❌ set_beacon_effect
❌ update_command_block_minecart
❌ update_structure_block
❌ generate_structure
❌ lock_difficulty
❌ craft_recipe_request
❌ displayed_recipe
❌ recipe_book
❌ update_jigsaw_block
❌ spectate
❌ advancement_tab
✅ teleport_confirm
✅ chat_command
✅ chat_message
✅ message_acknowledgement
✅ edit_book
✅ name_item
✅ select_trade
✅ update_command_block
✅ tab_complete
✅ client_command
✅ settings
✅ enchant_item
✅ window_click
✅ close_window
✅ custom_payload
✅ use_entity
✅ keep_alive
✅ position
✅ position_look
✅ look
✅ flying
✅ vehicle_move
✅ steer_boat
✅ abilities
✅ block_dig
✅ entity_action
✅ steer_vehicle
✅ resource_pack_receive
✅ held_item_slot
✅ set_creative_slot
✅ update_sign
✅ arm_animation
✅ block_place
✅ use_item
✅ pong
✅ chat_session_update
Binary file added docs-assets/npm-banner.jpeg
Binary file added docs-assets/singleplayer-future-city-1-10-2.jpg
Binary file added docs-assets/watch-expr.png
71 changes: 71 additions & 0 deletions experiments/UiStandaloneExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useState } from 'react'
import { createRoot } from 'react-dom/client'
import {
Button,
Slider,
ArmorBar,
BreathBar,
Chat,
HealthBar,
PlayerListOverlay,
Scoreboard,
MessageFormattedString,
XPBar,
FoodBar
} from '../dist-npm'

const ExampleDemo = () => {
const [sliderValue, setSliderValue] = useState(0)

return (
<div style={{ scale: '2', transformOrigin: 'top left', width: '50%', height: '50dvh', fontFamily: 'mojangles, sans-serif', background: 'gray' }}>
<Button>Button</Button>
<Slider label="Slider" value={sliderValue} updateValue={value => setSliderValue(value)} />
<ArmorBar armorValue={10} />
<Chat
messages={[
{ id: 0, parts: [{ text: 'A formmated message in the chat', color: 'blue' }] },
{ id: 1, parts: [{ text: 'An other message in the chat', color: 'red' }] },
]}
usingTouch={false}
opened
sendMessage={message => {
console.log('typed', message)
// close
}}
/>
<BreathBar oxygen={10} />
<HealthBar isHardcore={false} healthValue={10} damaged={false} />
<FoodBar food={10} />
<PlayerListOverlay
style={{
position: 'static',
}}
clientId="" // needed for current player highlight
serverIP="Server IP"
tablistHeader="Tab §aHeader"
tablistFooter="Tab §bFooter"
playersLists={[
[
{ username: 'Player 1', ping: 10, uuid: undefined },
{ username: 'Player 2', ping: 20, uuid: undefined },
{ username: 'Player 3', ping: 30, uuid: undefined },
],
]}
/>
"§bRed" displays as <MessageFormattedString message="§bRed" />
<Scoreboard
open
title="Scoreboard"
items={[
{ name: 'Player 1', value: 10 },
{ name: 'Player 2', value: 20 },
{ name: 'Player 3', value: 30 },
]}
/>
<XPBar gamemode="survival" level={10} progress={0.5} />
</div>
)
}

createRoot(document.body as Element).render(<ExampleDemo />)
1 change: 1 addition & 0 deletions experiments/decode.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src="decode.ts" type="module"></script>
26 changes: 26 additions & 0 deletions experiments/decode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Include the pako library
import pako from 'pako';
import compressedJsRaw from './compressed.js?raw'

function decompressFromBase64(input) {
// Decode the Base64 string
const binaryString = atob(input);
const len = binaryString.length;
const bytes = new Uint8Array(len);

// Convert the binary string to a byte array
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}

// Decompress the byte array
const decompressedData = pako.inflate(bytes, { to: 'string' });

return decompressedData;
}

// Use the function
console.time('decompress');
const decompressedData = decompressFromBase64(compressedJsRaw);
console.timeEnd('decompress')
console.log(decompressedData)
15 changes: 15 additions & 0 deletions experiments/ios-safe-area-bottom-bug.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<style>
div {
position: fixed;
bottom: env(safe-area-inset-bottom);
left: 0;
right: 0;
background: #f00;
width: 100%;
height: 20px;
font-family: sans-serif;
}
</style>
<div>
<span>bottom: env(safe-area-inset-bottom)</span>
</div>
39 changes: 39 additions & 0 deletions experiments/pointers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="captured"></div>
<script>
const pointers = []
const up = () => {
captured.textContent = pointers.join(', ')
}
window.addEventListener('pointerdown', (e) => {
pointers.push(e.pointerId)
document.body.setPointerCapture(e.pointerId)
up()
e.preventDefault()
})
const remove = (id) => {
pointers.splice(pointers.indexOf(id), 1)
up()
}
window.addEventListener('pointerup', (e) => {
pointers.push('up')
remove(e.pointerId)
})
window.addEventListener('pointercancel', (e) => {
pointers.push('cancel')
remove(e.pointerId)
})
window.addEventListener('lostpointercapture', (e) => {
pointers.push('lost')
remove(e.pointerId)
})
</script>
</body>
</html>
1 change: 1 addition & 0 deletions experiments/state.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src="state.ts" type="module"></script>
37 changes: 37 additions & 0 deletions experiments/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { SmoothSwitcher } from '../renderer/viewer/lib/smoothSwitcher'

const div = document.createElement('div')
div.style.width = '100px'
div.style.height = '100px'
div.style.backgroundColor = 'red'
document.body.appendChild(div)

const pos = {x: 0, y: 0}

const positionSwitcher = new SmoothSwitcher(() => pos, (key, value) => {
pos[key] = value
})
globalThis.positionSwitcher = positionSwitcher

document.body.addEventListener('keydown', e => {
if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') {
const to = {
x: e.code === 'ArrowLeft' ? -100 : 100
}
console.log(pos, to)
positionSwitcher.transitionTo(to, e.code === 'ArrowLeft' ? 'Left' : 'Right', () => {
console.log('Switched to ', e.code === 'ArrowLeft' ? 'Left' : 'Right')
})
}
if (e.code === 'Space') {
pos.x = 200
}
})

const render = () => {
positionSwitcher.update()
div.style.transform = `translate(${pos.x}px, ${pos.y}px)`
requestAnimationFrame(render)
}

render()
1 change: 1 addition & 0 deletions experiments/three.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script type="module" src="three.ts"></script>
101 changes: 101 additions & 0 deletions experiments/three.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as THREE from 'three'
import * as tweenJs from '@tweenjs/tween.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as THREE from 'three';
import Jimp from 'jimp';

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(0, 0, 5)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)

const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const cube = new THREE.Mesh(geometry, material)
cube.position.set(0.5, 0.5, 0.5);
const group = new THREE.Group()
group.add(cube)
group.position.set(-0.5, -0.5, -0.5);
const outerGroup = new THREE.Group()
outerGroup.add(group)
outerGroup.scale.set(0.2, 0.2, 0.2)
outerGroup.position.set(1, 1, 0)
scene.add(outerGroup)

// const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 }))
// mesh.position.set(0.5, 1, 0.5)
// const group = new THREE.Group()
// group.add(mesh)
// group.position.set(-0.5, -1, -0.5)
// const outerGroup = new THREE.Group()
// outerGroup.add(group)
// // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z)
// scene.add(outerGroup)

new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start()

const tweenGroup = new tweenJs.Group()
function animate () {
tweenGroup.update()
requestAnimationFrame(animate)
// cube.rotation.x += 0.01
// cube.rotation.y += 0.01
renderer.render(scene, camera)
}
animate()

// let animation

window.animate = () => {
// new Tween.Tween(group.position).to({ y: group.position.y - 1}, 1000 * 0.35/2).yoyo(true).repeat(1).start()
new tweenJs.Tween(group.rotation, tweenGroup).to({ z: THREE.MathUtils.degToRad(90) }, 1000 * 0.35 / 2).yoyo(true).repeat(Infinity).start().onRepeat(() => {
console.log('done')
})
}

window.stop = () => {
tweenGroup.removeAll()
}


function createGeometryFromImage() {
return new Promise<THREE.ShapeGeometry>((resolve, reject) => {
const img = new Image();
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABEElEQVQ4jWNkIAPw2Zv9J0cfXPOSvx/+L/n74T+HqsJ/JlI1T9u3i6H91B7ybdY+vgZuO1majV+fppFmPnuz/+ihy2dv9t/49Wm8mlECkV1FHh5FfPZm/1XXTGX4cechA4eKPMNVq1CGH7cfMBJ0rlxX+X8OVYX/xq9P/5frKifoZ0Z0AwS8HRkYGBgYvt+8xyDXUUbQZgwJPnuz/+wq8gw/7zxk+PXsFUFno0h6mon+l5fgZFhwnYmBTUqMgYGBgaAhLMiaHQyFGOZvf8Lw49FXRgYGhv8MDAwwg/7jMoQFFury/C8Y5m9/wnADohnZVryJhoWBARJ9Cw69gtmMAgiFAcuvZ68Yfj17hU8NXgAATdKfkzbQhBEAAAAASUVORK5CYII='
console.log('img.complete', img.complete)
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const context = canvas.getContext('2d');
context.drawImage(img, 0, 0, img.width, img.height);
const imgData = context.getImageData(0, 0, img.width, img.height);

const shape = new THREE.Shape();
for (let y = 0; y < img.height; y++) {
for (let x = 0; x < img.width; x++) {
const index = (y * img.width + x) * 4;
const alpha = imgData.data[index + 3];
if (alpha !== 0) {
shape.lineTo(x, y);
}
}
}

const geometry = new THREE.ShapeGeometry(shape);
resolve(geometry);
};
img.onerror = reject;
});
}

// Usage:
const shapeGeomtry = createGeometryFromImage().then(geometry => {
const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
})
5 changes: 5 additions & 0 deletions experiments/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from 'vite';

export default defineConfig({
root: 'experiments',
})
Binary file removed extra-textures/loading.png
Binary file not shown.
Binary file removed extra-textures/loading.psd
Binary file not shown.
154 changes: 138 additions & 16 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,152 @@
<!DOCTYPE html>
<html>
<head>
<meta name="darkreader-lock">
<script>
window.startLoad = Date.now()
// g663 fix: forbid change of string prototype
Object.defineProperty(String.prototype, 'format', {
writable: false,
configurable: false
});
Object.defineProperty(String.prototype, 'replaceAll', {
writable: false,
configurable: false
});
</script>
<!-- // #region initial loader -->
<script async>
const loadingDiv = /* html */ `
<div class="initial-loader" style="position: fixed;transition:opacity 0.2s;inset: 0;background:black;display: flex;justify-content: center;align-items: center;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', Arial, Helvetica, sans-serif;gap: 15px;" ontransitionend="this.remove()">
<div>
<img src="./loading-bg.jpg" alt="Prismarine Web Client" style="position:fixed;inset:0;width:100%;height:100%;z-index: -2;object-fit: cover;filter: blur(3px);">
<div style="position: fixed;inset: 0;z-index: -1;background-color: rgba(0, 0, 0, 0.8);"></div>
</div>
<div>
<div style="font-size: calc(var(--font-size) * 1.8);color: lightgray;" class="title">Loading...</div>
<div style="font-size: var(--font-size);color: rgb(176, 176, 176);margin-top: 3px;text-align: center" class="subtitle">A true Minecraft client in your browser!</div>
<!-- small text pre -->
<div style="font-size: calc(var(--font-size) * 0.6);color: rgb(150, 150, 150);margin-top: 3px;text-align: center;white-space: pre-line;" class="advanced-info"></div>
</div>
</div>
`
const insertLoadingDiv = () => {
const loadingDivElem = document.createElement('div')
loadingDivElem.innerHTML = loadingDiv
if (!window.pageLoaded) {
document.documentElement.appendChild(loadingDivElem)
}
// load error handling
const onError = (errorOrMessage, log = false) => {
let message = errorOrMessage instanceof Error ? (errorOrMessage.stack ?? errorOrMessage.message) : errorOrMessage
if (log) console.log(message)
if (typeof message !== 'string') message = String(message)
if (document.querySelector('.initial-loader') && document.querySelector('.initial-loader').querySelector('.title').textContent !== 'Error') {
document.querySelector('.initial-loader').querySelector('.title').textContent = 'Error'
const [errorMessage, ...errorStack] = message.split('\n')
document.querySelector('.initial-loader').querySelector('.subtitle').textContent = errorMessage
document.querySelector('.initial-loader').querySelector('.advanced-info').textContent = errorStack.join('\n')
if (window.navigator.maxTouchPoints > 1) window.location.hash = '#dev' // show eruda
// unregister all sw
if (window.navigator.serviceWorker) {
window.navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => {
registration.unregister()
})
})
}
window.lastError = errorOrMessage instanceof Error ? errorOrMessage : new Error(errorOrMessage)
}
}
window.addEventListener('unhandledrejection', (e) => onError(e.reason, true))
window.addEventListener('error', (e) => onError(e.error ?? e.message))
}
insertLoadingDiv()
document.addEventListener('DOMContentLoaded', () => {
// move loading div to the end of body
const loadingDivElem = document.querySelector('.initial-loader');
const newContainer = document.body; // replace with your new container
if (loadingDivElem) newContainer.appendChild(loadingDivElem);
})
</script>
<script type="module" async>
const checkLoadEruda = () => {
if (window.location.hash === '#dev') {
// todo precache (check offline)?
import('https://cdn.skypack.dev/eruda').then(({ default: eruda }) => {
eruda.init()
})
Promise.all([import('https://cdn.skypack.dev/stacktrace-gps'), import('https://cdn.skypack.dev/error-stack-parser')]).then(async ([{ default: StackTraceGPS }, { default: ErrorStackParser }]) => {
if (!window.lastError) return

let stackFrames = [];
if (window.lastError instanceof Error) {
stackFrames = ErrorStackParser.parse(window.lastError);
}
console.log('stackFrames', stackFrames)
const gps = new StackTraceGPS()
const mappedFrames = await Promise.all(
stackFrames.map(frame => gps.pinpoint(frame))
);
console.log('mappedFrames', mappedFrames)

const stackTrace = mappedFrames
.map(frame => `at ${frame.functionName} (${frame.fileName}:${frame.lineNumber}:${frame.columnNumber})`)
.join('\n');
console.log('stackTrace', stackTrace)
})
}
}
checkLoadEruda()
window.addEventListener('hashchange', (e) => {
setTimeout(() => {
checkLoadEruda()
})
})
</script>
<style>
html {
background: black;
}
.initial-loader {
--font-size: 20px;
}
@media screen and (min-width: 550px) {
.initial-loader {
--font-size: 30px;
}
}
@media screen and (min-width: 950px) {
.initial-loader {
--font-size: 50px;
}
}
</style>
<!-- // #endregion -->
<!-- <script type="module">
window.loadPluginScript = async ({ pluginName, script }) => {
window.loadedPlugins[pluginName] = await import(script)
}
</script> -->
<title>Prismarine Web Client</title>
<link rel="stylesheet" href="styles.css">
<link rel="favicon" href="favicon.ico">
<link rel="favicon" href="favicon.png">
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="canonical" href="https://mcraft.fun">
<meta name="description" content="Minecraft web client running in your browser">
<meta name="keywords" content="Minecraft, Web, Voxel, Javascript, PrismarineJS">
<meta name="author" content="PrismarineJS">
<meta name="keywords" content="Play, Minecraft, Online, Web, Java, Server, Single player, Javascript, PrismarineJS, Voxel, WebGL, Three.js">
<meta name="date" content="2024-07-11" scheme="YYYY-MM-DD">
<meta name="language" content="English">
<meta name="theme-color" content="#349474">
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=0'>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'>
<meta property="og:title" content="Prismarine Web Client" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://prismarinejs.github.io/prismarine-web-client/" />
<meta property="og:image" content="https://prismarinejs.github.io/prismarine-web-client/favicon.png" />
<meta property="og:image" content="favicon.png" />
<meta name="format-detection" content="telephone=no">
<link rel="manifest" href="manifest.json" crossorigin="use-credentials">
</head>
<body>
<div id="ui-root">
<pmui-hud id="hud" style="display: none;"></pmui-hud>
<pmui-pausescreen id="pause-screen" style="display: none;"></pmui-pausescreen>
<pmui-loadingscreen id="loading-screen" style="display: none;"></pmui-loadingscreen>
<pmui-playscreen id="play-screen" style="display: none;"></pmui-playscreen>
<pmui-keybindsscreen id="keybinds-screen" style="display: none;"></pmui-keybindsscreen>
<pmui-optionsscreen id="options-screen" style="display: none;"></pmui-optionsscreen>
<pmui-titlescreen id="title-screen" style="display: none;"></pmui-titlescreen>
</div>
<div id="react-root"></div>
<div id="ui-root"></div>
<!-- inject script -->
</body>
</html>
455 changes: 0 additions & 455 deletions index.js

This file was deleted.

361 changes: 0 additions & 361 deletions lib/chat.js

This file was deleted.

143 changes: 0 additions & 143 deletions lib/cursor.js

This file was deleted.

4,812 changes: 0 additions & 4,812 deletions lib/invsprite.json

This file was deleted.

147 changes: 0 additions & 147 deletions lib/menus/components/bossbars_overlay.js

This file was deleted.

88 changes: 0 additions & 88 deletions lib/menus/components/breath_bar.js

This file was deleted.

140 changes: 0 additions & 140 deletions lib/menus/components/button.js

This file was deleted.

72 changes: 0 additions & 72 deletions lib/menus/components/common.js

This file was deleted.

146 changes: 0 additions & 146 deletions lib/menus/components/debug_overlay.js

This file was deleted.

97 changes: 0 additions & 97 deletions lib/menus/components/edit_box.js

This file was deleted.

117 changes: 0 additions & 117 deletions lib/menus/components/food_bar.js

This file was deleted.

158 changes: 0 additions & 158 deletions lib/menus/components/health_bar.js

This file was deleted.

210 changes: 0 additions & 210 deletions lib/menus/components/hotbar.js

This file was deleted.

179 changes: 0 additions & 179 deletions lib/menus/components/playerlist_overlay.js

This file was deleted.

176 changes: 0 additions & 176 deletions lib/menus/components/slider.js

This file was deleted.

370 changes: 0 additions & 370 deletions lib/menus/hud.js

This file was deleted.

172 changes: 0 additions & 172 deletions lib/menus/keybinds_screen.js

This file was deleted.

67 changes: 0 additions & 67 deletions lib/menus/loading_screen.js

This file was deleted.

158 changes: 0 additions & 158 deletions lib/menus/options_screen.js

This file was deleted.

110 changes: 0 additions & 110 deletions lib/menus/pause_screen.js

This file was deleted.

163 changes: 0 additions & 163 deletions lib/menus/play_screen.js

This file was deleted.

124 changes: 0 additions & 124 deletions lib/menus/title_screen.js

This file was deleted.

84 changes: 0 additions & 84 deletions lib/vr.js

This file was deleted.

244 changes: 193 additions & 51 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,78 +1,220 @@
{
"name": "prismarine-web-client",
"version": "1.5.0",
"name": "minecraft-web-client",
"version": "0.0.0-dev",
"description": "A minecraft client running in a browser",
"main": "index.js",
"scripts": {
"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"
"dev-rsbuild": "rsbuild dev",
"dev-proxy": "node server.js",
"start": "run-p dev-proxy dev-rsbuild watch-mesher",
"start2": "run-p dev-rsbuild watch-mesher",
"build": "pnpm build-other-workers && rsbuild build",
"build-analyze": "BUNDLE_ANALYZE=true rsbuild build && pnpm build-other-workers",
"check-build": "tsx scripts/genShims.ts && tsc && pnpm build",
"test:cypress": "cypress run",
"test:cypress:open": "cypress open",
"test-unit": "vitest",
"test:e2e": "start-test http-get://localhost:8080 test:cypress",
"prod-start": "node server.js --prod",
"test-mc-server": "tsx cypress/minecraft-server.mjs",
"lint": "eslint \"{src,cypress,renderer}/**/*.{ts,js,jsx,tsx}\"",
"lint-fix": "pnpm lint --fix",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build && node scripts/build.js moveStorybookFiles",
"start-experiments": "vite --config experiments/vite.config.ts --host",
"watch-other-workers": "echo NOT IMPLEMENTED",
"build-other-workers": "echo NOT IMPLEMENTED",
"build-mesher": "node renderer/buildMesherWorker.mjs",
"watch-mesher": "pnpm build-mesher -w",
"run-playground": "run-p watch-mesher watch-other-workers watch-playground",
"run-all": "run-p start run-playground",
"build-playground": "rsbuild build --config renderer/rsbuild.config.ts",
"watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts"
},
"keywords": [
"prismarine",
"web",
"client"
],
"author": "PrismarineJS",
"license": "MIT",
"bin": {
"prismarine-web-client": "./server.js"
"release": {
"attachReleaseFiles": "self-host.zip"
},
"bugs": {
"url": "https://github.com/PrismarineJS/prismarine-web-client/issues"
"publish": {
"preset": {
"publishOnlyIfChanged": true,
"runBuild": false
}
},
"homepage": "https://github.com/PrismarineJS/prismarine-web-client#readme",
"license": "MIT",
"dependencies": {
"@dimaka/interface": "0.0.3-alpha.0",
"@floating-ui/react": "^0.26.1",
"@nxg-org/mineflayer-auto-jump": "^0.7.12",
"@nxg-org/mineflayer-tracker": "1.2.1",
"@react-oauth/google": "^0.12.1",
"@stylistic/eslint-plugin": "^2.6.1",
"@types/gapi": "^0.0.47",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/wicg-file-system-access": "^2023.10.2",
"@xmcl/text-component": "^2.1.3",
"@zardoy/react-util": "^0.2.4",
"@zardoy/utils": "^0.0.11",
"adm-zip": "^0.5.12",
"browserfs": "github:zardoy/browserfs#build",
"change-case": "^5.1.2",
"classnames": "^2.5.1",
"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"
"cors": "^2.8.5",
"debug": "^4.3.4",
"deepslate": "^0.23.5",
"diff-match-patch": "^1.0.5",
"eruda": "^3.0.1",
"esbuild": "^0.19.3",
"esbuild-plugin-polyfill-node": "^0.3.0",
"express": "^4.18.2",
"filesize": "^10.0.12",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.51",
"fs-extra": "^11.1.1",
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21",
"minecraft-data": "3.83.1",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
"mojangson": "^2.0.4",
"net-browserify": "github:zardoy/prismarinejs-net-browserify",
"node-gzip": "^1.1.2",
"mcraft-fun-mineflayer": "^0.1.8",
"peerjs": "^1.5.0",
"pixelarticons": "^1.8.1",
"pretty-bytes": "^6.1.1",
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"prosemirror-example-setup": "^1.2.2",
"prosemirror-markdown": "^1.12.0",
"prosemirror-menu": "^1.2.4",
"prosemirror-state": "^1.4.3",
"prosemirror-view": "^1.33.1",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-select": "^5.8.0",
"react-transition-group": "^4.4.5",
"react-zoom-pan-pinch": "3.4.4",
"remark": "^15.0.1",
"sanitize-filename": "^1.6.3",
"skinview3d": "^3.0.1",
"source-map-js": "^1.0.2",
"stats-gl": "^1.0.5",
"stats.js": "^0.17.0",
"tabbable": "^6.2.0",
"title-case": "3.x",
"ua-parser-js": "^1.0.37",
"use-typed-event-listener": "^4.0.2",
"valtio": "^1.11.1",
"vec3": "^0.1.7",
"wait-on": "^7.2.0",
"workbox-build": "^7.0.0"
},
"devDependencies": {
"@rsbuild/core": "^1.0.1-beta.9",
"@rsbuild/plugin-node-polyfill": "^1.0.3",
"@rsbuild/plugin-react": "^1.0.1-beta.9",
"@rsbuild/plugin-type-check": "^1.0.1-beta.9",
"@rsbuild/plugin-typed-css-modules": "^1.0.1",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/blocks": "^7.4.6",
"@storybook/react": "^7.4.6",
"@storybook/react-vite": "^7.4.6",
"@types/diff-match-patch": "^1.0.36",
"@types/lodash-es": "^4.17.9",
"@types/react-transition-group": "^4.4.7",
"@types/stats.js": "^0.17.1",
"@types/three": "0.154.0",
"@types/ua-parser-js": "^0.7.39",
"@types/wait-on": "^5.3.4",
"@xmcl/installer": "^5.1.0",
"assert": "^2.0.0",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
"constants-browserify": "^1.0.0",
"copy-webpack-plugin": "^11.0.0",
"contro-max": "^0.1.8",
"crypto-browserify": "^3.12.0",
"events": "^3.2.0",
"html-webpack-plugin": "^5.3.1",
"cypress-esbuild-preprocessor": "^1.0.2",
"eslint": "^8.50.0",
"eslint-config-zardoy": "^0.2.17",
"events": "^3.3.0",
"gzip-size": "^7.0.0",
"http-browserify": "^1.7.0",
"http-server": "^14.1.0",
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
"lodash-webpack-plugin": "^0.11.6",
"memfs": "^3.2.0",
"mineflayer": "^4.1.0",
"mineflayer-pathfinder": "^2.0.0",
"mocha": "^10.0.0",
"mc-assets": "^0.2.42",
"mineflayer-mouse": "^0.0.7",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer",
"mineflayer-pathfinder": "^2.4.4",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",
"path-browserify": "^1.0.1",
"prismarine-viewer": "^1.22.0",
"process": "PrismarineJS/node-process",
"standard": "^17.0.0",
"path-exists-cli": "^2.0.0",
"renderer": "link:renderer",
"process": "github:PrismarineJS/node-process",
"rimraf": "^5.0.1",
"storybook": "^7.4.6",
"stream-browserify": "^3.0.0",
"three": "0.127.0",
"three": "0.154.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"
}
"typescript": "5.5.4",
"vitest": "^0.34.6",
"yaml": "^2.3.2"
},
"optionalDependencies": {
"cypress": "^10.11.0",
"cypress-plugin-snapshots": "^1.4.4",
"systeminformation": "^5.21.22"
},
"browserslist": {
"production": [
"iOS >= 14",
"Android >= 13",
"Chrome >= 103",
"not dead",
"not ie <= 11",
"not op_mini all",
"> 0.5%"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"pnpm": {
"overrides": {
"buffer": "^6.0.3",
"vec3": "0.1.10",
"@nxg-org/mineflayer-physics-util": "1.5.8",
"three": "0.154.0",
"diamond-square": "github:zardoy/diamond-square",
"prismarine-block": "github:zardoy/prismarine-block#next-era",
"prismarine-world": "github:zardoy/prismarine-world#next-era",
"minecraft-data": "3.83.1",
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"prismarine-physics": "github:zardoy/prismarine-physics",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
"react": "^18.2.0",
"prismarine-chunk": "github:zardoy/prismarine-chunk#master",
"prismarine-item": "latest"
},
"updateConfig": {
"ignoreDependencies": []
},
"patchedDependencies": {
"three@0.154.0": "patches/three@0.154.0.patch",
"pixelarticons@1.8.1": "patches/pixelarticons@1.8.1.patch",
"mineflayer-item-map-downloader@1.2.0": "patches/mineflayer-item-map-downloader@1.2.0.patch",
"minecraft-protocol@1.54.0": "patches/minecraft-protocol@1.54.0.patch"
}
},
"packageManager": "pnpm@9.0.4"
}
42 changes: 42 additions & 0 deletions package.npm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "minecraft-react",
"description": "A Minecraft-like React UI library",
"keywords": [
"minecraft",
"minecraft style",
"minecraft ui",
"minecraft components",
"minecraft react",
"minecraft library",
"minecraft web",
"minecraft browser"
],
"license": "MIT",
"sideEffects": false,
"files": [
"**"
],
"exports": {
".": {
"default": "./dist/react/npmReactComponents.js",
"types": "./dist/react/npmReactComponents.d.ts"
},
"./*": {
"default": "./dist/react/*",
"types": "./dist/react/*"
},
"./dist": {
"default": "./dist/*",
"types": "./dist/*"
}
},
"module": "./dist/react/npmReactComponents.js",
"types": "./dist/react/npmReactComponents.d.ts",
"repository": "zardoy/minecraft-web-client",
"version": "0.0.0-dev",
"dependencies": {},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
146 changes: 146 additions & 0 deletions patches/minecraft-protocol@1.54.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
diff --git a/src/client/chat.js b/src/client/chat.js
index f14269bea055d4329cd729271e7406ec4b344de7..00f5482eb6e3c911381ca9a728b1b4aae0d1d337 100644
--- a/src/client/chat.js
+++ b/src/client/chat.js
@@ -111,7 +111,7 @@ module.exports = function (client, options) {
for (const player of packet.data) {
if (!player.chatSession) continue
client._players[player.UUID] = {
- publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
+ // publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
publicKeyDER: player.chatSession.publicKey.keyBytes,
sessionUuid: player.chatSession.uuid
}
@@ -127,7 +127,7 @@ module.exports = function (client, options) {
for (const player of packet.data) {
if (player.crypto) {
client._players[player.UUID] = {
- publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
+ // publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
publicKeyDER: player.crypto.publicKey,
signature: player.crypto.signature,
displayName: player.displayName || player.name
@@ -198,7 +198,7 @@ module.exports = function (client, options) {
if (mcData.supportFeature('useChatSessions')) {
const tsDelta = BigInt(Date.now()) - packet.timestamp
const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0
- const verified = !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired
+ const verified = false && !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired
if (verified) client._signatureCache.push(packet.signature)
client.emit('playerChat', {
plainMessage: packet.plainMessage,
@@ -363,7 +363,7 @@ module.exports = function (client, options) {
}
}

- client._signedChat = (message, options = {}) => {
+ client._signedChat = async (message, options = {}) => {
options.timestamp = options.timestamp || BigInt(Date.now())
options.salt = options.salt || 1n

@@ -405,7 +405,7 @@ module.exports = function (client, options) {
message,
timestamp: options.timestamp,
salt: options.salt,
- signature: (client.profileKeys && client._session) ? client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
+ signature: (client.profileKeys && client._session) ? await client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
offset: client._lastSeenMessages.pending,
acknowledged
})
@@ -419,7 +419,7 @@ module.exports = function (client, options) {
message,
timestamp: options.timestamp,
salt: options.salt,
- signature: client.profileKeys ? client.signMessage(message, options.timestamp, options.salt, options.preview) : Buffer.alloc(0),
+ signature: client.profileKeys ? await client.signMessage(message, options.timestamp, options.salt, options.preview) : Buffer.alloc(0),
signedPreview: options.didPreview,
previousMessages: client._lastSeenMessages.map((e) => ({
messageSender: e.sender,
diff --git a/src/client/encrypt.js b/src/client/encrypt.js
index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108f4c63536 100644
--- a/src/client/encrypt.js
+++ b/src/client/encrypt.js
@@ -25,7 +25,11 @@ module.exports = function (client, options) {
if (packet.serverId !== '-') {
debug('This server appears to be an online server and you are providing no password, the authentication will probably fail')
}
- sendEncryptionKeyResponse()
+ client.end('This server appears to be an online server and you are providing no authentication. Try authenticating first.')
+ // sendEncryptionKeyResponse()
+ // client.once('set_compression', () => {
+ // clearTimeout(loginTimeout)
+ // })
}

function onJoinServerResponse (err) {
diff --git a/src/client.js b/src/client.js
index 74749698f8cee05b5dc749c271544f78d06645b0..e77e0a3f41c1ee780c3abbd54b0801d248c2a07c 100644
--- a/src/client.js
+++ b/src/client.js
@@ -89,10 +89,12 @@ class Client extends EventEmitter {
parsed.metadata.name = parsed.data.name
parsed.data = parsed.data.params
parsed.metadata.state = state
- debug('read packet ' + state + '.' + parsed.metadata.name)
- if (debug.enabled) {
- const s = JSON.stringify(parsed.data, null, 2)
- debug(s && s.length > 10000 ? parsed.data : s)
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) {
+ debug('read packet ' + state + '.' + parsed.metadata.name)
+ if (debug.enabled) {
+ const s = JSON.stringify(parsed.data, null, 2)
+ debug(s && s.length > 10000 ? parsed.data : s)
+ }
}
if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') {
if (this._mcBundle.length) { // End bundle
@@ -110,7 +112,13 @@ class Client extends EventEmitter {
this._hasBundlePacket = false
}
} else {
- emitPacket(parsed)
+ try {
+ emitPacket(parsed)
+ } catch (err) {
+ console.log('Client incorrectly handled packet ' + parsed.metadata.name)
+ console.error(err)
+ // todo investigate why it doesn't close the stream even if unhandled there
+ }
}
})
}
@@ -168,7 +176,10 @@ class Client extends EventEmitter {
}

const onFatalError = (err) => {
- this.emit('error', err)
+ // todo find out what is trying to write after client disconnect
+ if(err.code !== 'ECONNABORTED') {
+ this.emit('error', err)
+ }
endSocket()
}

@@ -197,6 +208,8 @@ class Client extends EventEmitter {
serializer -> framer -> socket -> splitter -> deserializer */
if (this.serializer) {
this.serializer.end()
+ this.socket?.end()
+ this.socket?.emit('end')
} else {
if (this.socket) this.socket.end()
}
@@ -238,8 +251,11 @@ class Client extends EventEmitter {

write (name, params) {
if (!this.serializer.writable) { return }
- debug('writing packet ' + this.state + '.' + name)
- debug(params)
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) {
+ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name)
+ debug(params)
+ }
+ this.emit('writePacket', name, params)
this.serializer.write({ name, params })
}

16 changes: 16 additions & 0 deletions patches/mineflayer-item-map-downloader@1.2.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
diff --git a/package.json b/package.json
index 2a7aff75a9f1c7fe4eebb657002e58f4581dad0e..cd3490983353336efeb13f24f0af69c6c1d16444 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,7 @@
"keywords": [],
"author": "Ic3Tank",
"license": "ISC",
- "dependencies": {
- "mineflayer": "^4.3.0",
- "sharp": "^0.30.6"
- },
+ "dependencies": {},
"devDependencies": {
"mineflayer-item-map-downloader": "file:./"
}
27 changes: 27 additions & 0 deletions patches/pixelarticons@1.8.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
diff --git a/fonts/pixelart-icons-font.css b/fonts/pixelart-icons-font.css
index 3b2ebe839370d96bf93ef5ca94a827f07e49378d..103ab4d6b9f3b5c9f41d1407e3cbf4ac392fbf41 100644
--- a/fonts/pixelart-icons-font.css
+++ b/fonts/pixelart-icons-font.css
@@ -1,16 +1,13 @@
@font-face {
font-family: "pixelart-icons-font";
- src: url('pixelart-icons-font.eot?t=1711815892278'); /* IE9*/
- src: url('pixelart-icons-font.eot?t=1711815892278#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ src:
url("pixelart-icons-font.woff2?t=1711815892278") format("woff2"),
url("pixelart-icons-font.woff?t=1711815892278") format("woff"),
url('pixelart-icons-font.ttf?t=1711815892278') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
- url('pixelart-icons-font.svg?t=1711815892278#pixelart-icons-font') format('svg'); /* iOS 4.1- */
}

[class^="pixelart-icons-font-"], [class*=" pixelart-icons-font-"] {
font-family: 'pixelart-icons-font' !important;
- font-size:24px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -503,4 +500,3 @@
.pixelart-icons-font-zap:before { content: "\ebe4"; }
.pixelart-icons-font-zoom-in:before { content: "\ebe5"; }
.pixelart-icons-font-zoom-out:before { content: "\ebe6"; }
-
16 changes: 16 additions & 0 deletions patches/three@0.154.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js
index 6856a21b17aa45d7922bbf776fd2d7e63c7a9b4e..0925b706f7629bd52f0bb5af469536af8f5fce2c 100644
--- a/examples/jsm/webxr/VRButton.js
+++ b/examples/jsm/webxr/VRButton.js
@@ -62,7 +62,10 @@ class VRButton {
// ('local' is always available for immersive sessions and doesn't need to
// be requested separately.)

- const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] };
+ const sessionInit = {
+ optionalFeatures: ['local-floor', 'bounded-floor', 'layers'],
+ domOverlay: { root: document.body },
+ };
navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );

} else {
21,304 changes: 21,304 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
packages:
- "."
- "renderer"
- "renderer/viewer/sign-renderer/"
5 changes: 5 additions & 0 deletions prismarine-viewer/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Prismarine Viewer

Renamed to `renderer`.

For more info see [CONTRIBUTING.md](../CONTRIBUTING.md).
3 changes: 3 additions & 0 deletions renderer/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public-hoist-pattern=*
shell-emulator=true
package-lock=false
1 change: 1 addition & 0 deletions renderer/buildMesherConfig.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dynamicMcDataFiles = ['blocks', 'blockCollisionShapes', 'biomes', 'version']
137 changes: 137 additions & 0 deletions renderer/buildMesherWorker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//@ts-check
import { context } from 'esbuild'
import { build } from 'esbuild'
import { polyfillNode } from 'esbuild-plugin-polyfill-node'
import path from 'path'
import { fileURLToPath } from 'url'
import fs from 'fs'
import { dynamicMcDataFiles } from './buildMesherConfig.mjs'
import { mesherSharedPlugins } from '../scripts/esbuildPlugins.mjs'

const allowedBundleFiles = ['legacy', 'versions', 'protocolVersions', 'features']

const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))

const watch = process.argv.includes('-w')

/** @type {import('esbuild').BuildOptions} */
const buildOptions = {
bundle: true,
banner: {
js: `globalThis.global = globalThis;process = {env: {}, versions: {} };`,
},
platform: 'browser',
entryPoints: [path.join(__dirname, './viewer/lib/mesher/mesher.ts')],
minify: true,
logLevel: 'info',
drop: !watch ? [
'debugger'
] : [],
sourcemap: 'linked',
target: watch ? undefined : ['ios14'],
write: false,
metafile: true,
outdir: path.join(__dirname, './dist'),
define: {
'process.env.BROWSER': '"true"',
},
plugins: [
...mesherSharedPlugins,
{
name: 'external-json',
setup (build) {
build.onResolve({ filter: /\.json$/ }, args => {
const fileName = args.path.split('/').pop().replace('.json', '')
if (args.resolveDir.includes('minecraft-data')) {
if (args.path.replaceAll('\\', '/').endsWith('bedrock/common/protocolVersions.json')) {
return
}
if (args.path.includes('bedrock')) {
return { path: args.path, namespace: 'empty-file', }
}
if (dynamicMcDataFiles.includes(fileName)) {
return {
path: args.path,
namespace: 'mc-data',
}
}
if (!allowedBundleFiles.includes(fileName)) {
return { path: args.path, namespace: 'empty-file', }
}
}
})
build.onResolve({
filter: /^zlib$/,
}, ({ path }) => {
return {
path,
namespace: 'empty-file',
}
})
build.onLoad({
filter: /.*/,
namespace: 'empty-file',
}, () => {
return { contents: 'module.exports = undefined', loader: 'js' }
})
build.onLoad({
namespace: 'mc-data',
filter: /.*/,
}, async ({ path }) => {
const fileName = path.split(/[\\\/]/).pop().replace('.json', '')
return {
contents: `module.exports = globalThis.mcData["${fileName}"]`,
loader: 'js',
resolveDir: process.cwd(),
}
})
build.onResolve({
filter: /^esbuild-data$/,
}, () => {
return {
path: 'esbuild-data',
namespace: 'esbuild-data',
}
})
build.onLoad({
filter: /.*/,
namespace: 'esbuild-data',
}, () => {
const data = {
// todo always use latest
tints: 'require("minecraft-data/minecraft-data/data/pc/1.16.2/tints.json")'
}
return {
contents: `module.exports = {${Object.entries(data).map(([key, code]) => `${key}: ${code}`).join(', ')}}`,
loader: 'js',
resolveDir: process.cwd(),
}
})
build.onEnd(({ metafile, outputFiles }) => {
if (!metafile) return
fs.mkdirSync(path.join(__dirname, './dist'), { recursive: true })
fs.writeFileSync(path.join(__dirname, './dist/metafile.json'), JSON.stringify(metafile))
for (const outDir of ['../dist/', './dist/']) {
for (const outputFile of outputFiles) {
if (outDir === '../dist/' && outputFile.path.endsWith('.map')) {
// skip writing & browser loading sourcemap there, worker debugging should be done in playground
// continue
}
const writePath = path.join(__dirname, outDir, path.basename(outputFile.path))
fs.mkdirSync(path.dirname(writePath), { recursive: true })
fs.writeFileSync(writePath, outputFile.text)
}
}
})
}
},
polyfillNode(),
],
}

if (watch) {
const ctx = await context(buildOptions)
await ctx.watch()
} else {
await build(buildOptions)
}
45 changes: 45 additions & 0 deletions renderer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "renderer",
"version": "1.25.0",
"description": "Web based viewer",
"main": "index.js",
"scripts": {},
"author": "PrismarineJS",
"license": "MIT",
"standard": {
"ignore": [
"examples/electron/",
"examples/exporter/",
"examples/standalone/",
"examples/web_client/"
]
},
"dependencies": {
"@tweenjs/tween.js": "^20.0.3",
"assert": "^2.0.0",
"buffer": "^6.0.3",
"filesize": "^10.0.12",
"fs-extra": "^11.0.0",
"lil-gui": "^0.18.2",
"minecraft-wrap": "^1.3.0",
"minecrafthawkeye": "^1.3.6",
"prismarine-block": "^1.7.3",
"prismarine-chunk": "^1.22.0",
"prismarine-schematic": "^1.2.0",
"renderer": "link:./",
"process": "^0.11.10",
"socket.io": "^4.0.0",
"socket.io-client": "^4.0.0",
"three-stdlib": "^2.26.11",
"three.meshline": "^1.3.0",
"tsx": "^4.7.0",
"vec3": "^0.1.7"
},
"optionalDependencies": {
"canvas": "^2.11.2",
"node-canvas-webgl": "^0.3.0"
},
"devDependencies": {
"live-server": "^1.2.2"
}
}
45 changes: 45 additions & 0 deletions renderer/playground.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<title>Renderer Playground</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'>
<style type="text/css">
html {
overflow: hidden;
background: black;
}

html, body {
height: 100%;

margin: 0;
padding: 0;
}

canvas {
height: 100%;
width: 100%;
font-size: 0;

margin: 0;
padding: 0;
}
@font-face {
font-family: mojangles;
src: url(../../../assets/mojangles.ttf);
}
* {
user-select: none;
}
</style>
<script>
if (window.location.pathname.endsWith('playground')) {
// add trailing slash
window.location.href = `${window.location.origin}${window.location.pathname}/${window.location.search}`
}
</script>
</head>
<body>
<div id="root"></div>
</body>
</html>
413 changes: 413 additions & 0 deletions renderer/playground/baseScene.ts

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions renderer/playground/playground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BasePlaygroundScene } from './baseScene'
import { playgroundGlobalUiState } from './playgroundUi'
import * as scenes from './scenes'

const qsScene = new URLSearchParams(window.location.search).get('scene')
const Scene: typeof BasePlaygroundScene = qsScene ? scenes[qsScene] : scenes.main
playgroundGlobalUiState.scenes = ['main', 'railsCobweb', 'floorRandom', 'lightingStarfield', 'transparencyIssue', 'entities', 'frequentUpdates', 'slabsOptimization', 'allEntities']
playgroundGlobalUiState.selected = qsScene ?? 'main'

const scene = new Scene()
globalThis.scene = scene
175 changes: 175 additions & 0 deletions renderer/playground/playgroundUi.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { renderToDom } from '@zardoy/react-util'
import { useEffect } from 'react'
import { proxy, useSnapshot } from 'valtio'
import { LeftTouchArea, RightTouchArea, useInterfaceState } from '@dimaka/interface'
import { css } from '@emotion/css'
import { Vec3 } from 'vec3'
import useLongPress from '../../src/react/useLongPress'
import { isMobile } from '../viewer/lib/simpleUtils'

export const playgroundGlobalUiState = proxy({
scenes: [] as string[],
selected: '',
selectorOpened: false,
actions: {} as Record<string, () => void>,
})

renderToDom(<Playground />)

function Playground () {
useEffect(() => {
const style = document.createElement('style')
style.innerHTML = /* css */ `
.lil-gui {
top: 60px !important;
right: 0 !important;
}
`
document.body.appendChild(style)
return () => {
style.remove()
}
}, [])

return <div style={{
fontFamily: 'monospace',
color: 'white',
}}>
<Controls />
<SceneSelector />
<ActionsSelector />
</div>
}

function SceneSelector () {
const mobile = isMobile()
const { scenes, selected } = useSnapshot(playgroundGlobalUiState)
const longPressEvents = useLongPress(() => {
playgroundGlobalUiState.selectorOpened = true
}, () => { })

return <div
style={{
position: 'fixed',
top: 0,
left: 0,
}} {...longPressEvents}>
{scenes.map(scene => <div
key={scene}
style={{
padding: mobile ? '5px' : '2px 5px',
cursor: 'pointer',
userSelect: 'none',
background: scene === selected ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.6)',
fontWeight: scene === selected ? 'bold' : 'normal',
}}
onClick={() => {
const qs = new URLSearchParams(window.location.search)
qs.set('scene', scene)
location.search = qs.toString()
}}
>{scene}</div>)}
</div>
}

const ActionsSelector = () => {
const { actions, selectorOpened } = useSnapshot(playgroundGlobalUiState)

if (!selectorOpened) return null
return <div style={{
position: 'fixed',
inset: 0,
background: 'rgba(0, 0, 0, 0.5)',
zIndex: 10,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
gap: 5,
fontSize: 24,
}}>{Object.entries({
...actions,
'Close' () {
playgroundGlobalUiState.selectorOpened = false
}
}).map(([name, action]) => <div
key={name}
style={{
padding: '2px 5px',
cursor: 'pointer',
userSelect: 'none',
background: 'rgba(0, 0, 0, 0.5)',
}}
onClick={() => {
action()
playgroundGlobalUiState.selectorOpened = false
}}
>{name}</div>)}</div>
}

const Controls = () => {
// todo setting
const usingTouch = navigator.maxTouchPoints > 0

useEffect(() => {
window.addEventListener('touchstart', (e) => {
e.preventDefault()
})

const pressedKeys = new Set<string>()
useInterfaceState.setState({
isFlying: false,
uiCustomization: {
touchButtonSize: 40,
},
updateCoord ([coord, state]) {
const vec3 = new Vec3(0, 0, 0)
vec3[coord] = state
let key: string | undefined
if (vec3.z < 0) key = 'KeyW'
if (vec3.z > 0) key = 'KeyS'
if (vec3.y > 0) key = 'Space'
if (vec3.y < 0) key = 'ShiftLeft'
if (vec3.x < 0) key = 'KeyA'
if (vec3.x > 0) key = 'KeyD'
if (key) {
if (!pressedKeys.has(key)) {
pressedKeys.add(key)
window.dispatchEvent(new KeyboardEvent('keydown', { code: key }))
}
}
for (const k of pressedKeys) {
if (k !== key) {
window.dispatchEvent(new KeyboardEvent('keyup', { code: k }))
pressedKeys.delete(k)
}
}
}
})
}, [])

if (!usingTouch) return null
return (
<div
style={{ zIndex: 8 }}
className={css`
position: fixed;
inset: 0;
height: 100%;
display: flex;
width: 100%;
justify-content: space-between;
align-items: flex-end;
pointer-events: none;
touch-action: none;
& > div {
pointer-events: auto;
}
`}
>
<LeftTouchArea />
<div />
<RightTouchArea />
</div>
)
}
165 changes: 165 additions & 0 deletions renderer/playground/scenes/allEntities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { BasePlaygroundScene } from '../baseScene'
import { EntityDebugFlags, EntityMesh, rendererSpecialHandled } from '../../viewer/lib/entity/EntityMesh'

export default class AllEntities extends BasePlaygroundScene {
continuousRender = false
enableCameraControls = false

async initData () {
await super.initData()

// Create results container
const container = document.createElement('div')
container.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-height: 90vh;
overflow-y: auto;
background: rgba(0,0,0,0.8);
color: white;
padding: 20px;
border-radius: 8px;
font-family: monospace;
min-width: 400px;
`
document.body.appendChild(container)

// Add title
const title = document.createElement('h2')
title.textContent = 'Minecraft Entity Support'
title.style.cssText = 'margin-top: 0; text-align: center;'
container.appendChild(title)

// Test entities
const results: Array<{
entity: string;
supported: boolean;
type?: 'obj' | 'bedrock';
mappedFrom?: string;
textureMap?: boolean;
errors?: string[];
}> = []
const { mcData } = window
const entityNames = Object.keys(mcData.entitiesArray.reduce((acc, entity) => {
acc[entity.name] = true
return acc
}, {}))

// Add loading indicator
const loading = document.createElement('div')
loading.textContent = 'Testing entities...'
loading.style.textAlign = 'center'
container.appendChild(loading)

for (const entity of entityNames) {
const debugFlags: EntityDebugFlags = {}

try {
// eslint-disable-next-line no-new
new EntityMesh(this.version, entity, viewer.world, {}, debugFlags)

results.push({
entity,
supported: !!debugFlags.type || rendererSpecialHandled.includes(entity),
type: debugFlags.type,
mappedFrom: debugFlags.tempMap,
textureMap: debugFlags.textureMap,
errors: debugFlags.errors
})
} catch (e) {
console.error(e)
results.push({
entity,
supported: false,
mappedFrom: debugFlags.tempMap
})
}
}

// Remove loading indicator
loading.remove()

const createSection = (title: string, items: any[], filter: (item: any) => boolean) => {
const section = document.createElement('div')
section.style.marginBottom = '20px'

const sectionTitle = document.createElement('h3')
sectionTitle.textContent = title
sectionTitle.style.textAlign = 'center'
section.appendChild(sectionTitle)

const list = document.createElement('ul')
list.style.cssText = 'padding-left: 20px; list-style-type: none; margin: 0;'

const filteredItems = items.filter(filter)
for (const item of filteredItems) {
const listItem = document.createElement('li')
listItem.style.cssText = 'line-height: 1.4; margin: 8px 0;'

const entityName = document.createElement('strong')
entityName.style.cssText = 'user-select: text;-webkit-user-select: text;'
entityName.textContent = item.entity
listItem.appendChild(entityName)

let text = ''
if (item.mappedFrom) {
text += ` -> ${item.mappedFrom}`
}
if (item.type) {
text += ` - ${item.type}`
}
if (item.textureMap) {
text += ' ⚠️'
}
if (item.errors) {
text += ' ❌'
}

listItem.appendChild(document.createTextNode(text))
list.appendChild(listItem)
}

section.appendChild(list)
return { section, count: filteredItems.length }
}

// Sort results - bedrock first
results.sort((a, b) => {
if (a.type === 'bedrock' && b.type !== 'bedrock') return -1
if (a.type !== 'bedrock' && b.type === 'bedrock') return 1
return a.entity.localeCompare(b.entity)
})

// Add sections
const sections = [
{
title: '❌ Unsupported Entities',
filter: (r: any) => !r.supported && !r.mappedFrom
},
{
title: '⚠️ Partially Supported Entities',
filter: (r: any) => r.mappedFrom
},
{
title: '✅ Supported Entities',
filter: (r: any) => r.supported && !r.mappedFrom
}
]

for (const { title, filter } of sections) {
const { section, count } = createSection(title, results, filter)
if (count > 0) {
container.appendChild(section)
}
}

// log object with errors per entity
const errors = results.filter(r => r.errors).map(r => ({
entity: r.entity,
errors: r.errors
}))
console.log(errors)
}
}
36 changes: 36 additions & 0 deletions renderer/playground/scenes/entities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as THREE from 'three'
import { Vec3 } from 'vec3'
import { BasePlaygroundScene } from '../baseScene'
import { WorldRendererThree } from '../../viewer/lib/worldrendererThree'

export default class extends BasePlaygroundScene {
continuousRender = true

override initGui (): void {
this.params = {
starfield: false,
entity: 'player',
count: 4
}
}

override renderFinish (): void {
if (this.params.starfield) {
;(viewer.world as WorldRendererThree).scene.background = new THREE.Color(0x00_00_00)
;(viewer.world as WorldRendererThree).starField.enabled = true
;(viewer.world as WorldRendererThree).starField.addToScene()
}

for (let i = 0; i < this.params.count; i++) {
for (let j = 0; j < this.params.count; j++) {
for (let k = 0; k < this.params.count; k++) {
viewer.entities.update({
id: i * 1000 + j * 100 + k,
name: this.params.entity,
pos: this.targetPos.offset(i, j, k)
} as any, {})
}
}
}
}
}
33 changes: 33 additions & 0 deletions renderer/playground/scenes/floorRandom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { BasePlaygroundScene } from '../baseScene'

export default class RailsCobwebScene extends BasePlaygroundScene {
viewDistance = 5
continuousRender = true

override initGui (): void {
this.params = {
squareSize: 50
}

super.initGui()
}

setupWorld () {
const squareSize = this.params.squareSize ?? 30
const maxSquareSize = this.viewDistance * 16 * 2
if (squareSize > maxSquareSize) throw new Error(`Square size too big, max is ${maxSquareSize}`)
// const fullBlocks = loadedData.blocksArray.map(x => x.name)
const fullBlocks = loadedData.blocksArray.filter(block => {
const b = this.Block.fromStateId(block.defaultState, 0)
if (b.shapes?.length !== 1) return false
const shape = b.shapes[0]
return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1
})
for (let x = -squareSize; x <= squareSize; x++) {
for (let z = -squareSize; z <= squareSize; z++) {
const i = Math.abs(x + z) * squareSize
worldView!.world.setBlock(this.targetPos.offset(x, 0, z), this.Block.fromStateId(fullBlocks[i % fullBlocks.length].defaultState, 0))
}
}
}
}
147 changes: 147 additions & 0 deletions renderer/playground/scenes/frequentUpdates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { Vec3 } from 'vec3'
import { BasePlaygroundScene } from '../baseScene'

export default class extends BasePlaygroundScene {
viewDistance = 5
continuousRender = true

override initGui (): void {
this.params = {
testActive: false,
testUpdatesPerSecond: 10,
testInitialUpdate: false,
stopGeometryUpdate: false,
manualTest: () => {
this.updateBlock()
},
testNeighborUpdates: () => {
this.testNeighborUpdates()
}
}

super.initGui()
}

lastUpdatedOffset = 0
lastUpdatedId = 2
updateBlock () {
const x = this.lastUpdatedOffset % 16
const z = Math.floor(this.lastUpdatedOffset / 16)
const y = 90
worldView!.setBlockStateId(new Vec3(x, y, z), this.lastUpdatedId++)
this.lastUpdatedOffset++
if (this.lastUpdatedOffset > 16 * 16) this.lastUpdatedOffset = 0
if (this.lastUpdatedId > 500) this.lastUpdatedId = 1
}

testNeighborUpdates () {
viewer.world.setBlockStateId(new Vec3(15, 95, 15), 1)
viewer.world.setBlockStateId(new Vec3(0, 95, 15), 1)
viewer.world.setBlockStateId(new Vec3(15, 95, 0), 1)
viewer.world.setBlockStateId(new Vec3(0, 95, 0), 1)

viewer.world.setBlockStateId(new Vec3(16, 95, 15), 1)
viewer.world.setBlockStateId(new Vec3(-1, 95, 15), 1)
viewer.world.setBlockStateId(new Vec3(15, 95, -1), 1)
viewer.world.setBlockStateId(new Vec3(-1, 95, 0), 1)
setTimeout(() => {
viewer.world.setBlockStateId(new Vec3(16, 96, 16), 1)
viewer.world.setBlockStateId(new Vec3(-1, 96, 16), 1)
viewer.world.setBlockStateId(new Vec3(16, 96, -1), 1)
viewer.world.setBlockStateId(new Vec3(-1, 96, -1), 1)
}, 3000)
}

setupTimer () {
// this.stopRender = true

let lastTime = 0
const tick = () => {
viewer.world.debugStopGeometryUpdate = this.params.stopGeometryUpdate
const updateEach = 1000 / this.params.testUpdatesPerSecond
requestAnimationFrame(tick)
if (!this.params.testActive) return
const updateCount = Math.floor(performance.now() - lastTime) / updateEach
for (let i = 0; i < updateCount; i++) {
this.updateBlock()
}
lastTime = performance.now()
}

requestAnimationFrame(tick)

// const limit = 1000
// const limit = 100
// const limit = 1
// const updatedChunks = new Set<string>()
// const updatedBlocks = new Set<string>()
// let lastSecond = 0
// setInterval(() => {
// const second = Math.floor(performance.now() / 1000)
// if (lastSecond !== second) {
// lastSecond = second
// updatedChunks.clear()
// updatedBlocks.clear()
// }
// const isEven = second % 2 === 0
// if (updatedBlocks.size > limit) {
// return
// }
// const changeBlock = (x, z) => {
// const chunkKey = `${Math.floor(x / 16)},${Math.floor(z / 16)}`
// const key = `${x},${z}`
// if (updatedBlocks.has(chunkKey)) return

// updatedChunks.add(chunkKey)
// worldView!.world.setBlock(this.targetPos.offset(x, 0, z), this.Block.fromStateId(isEven ? 2 : 3, 0))
// updatedBlocks.add(key)
// }
// const { squareSize } = this.params
// const xStart = -squareSize
// const zStart = -squareSize
// const xEnd = squareSize
// const zEnd = squareSize
// for (let x = xStart; x <= xEnd; x += 16) {
// for (let z = zStart; z <= zEnd; z += 16) {
// const key = `${x},${z}`
// if (updatedChunks.has(key)) continue
// changeBlock(x, z)
// return
// }
// }
// for (let x = xStart; x <= xEnd; x += 16) {
// for (let z = zStart; z <= zEnd; z += 16) {
// const key = `${x},${z}`
// if (updatedChunks.has(key)) continue
// changeBlock(x, z)
// return
// }
// }
// }, 1)
}

setupWorld () {
this.worldConfig.showChunkBorders = true

const maxSquareRadius = this.viewDistance * 16
// const fullBlocks = loadedData.blocksArray.map(x => x.name)
const squareSize = maxSquareRadius
for (let x = -squareSize; x <= squareSize; x++) {
for (let z = -squareSize; z <= squareSize; z++) {
const i = Math.abs(x + z) * squareSize
worldView!.world.setBlock(this.targetPos.offset(x, 0, z), this.Block.fromStateId(1, 0))
}
}
let done = false
viewer.world.renderUpdateEmitter.on('update', () => {
if (!viewer.world.allChunksFinished || done) return
done = true
this.setupTimer()
})
setTimeout(() => {
if (this.params.testInitialUpdate) {
this.updateBlock()
}
})
}
}
11 changes: 11 additions & 0 deletions renderer/playground/scenes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// export { default as rotation } from './rotation'
export { default as main } from './main'
export { default as railsCobweb } from './railsCobweb'
export { default as floorRandom } from './floorRandom'
export { default as lightingStarfield } from './lightingStarfield'
export { default as transparencyIssue } from './transparencyIssue'
export { default as rotationIssue } from './rotationIssue'
export { default as entities } from './entities'
export { default as frequentUpdates } from './frequentUpdates'
export { default as slabsOptimization } from './slabsOptimization'
export { default as allEntities } from './allEntities'
39 changes: 39 additions & 0 deletions renderer/playground/scenes/lightingStarfield.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as THREE from 'three'
import { Vec3 } from 'vec3'
import { BasePlaygroundScene } from '../baseScene'
import { WorldRendererThree } from '../../viewer/lib/worldrendererThree'

export default class extends BasePlaygroundScene {
continuousRender = true

override setupWorld (): void {
viewer.world.mesherConfig.enableLighting = true
viewer.world.mesherConfig.skyLight = 0
this.addWorldBlock(0, 0, 0, 'stone')
this.addWorldBlock(0, 0, 1, 'stone')
this.addWorldBlock(1, 0, 0, 'stone')
this.addWorldBlock(1, 0, 1, 'stone')
// chess like
worldView?.world.setBlockLight(this.targetPos.offset(0, 1, 0), 15)
worldView?.world.setBlockLight(this.targetPos.offset(0, 1, 1), 0)
worldView?.world.setBlockLight(this.targetPos.offset(1, 1, 0), 0)
worldView?.world.setBlockLight(this.targetPos.offset(1, 1, 1), 15)
}

override renderFinish (): void {
viewer.scene.background = new THREE.Color(0x00_00_00)
// starfield and test entities
;(viewer.world as WorldRendererThree).starField.enabled = true
;(viewer.world as WorldRendererThree).starField.addToScene()
viewer.entities.update({
id: 0,
name: 'player',
pos: this.targetPos.clone()
} as any, {})
viewer.entities.update({
id: 1,
name: 'creeper',
pos: this.targetPos.offset(1, 0, 0)
} as any, {})
}
}
314 changes: 314 additions & 0 deletions renderer/playground/scenes/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
// eslint-disable-next-line import/no-named-as-default
import GUI, { Controller } from 'lil-gui'
import * as THREE from 'three'
import JSZip from 'jszip'
import { BasePlaygroundScene } from '../baseScene'
import { TWEEN_DURATION } from '../../viewer/lib/entities'
import { EntityMesh } from '../../viewer/lib/entity/EntityMesh'

class MainScene extends BasePlaygroundScene {
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor (...args) {
//@ts-expect-error
super(...args)
}

override initGui (): void {
// initial values
this.params = {
version: globalThis.includedVersions.at(-1),
skipQs: '',
block: '',
metadata: 0,
supportBlock: false,
entity: '',
removeEntity () {
this.entity = ''
},
entityRotate: false,
camera: '',
playSound () { },
blockIsomorphicRenderBundle () { },
modelVariant: 0
}
this.metadataGui = this.gui.add(this.params, 'metadata')
this.paramOptions = {
version: {
options: globalThis.includedVersions,
hide: false
},
block: {
options: mcData.blocksArray.map(b => b.name).sort((a, b) => a.localeCompare(b))
},
entity: {
options: mcData.entitiesArray.map(b => b.name).sort((a, b) => a.localeCompare(b))
},
camera: {
hide: true,
}
}
super.initGui()
}

blockProps = {}
metadataFolder: GUI | undefined
metadataGui: Controller

override onParamUpdate = {
version () {
// if (initialUpdate) return
// viewer.world.texturesVersion = params.version
// viewer.world.updateTexturesData()
// todo warning
},
block: () => {
this.blockProps = {}
this.metadataFolder?.destroy()
const block = mcData.blocksByName[this.params.block]
if (!block) return
console.log('block', block.name)
const props = new this.Block(block.id, 0, 0).getProperties()
const { states } = mcData.blocksByStateId[this.getBlock()?.minStateId] ?? {}
this.metadataFolder = this.gui.addFolder('metadata')
if (states) {
for (const state of states) {
let defaultValue: string | number | boolean
if (state.values) { // int, enum
defaultValue = state.values[0]
} else {
switch (state.type) {
case 'bool':
defaultValue = false
break
case 'int':
defaultValue = 0
break
case 'direction':
defaultValue = 'north'
break

default:
continue
}
}
this.blockProps[state.name] = defaultValue
if (state.values) {
this.metadataFolder.add(this.blockProps, state.name, state.values)
} else {
this.metadataFolder.add(this.blockProps, state.name)
}
}
} else {
for (const [name, value] of Object.entries(props)) {
this.blockProps[name] = value
this.metadataFolder.add(this.blockProps, name)
}
}
console.log('props', this.blockProps)
this.metadataFolder.open()
},
entity: () => {
this.continuousRender = this.params.entity === 'player'
this.entityUpdateShared()
if (!this.params.entity) return
if (this.params.entity === 'player') {
viewer.entities.updatePlayerSkin('id', viewer.entities.entities.id.username, undefined, true, true)
viewer.entities.playAnimation('id', 'running')
}
// let prev = false
// setInterval(() => {
// viewer.entities.playAnimation('id', prev ? 'running' : 'idle')
// prev = !prev
// }, 1000)

EntityMesh.getStaticData(this.params.entity)
// entityRotationFolder.destroy()
// entityRotationFolder = gui.addFolder('entity metadata')
// entityRotationFolder.add(params, 'entityRotate')
// entityRotationFolder.open()
},
supportBlock: () => {
viewer.setBlockStateId(this.targetPos.offset(0, -1, 0), this.params.supportBlock ? 1 : 0)
},
modelVariant: () => {
viewer.world.mesherConfig.debugModelVariant = this.params.modelVariant === 0 ? undefined : [this.params.modelVariant]
}
}

entityUpdateShared () {
viewer.entities.clear()
if (!this.params.entity) return
worldView!.emit('entity', {
id: 'id', name: this.params.entity, pos: this.targetPos.offset(0.5, 1, 0.5), width: 1, height: 1, username: localStorage.testUsername, yaw: Math.PI, pitch: 0
})
const enableSkeletonDebug = (obj) => {
const { children, isSkeletonHelper } = obj
if (!Array.isArray(children)) return
if (isSkeletonHelper) {
obj.visible = true
return
}
for (const child of children) {
if (typeof child === 'object') enableSkeletonDebug(child)
}
}
enableSkeletonDebug(viewer.entities.entities['id'])
setTimeout(() => {
viewer.render()
}, TWEEN_DURATION)
}

blockIsomorphicRenderBundle () {
const { renderer } = viewer

const canvas = renderer.domElement
const onlyCurrent = !confirm('Ok - render all blocks, Cancel - render only current one')
const sizeRaw = prompt('Size', '512')
if (!sizeRaw) return
const size = parseInt(sizeRaw, 10)
// const size = 512

this.ignoreResize = true
canvas.width = size
canvas.height = size
renderer.setSize(size, size)

//@ts-expect-error
viewer.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10)
viewer.scene.background = null

const rad = THREE.MathUtils.degToRad(-120)
viewer.directionalLight.position.set(
Math.cos(rad),
Math.sin(rad),
0.2
).normalize()
viewer.directionalLight.intensity = 1

const cameraPos = this.targetPos.offset(2, 2, 2)
const pitch = THREE.MathUtils.degToRad(-30)
const yaw = THREE.MathUtils.degToRad(45)
viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX')
// viewer.camera.lookAt(center.x + 0.5, center.y + 0.5, center.z + 0.5)
viewer.camera.position.set(cameraPos.x + 1, cameraPos.y + 0.5, cameraPos.z + 1)

const allBlocks = mcData.blocksArray.map(b => b.name)
// const allBlocks = ['stone', 'warped_slab']

let blockCount = 1
let blockName = allBlocks[0]

const updateBlock = () => {
// viewer.setBlockStateId(targetPos, mcData.blocksByName[blockName].minStateId)
this.params.block = blockName
// todo cleanup (introduce getDefaultState)
// TODO
// onUpdate.block()
// applyChanges(false, true)
}
void viewer.waitForChunksToRender().then(async () => {
// wait for next macro task
await new Promise(resolve => {
setTimeout(resolve, 0)
})
if (onlyCurrent) {
viewer.render()
onWorldUpdate()
} else {
// will be called on every render update
viewer.world.renderUpdateEmitter.addListener('update', onWorldUpdate)
updateBlock()
}
})

const zip = new JSZip()
zip.file('description.txt', 'Generated with mcraft.fun/playground')

const end = async () => {
// download zip file

const a = document.createElement('a')
const blob = await zip.generateAsync({ type: 'blob' })
const dataUrlZip = URL.createObjectURL(blob)
a.href = dataUrlZip
a.download = 'blocks_render.zip'
a.click()
URL.revokeObjectURL(dataUrlZip)
console.log('end')

viewer.world.renderUpdateEmitter.removeListener('update', onWorldUpdate)
}

async function onWorldUpdate () {
// await new Promise(resolve => {
// setTimeout(resolve, 50)
// })
const dataUrl = canvas.toDataURL('image/png')

zip.file(`${blockName}.png`, dataUrl.split(',')[1], { base64: true })

if (onlyCurrent) {
end()
} else {
nextBlock()
}
}
const nextBlock = async () => {
blockName = allBlocks[blockCount++]
console.log(allBlocks.length, '/', blockCount, blockName)
if (blockCount % 5 === 0) {
await new Promise(resolve => {
setTimeout(resolve, 100)
})
}
if (blockName) {
updateBlock()
} else {
end()
}
}
}

getBlock () {
return mcData.blocksByName[this.params.block || 'air']
}

// applyChanges (metadataUpdate = false, skipQs = false) {
override onParamsUpdate (paramName: string, object: any) {
const metadataUpdate = paramName === 'metadata'

const blockId = this.getBlock()?.id
let block: import('prismarine-block').Block
if (metadataUpdate) {
block = new this.Block(blockId, 0, this.params.metadata)
Object.assign(this.blockProps, block.getProperties())
for (const _child of this.metadataFolder!.children) {
const child = _child as import('lil-gui').Controller
child.updateDisplay()
}
} else {
try {
block = this.Block.fromProperties(blockId ?? -1, this.blockProps, 0)
} catch (err) {
console.error(err)
block = this.Block.fromStateId(0, 0)
}
}

worldView!.setBlockStateId(this.targetPos, block.stateId ?? 0)
console.log('up stateId', block.stateId)
this.params.metadata = block.metadata
this.metadataGui.updateDisplay()
}

override renderFinish () {
for (const update of Object.values(this.onParamUpdate)) {
// update(true)
update()
}
this.onParamsUpdate('', {})
this.gui.openAnimated()
}
}

export default MainScene
14 changes: 14 additions & 0 deletions renderer/playground/scenes/railsCobweb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BasePlaygroundScene } from '../baseScene'

export default class RailsCobwebScene extends BasePlaygroundScene {
setupWorld () {
this.addWorldBlock(0, 0, 0, 'cobweb')
this.addWorldBlock(0, -1, 0, 'cobweb')
this.addWorldBlock(1, -1, 0, 'cobweb')
this.addWorldBlock(1, 0, 0, 'cobweb')

this.addWorldBlock(0, 0, 1, 'powered_rail', { shape: 'north_south', waterlogged: false })
this.addWorldBlock(0, 0, 2, 'powered_rail', { shape: 'ascending_south', waterlogged: false })
this.addWorldBlock(0, 1, 3, 'powered_rail', { shape: 'north_south', waterlogged: false })
}
}
7 changes: 7 additions & 0 deletions renderer/playground/scenes/rotationIssue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BasePlaygroundScene } from '../baseScene'

export default class RotationIssueScene extends BasePlaygroundScene {
setupWorld () {
// todo
}
}
15 changes: 15 additions & 0 deletions renderer/playground/scenes/slabsOptimization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BasePlaygroundScene } from '../baseScene'

export default class extends BasePlaygroundScene {
expectedNumberOfFaces = 30

setupWorld () {
this.addWorldBlock(0, 1, 0, 'stone_slab')
this.addWorldBlock(0, 0, 0, 'stone')
this.addWorldBlock(0, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(0, -1, -1, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(0, -1, 1, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(-1, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
this.addWorldBlock(1, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
}
}
11 changes: 11 additions & 0 deletions renderer/playground/scenes/transparencyIssue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BasePlaygroundScene } from '../baseScene'

export default class extends BasePlaygroundScene {
setupWorld () {
this.addWorldBlock(0, 0, 0, 'water')
this.addWorldBlock(0, 1, 0, 'lime_stained_glass')
this.addWorldBlock(0, 0, -1, 'lime_stained_glass')
this.addWorldBlock(0, -1, 0, 'lime_stained_glass')
this.addWorldBlock(0, -1, -1, 'stone')
}
}
79 changes: 79 additions & 0 deletions renderer/playground/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import WorldLoader, { world } from 'prismarine-world'
import ChunkLoader from 'prismarine-chunk'

export type BlockFaceType = {
side: number
textureIndex: number
tint?: [number, number, number]
isTransparent?: boolean

// for testing
face?: string
neighbor?: string
light?: number
}

export type BlockType = {
faces: BlockFaceType[]

// for testing
block: string
}

export const makeError = (str: string) => {
reportError?.(str)
}
export const makeErrorCritical = (str: string) => {
throw new Error(str)
}

export const getSyncWorld = (version: string): world.WorldSync => {
const World = (WorldLoader as any)(version)
const Chunk = (ChunkLoader as any)(version)

const world = new World(version).sync

const methods = getAllMethods(world)
for (const method of methods) {
if (method.startsWith('set') && method !== 'setColumn') {
const oldMethod = world[method].bind(world)
world[method] = (...args) => {
const arg = args[0]
if (arg.x !== undefined && !world.getColumnAt(arg)) {
world.setColumn(Math.floor(arg.x / 16), Math.floor(arg.z / 16), new Chunk(undefined as any))
}
oldMethod(...args)
}
}
}

return world
}

function getAllMethods (obj) {
const methods = new Set()
let currentObj = obj

do {
for (const name of Object.getOwnPropertyNames(currentObj)) {
if (typeof obj[name] === 'function' && name !== 'constructor') {
methods.add(name)
}
}
} while ((currentObj = Object.getPrototypeOf(currentObj)))

return [...methods] as string[]
}

export const delayedIterator = async <T> (arr: T[], delay: number, exec: (item: T, index: number) => void, chunkSize = 1) => {
// if delay is 0 then don't use setTimeout
for (let i = 0; i < arr.length; i += chunkSize) {
if (delay) {
// eslint-disable-next-line no-await-in-loop
await new Promise(resolve => {
setTimeout(resolve, delay)
})
}
exec(arr[i], i)
}
}
59 changes: 59 additions & 0 deletions renderer/rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { defineConfig, mergeRsbuildConfig, RsbuildPluginAPI } from '@rsbuild/core';
import supportedVersions from '../src/supportedVersions.mjs'
import childProcess from 'child_process'
import path, { dirname, join } from 'path'
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
import fs from 'fs'
import fsExtra from 'fs-extra'
import { appAndRendererSharedConfig, rspackViewerConfig } from './rsbuildSharedConfig';

const mcDataPath = join(__dirname, '../generated/minecraft-data-optimized.json')

// if (!fs.existsSync('./playground/textures')) {
// fsExtra.copySync('node_modules/mc-assets/dist/other-textures/latest/entity', './playground/textures/entity')
// }

if (!fs.existsSync(mcDataPath)) {
childProcess.execSync('tsx ./scripts/makeOptimizedMcData.mjs', { stdio: 'inherit', cwd: path.join(__dirname, '..') })
}

export default mergeRsbuildConfig(
appAndRendererSharedConfig(),
defineConfig({
html: {
template: join(__dirname, './playground.html'),
},
output: {
cleanDistPath: false,
distPath: {
root: join(__dirname, './dist'),
},
},
server: {
port: 9090,
},
source: {
entry: {
index: join(__dirname, './playground/playground.ts')
},
define: {
'globalThis.includedVersions': JSON.stringify(supportedVersions),
},
},
plugins: [
{
name: 'test',
setup (build: RsbuildPluginAPI) {
const prep = async () => {
fsExtra.copySync(join(__dirname, '../node_modules/mc-assets/dist/other-textures/latest/entity'), join(__dirname, './dist/textures/entity'))
}
build.onBeforeBuild(async () => {
await prep()
})
build.onBeforeStartDevServer(() => prep())
},
},
],
})
)
101 changes: 101 additions & 0 deletions renderer/rsbuildSharedConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { defineConfig, ModifyRspackConfigUtils } from '@rsbuild/core';
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
import { pluginReact } from '@rsbuild/plugin-react';
import path from 'path'

export const appAndRendererSharedConfig = () => defineConfig({
dev: {
progressBar: true,
writeToDisk: true,
watchFiles: {
paths: [
path.join(__dirname, './dist/webgpuRendererWorker.js'),
path.join(__dirname, './dist/mesher.js'),
]
},
},
output: {
polyfill: 'usage',
// 50kb limit for data uri
dataUriLimit: 50 * 1024,
assetPrefix: './',
},
source: {
alias: {
fs: path.join(__dirname, `../src/shims/fs.js`),
http: 'http-browserify',
stream: 'stream-browserify',
net: 'net-browserify',
'minecraft-protocol$': 'minecraft-protocol/src/index.js',
'buffer$': 'buffer',
// avoid bundling, not used on client side
'prismarine-auth': path.join(__dirname, `../src/shims/prismarineAuthReplacement.ts`),
perf_hooks: path.join(__dirname, `../src/shims/perf_hooks_replacement.js`),
crypto: path.join(__dirname, `../src/shims/crypto.js`),
dns: path.join(__dirname, `../src/shims/dns.js`),
yggdrasil: path.join(__dirname, `../src/shims/yggdrasilReplacement.ts`),
'three$': 'three/src/Three.js',
'stats.js$': 'stats.js/src/Stats.js',
},
define: {
'process.platform': '"browser"',
},
decorators: {
version: 'legacy', // default is a lie
},
},
server: {
htmlFallback: false,
// publicDir: false,
headers: {
// enable shared array buffer
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
open: process.env.OPEN_BROWSER === 'true',
},
plugins: [
pluginReact(),
pluginNodePolyfill()
],
tools: {
rspack (config, helpers) {
rspackViewerConfig(config, helpers)
}
},
})

export const rspackViewerConfig = (config, { appendPlugins, addRules, rspack }: ModifyRspackConfigUtils) => {
appendPlugins(new rspack.NormalModuleReplacementPlugin(/data/, (resource) => {
let absolute: string
const request = resource.request.replaceAll('\\', '/')
absolute = path.join(resource.context, request).replaceAll('\\', '/')
if (request.includes('minecraft-data/data/pc/1.')) {
console.log('Error: incompatible resource', request, resource.contextInfo.issuer)
process.exit(1)
// throw new Error(`${resource.request} was requested by ${resource.contextInfo.issuer}`)
}
if (absolute.endsWith('/minecraft-data/data.js')) {
resource.request = path.join(__dirname, `../src/shims/minecraftData.ts`)
}
}))
addRules([
{
test: /\.obj$/,
type: 'asset/source',
},
{
test: /\.wgsl$/,
type: 'asset/source',
},
{
test: /\.mp3$/,
type: 'asset/source',
}
])
config.ignoreWarnings = [
/the request of a dependency is an expression/,
/Unsupported pseudo class or element: xr-overlay/
]

}
2 changes: 2 additions & 0 deletions renderer/viewer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
public/*
!public/.empty
6 changes: 6 additions & 0 deletions renderer/viewer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
Viewer: require('./lib/viewer').Viewer,
WorldDataEmitter: require('./lib/worldDataEmitter').WorldDataEmitter,
Entity: require('./lib/entity/EntityMesh'),
getBufferFromStream: require('./lib/simpleUtils').getBufferFromStream
}
174 changes: 174 additions & 0 deletions renderer/viewer/lib/DebugGui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// eslint-disable-next-line import/no-named-as-default
import GUI from 'lil-gui'

export interface ParamMeta {
min?: number
max?: number
step?: number
}

export class DebugGui {
private gui: GUI
private readonly storageKey: string
private target: any
private readonly params: string[]
private readonly paramsMeta: Record<string, ParamMeta>
private _visible = false // Default to not visible
private readonly initialValues: Record<string, any> = {} // Store initial values
private initialized = false

constructor (id: string, target: any, params?: string[], paramsMeta?: Record<string, ParamMeta>) {
this.gui = new GUI()
this.storageKey = `debug_params_${id}`
this.target = target
this.paramsMeta = paramsMeta ?? {}
this.params = params ?? Object.keys(target)

// Store initial values
for (const param of this.params) {
this.initialValues[param] = target[param]
}

// Hide by default
this.gui.domElement.style.display = 'none'
}

// Initialize and show the GUI
activate () {
if (!this.initialized) {
this.loadSavedValues()
this.setupControls()
this.initialized = true
}
this.show()
return this
}

// Getter for visibility
get visible (): boolean {
return this._visible
}

// Setter for visibility
set visible (value: boolean) {
this._visible = value
this.gui.domElement.style.display = value ? 'block' : 'none'
this.saveVisibility()
}

private loadSavedValues () {
try {
const saved = localStorage.getItem(this.storageKey)
if (saved) {
const values = JSON.parse(saved)
// Apply saved values to target
for (const param of this.params) {
if (param in values) {
const value = values[param]
if (value !== null) {
this.target[param] = value
}
}
}
}
} catch (e) {
console.warn('Failed to load debug values:', e)
}
}

private saveValues (deleteKey = false) {
try {
const values = {}
for (const param of this.params) {
values[param] = this.target[param]
}
if (deleteKey) {
localStorage.removeItem(this.storageKey)
} else {
localStorage.setItem(this.storageKey, JSON.stringify(values))
}
} catch (e) {
console.warn('Failed to save debug values:', e)
}
}

private saveVisibility () {
try {
localStorage.setItem(`${this.storageKey}_visible`, this._visible.toString())
} catch (e) {
console.warn('Failed to save debug visibility:', e)
}
}

private setupControls () {
// Add visibility toggle at the top
this.gui.add(this, 'visible').name('Show Controls')
this.gui.add({ resetAll: () => {
for (const param of this.params) {
this.target[param] = this.initialValues[param]
}
this.saveValues(true)
this.gui.destroy()
this.gui = new GUI()
this.setupControls()
} }, 'resetAll').name('Reset All Parameters')

for (const param of this.params) {
const value = this.target[param]
const meta = this.paramsMeta[param] ?? {}

if (typeof value === 'number') {
// For numbers, use meta values or calculate reasonable defaults
const min = meta.min ?? value - Math.abs(value * 2)
const max = meta.max ?? value + Math.abs(value * 2)
const step = meta.step ?? Math.abs(value) / 100

this.gui.add(this.target, param, min, max, step)
.onChange(() => this.saveValues())
} else if (typeof value === 'boolean') {
// For booleans, create a checkbox
this.gui.add(this.target, param)
.onChange(() => this.saveValues())
} else if (typeof value === 'string' && ['x', 'y', 'z'].includes(param)) {
// Special case for xyz coordinates
const min = meta.min ?? -10
const max = meta.max ?? 10
const step = meta.step ?? 0.1

this.gui.add(this.target, param, min, max, step)
.onChange(() => this.saveValues())
} else if (Array.isArray(value)) {
// For arrays, create a dropdown
this.gui.add(this.target, param, value)
.onChange(() => this.saveValues())
}
}
}

// Method to manually trigger save
save () {
this.saveValues()
this.saveVisibility()
}

// Method to destroy the GUI and clean up
destroy () {
this.saveVisibility()
this.gui.destroy()
}

// Toggle visibility
toggle () {
this.visible = !this.visible
}

// Show the GUI
show () {
this.visible = true
}

// Hide the GUI
hide () {
this.visible = false
}
}
85 changes: 85 additions & 0 deletions renderer/viewer/lib/animationController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as tweenJs from '@tweenjs/tween.js'

export class AnimationController {
private currentAnimation: tweenJs.Group | null = null
private isAnimating = false
private cancelRequested = false
private completionCallbacks: Array<() => void> = []
private currentCancelCallback: (() => void) | null = null

/** Main method */
async startAnimation (createAnimation: () => tweenJs.Group, onCancelled?: () => void): Promise<void> {
if (this.isAnimating) {
await this.cancelCurrentAnimation()
}

return new Promise((resolve) => {
this.isAnimating = true
this.cancelRequested = false
this.currentCancelCallback = onCancelled ?? null
this.currentAnimation = createAnimation()

this.completionCallbacks.push(() => {
this.isAnimating = false
this.currentAnimation = null
resolve()
})
})
}

/** Main method */
async cancelCurrentAnimation (): Promise<void> {
if (!this.isAnimating) return

if (this.currentCancelCallback) {
const callback = this.currentCancelCallback
this.currentCancelCallback = null
callback()
}

return new Promise((resolve) => {
this.cancelRequested = true
this.completionCallbacks.push(() => {
resolve()
})
})
}

animationCycleFinish () {
if (this.cancelRequested) this.forceFinish()
}

forceFinish (callComplete = true) {
if (!this.isAnimating) return

if (this.currentAnimation) {
for (const tween of this.currentAnimation.getAll()) tween.stop()
this.currentAnimation.removeAll()
this.currentAnimation = null
}

this.isAnimating = false
this.cancelRequested = false

const callbacks = [...this.completionCallbacks]
this.completionCallbacks = []
if (callComplete) {
for (const cb of callbacks) cb()
}
}

/** Required method */
update () {
if (this.currentAnimation) {
this.currentAnimation.update()
}
}

get isActive () {
return this.isAnimating
}

get shouldCancel () {
return this.cancelRequested
}
}
88 changes: 88 additions & 0 deletions renderer/viewer/lib/basePlayerState.ts
94 changes: 94 additions & 0 deletions renderer/viewer/lib/cameraBobbing.ts
29 changes: 29 additions & 0 deletions renderer/viewer/lib/cleanupDecorator.ts
1,141 changes: 1,141 additions & 0 deletions renderer/viewer/lib/entities.ts
553 changes: 553 additions & 0 deletions renderer/viewer/lib/entity/EntityMesh.ts
103 changes: 103 additions & 0 deletions renderer/viewer/lib/entity/animations.js
204 changes: 204 additions & 0 deletions renderer/viewer/lib/entity/armorModels.json
35 changes: 35 additions & 0 deletions renderer/viewer/lib/entity/armorModels.ts
6,230 changes: 6,230 additions & 0 deletions renderer/viewer/lib/entity/entities.json
38 changes: 38 additions & 0 deletions renderer/viewer/lib/entity/exportedModels.js
1 change: 1 addition & 0 deletions renderer/viewer/lib/entity/externalTextures.json
325 changes: 325 additions & 0 deletions renderer/viewer/lib/entity/models/allay.obj
60 changes: 60 additions & 0 deletions renderer/viewer/lib/entity/models/arrow.obj
509 changes: 509 additions & 0 deletions renderer/viewer/lib/entity/models/axolotl.obj
601 changes: 601 additions & 0 deletions renderer/viewer/lib/entity/models/blaze.obj
417 changes: 417 additions & 0 deletions renderer/viewer/lib/entity/models/boat.obj
1,061 changes: 1,061 additions & 0 deletions renderer/viewer/lib/entity/models/camel.obj
509 changes: 509 additions & 0 deletions renderer/viewer/lib/entity/models/cat.obj
371 changes: 371 additions & 0 deletions renderer/viewer/lib/entity/models/chicken.obj
371 changes: 371 additions & 0 deletions renderer/viewer/lib/entity/models/cod.obj
279 changes: 279 additions & 0 deletions renderer/viewer/lib/entity/models/creeper.obj
371 changes: 371 additions & 0 deletions renderer/viewer/lib/entity/models/dolphin.obj
2,993 changes: 2,993 additions & 0 deletions renderer/viewer/lib/entity/models/ender_dragon.obj
325 changes: 325 additions & 0 deletions renderer/viewer/lib/entity/models/enderman.obj
187 changes: 187 additions & 0 deletions renderer/viewer/lib/entity/models/endermite.obj
463 changes: 463 additions & 0 deletions renderer/viewer/lib/entity/models/fox.obj
739 changes: 739 additions & 0 deletions renderer/viewer/lib/entity/models/frog.obj
463 changes: 463 additions & 0 deletions renderer/viewer/lib/entity/models/ghast.obj
601 changes: 601 additions & 0 deletions renderer/viewer/lib/entity/models/goat.obj
1,015 changes: 1,015 additions & 0 deletions renderer/viewer/lib/entity/models/guardian.obj
1,061 changes: 1,061 additions & 0 deletions renderer/viewer/lib/entity/models/horse.obj
509 changes: 509 additions & 0 deletions renderer/viewer/lib/entity/models/llama.obj
233 changes: 233 additions & 0 deletions renderer/viewer/lib/entity/models/minecart.obj
509 changes: 509 additions & 0 deletions renderer/viewer/lib/entity/models/parrot.obj
739 changes: 739 additions & 0 deletions renderer/viewer/lib/entity/models/piglin.obj
371 changes: 371 additions & 0 deletions renderer/viewer/lib/entity/models/pillager.obj
555 changes: 555 additions & 0 deletions renderer/viewer/lib/entity/models/rabbit.obj
555 changes: 555 additions & 0 deletions renderer/viewer/lib/entity/models/sheep.obj
141 changes: 141 additions & 0 deletions renderer/viewer/lib/entity/models/shulker.obj
693 changes: 693 additions & 0 deletions renderer/viewer/lib/entity/models/sniffer.obj
509 changes: 509 additions & 0 deletions renderer/viewer/lib/entity/models/spider.obj
95 changes: 95 additions & 0 deletions renderer/viewer/lib/entity/models/tadpole.obj
371 changes: 371 additions & 0 deletions renderer/viewer/lib/entity/models/turtle.obj
325 changes: 325 additions & 0 deletions renderer/viewer/lib/entity/models/vex.obj
509 changes: 509 additions & 0 deletions renderer/viewer/lib/entity/models/villager.obj
463 changes: 463 additions & 0 deletions renderer/viewer/lib/entity/models/warden.obj
647 changes: 647 additions & 0 deletions renderer/viewer/lib/entity/models/witch.obj
509 changes: 509 additions & 0 deletions renderer/viewer/lib/entity/models/wolf.obj
463 changes: 463 additions & 0 deletions renderer/viewer/lib/entity/models/zombie_villager.obj
1 change: 1 addition & 0 deletions renderer/viewer/lib/entity/objModels.js
277 changes: 277 additions & 0 deletions renderer/viewer/lib/guiRenderer.ts
85 changes: 85 additions & 0 deletions renderer/viewer/lib/hand.ts
911 changes: 911 additions & 0 deletions renderer/viewer/lib/holdingBlock.ts
203 changes: 203 additions & 0 deletions renderer/viewer/lib/mesher/mesher.ts
650 changes: 650 additions & 0 deletions renderer/viewer/lib/mesher/models.ts
142 changes: 142 additions & 0 deletions renderer/viewer/lib/mesher/modelsGeometryCommon.ts
57 changes: 57 additions & 0 deletions renderer/viewer/lib/mesher/shared.ts
270 changes: 270 additions & 0 deletions renderer/viewer/lib/mesher/standaloneRenderer.ts
76 changes: 76 additions & 0 deletions renderer/viewer/lib/mesher/test/mesherTester.ts
20 changes: 20 additions & 0 deletions renderer/viewer/lib/mesher/test/playground.ts
58 changes: 58 additions & 0 deletions renderer/viewer/lib/mesher/test/tests.test.ts
270 changes: 270 additions & 0 deletions renderer/viewer/lib/mesher/world.ts
1 change: 1 addition & 0 deletions renderer/viewer/lib/mesher/worldConstants.ts
714 changes: 714 additions & 0 deletions renderer/viewer/lib/moreBlockDataGenerated.json
142 changes: 142 additions & 0 deletions renderer/viewer/lib/primitives.js
11 changes: 11 additions & 0 deletions renderer/viewer/lib/renderUtils.js
35 changes: 35 additions & 0 deletions renderer/viewer/lib/simpleUtils.ts
168 changes: 168 additions & 0 deletions renderer/viewer/lib/smoothSwitcher.ts
18 changes: 18 additions & 0 deletions renderer/viewer/lib/threeJsUtils.ts
48 changes: 48 additions & 0 deletions renderer/viewer/lib/ui/newStats.ts
47 changes: 47 additions & 0 deletions renderer/viewer/lib/utils.ts
23 changes: 23 additions & 0 deletions renderer/viewer/lib/utils/proxy.ts
27 changes: 27 additions & 0 deletions renderer/viewer/lib/utils/skins.ts
325 changes: 325 additions & 0 deletions renderer/viewer/lib/viewer.ts
122 changes: 122 additions & 0 deletions renderer/viewer/lib/viewerWrapper.ts
60 changes: 60 additions & 0 deletions renderer/viewer/lib/workerProxy.ts
251 changes: 251 additions & 0 deletions renderer/viewer/lib/worldDataEmitter.ts
671 changes: 671 additions & 0 deletions renderer/viewer/lib/worldrendererCommon.ts
568 changes: 568 additions & 0 deletions renderer/viewer/lib/worldrendererThree.ts
18 changes: 18 additions & 0 deletions renderer/viewer/prepare/utils.ts
21 changes: 21 additions & 0 deletions renderer/viewer/sign-renderer/index.html
139 changes: 139 additions & 0 deletions renderer/viewer/sign-renderer/index.ts
1 change: 1 addition & 0 deletions renderer/viewer/sign-renderer/noop.js
13 changes: 13 additions & 0 deletions renderer/viewer/sign-renderer/package.json
33 changes: 33 additions & 0 deletions renderer/viewer/sign-renderer/playground.ts
73 changes: 73 additions & 0 deletions renderer/viewer/sign-renderer/tests.test.ts
10 changes: 10 additions & 0 deletions renderer/viewer/sign-renderer/vite.config.ts
168 changes: 168 additions & 0 deletions rsbuild.config.ts
110 changes: 110 additions & 0 deletions scripts/build.js
160 changes: 160 additions & 0 deletions scripts/buildNpmReact.ts
35 changes: 35 additions & 0 deletions scripts/dockerPrepare.mjs
13 changes: 13 additions & 0 deletions scripts/downloadSoundsMap.mjs
27 changes: 27 additions & 0 deletions scripts/esbuildPlugins.mjs
51 changes: 51 additions & 0 deletions scripts/genMcDataTypes.ts
16 changes: 16 additions & 0 deletions scripts/genPixelartTypes.ts
36 changes: 36 additions & 0 deletions scripts/genShims.ts
41 changes: 41 additions & 0 deletions scripts/getMissingRecipes.mjs
32 changes: 32 additions & 0 deletions scripts/githubActions.mjs
245 changes: 245 additions & 0 deletions scripts/makeOptimizedMcData.mjs
59 changes: 59 additions & 0 deletions scripts/optimizeBlockCollisions.ts
52 changes: 52 additions & 0 deletions scripts/outdatedGitPackages.mjs
288 changes: 288 additions & 0 deletions scripts/prepareSounds.mjs
8 changes: 8 additions & 0 deletions scripts/replaceFavicon.mjs
116 changes: 116 additions & 0 deletions scripts/testOptimizedMcdata.ts
60 changes: 60 additions & 0 deletions scripts/updateHandledPackets.mjs
109 changes: 109 additions & 0 deletions scripts/uploadSoundFiles.ts
67 changes: 67 additions & 0 deletions scripts/uploadSounds.ts
85 changes: 67 additions & 18 deletions server.js
64 changes: 64 additions & 0 deletions src/api/mcStatusApi.ts
59 changes: 59 additions & 0 deletions src/appConfig.ts
107 changes: 107 additions & 0 deletions src/appParams.ts
36 changes: 36 additions & 0 deletions src/appStatus.ts
129 changes: 129 additions & 0 deletions src/basicSounds.ts
49 changes: 49 additions & 0 deletions src/botUtils.ts
692 changes: 692 additions & 0 deletions src/browserfs.ts
146 changes: 146 additions & 0 deletions src/builtinCommands.ts
85 changes: 85 additions & 0 deletions src/cameraRotationControls.ts
66 changes: 66 additions & 0 deletions src/chatUtils.test.ts
124 changes: 124 additions & 0 deletions src/chatUtils.ts
70 changes: 70 additions & 0 deletions src/connect.ts
950 changes: 950 additions & 0 deletions src/controls.ts
219 changes: 219 additions & 0 deletions src/core/progressReporter.ts
17 changes: 17 additions & 0 deletions src/createLocalServer.ts
109 changes: 109 additions & 0 deletions src/customChannels.ts
78 changes: 78 additions & 0 deletions src/customClient.js
91 changes: 91 additions & 0 deletions src/customCommands.ts
20 changes: 20 additions & 0 deletions src/customServer.ts
47 changes: 47 additions & 0 deletions src/dayCycle.ts
38 changes: 38 additions & 0 deletions src/defaultLocalServerOptions.js
12 changes: 12 additions & 0 deletions src/devReload.ts
161 changes: 161 additions & 0 deletions src/devtools.ts
109 changes: 109 additions & 0 deletions src/downloadAndOpenFile.ts
119 changes: 119 additions & 0 deletions src/dragndrop.ts
188 changes: 188 additions & 0 deletions src/entities.ts
12 changes: 12 additions & 0 deletions src/errorLoadingScreenHelpers.ts
Empty file added src/external/index.ts
Empty file.
27 changes: 27 additions & 0 deletions src/flyingSquidEvents.ts
43 changes: 43 additions & 0 deletions src/flyingSquidUtils.ts
17 changes: 17 additions & 0 deletions src/gameUnload.ts
543 changes: 543 additions & 0 deletions src/generatedClientPackets.ts
1,474 changes: 1,474 additions & 0 deletions src/generatedServerPackets.ts
17 changes: 17 additions & 0 deletions src/getCollisionInteractionShapes.ts
14 changes: 14 additions & 0 deletions src/getCollisionShapes.ts
37 changes: 37 additions & 0 deletions src/globalDomListeners.ts
157 changes: 157 additions & 0 deletions src/globalState.ts
37 changes: 37 additions & 0 deletions src/globals.d.ts
10 changes: 10 additions & 0 deletions src/globals.js
111 changes: 111 additions & 0 deletions src/googledrive.ts
9 changes: 9 additions & 0 deletions src/importsWorkaround.js
1,059 changes: 1,059 additions & 0 deletions src/index.ts
6,593 changes: 6,593 additions & 0 deletions src/interactionShapesGenerated.json
608 changes: 608 additions & 0 deletions src/inventoryWindows.ts
1,290 changes: 1,290 additions & 0 deletions src/itemsDescriptions.ts
198 changes: 198 additions & 0 deletions src/loadSave.ts
222 changes: 222 additions & 0 deletions src/localServerMultiplayer.ts
39 changes: 39 additions & 0 deletions src/markdownToFormattedText.test.ts
58 changes: 58 additions & 0 deletions src/markdownToFormattedText.ts
4,318 changes: 4,318 additions & 0 deletions src/mcDataTypes.ts
165 changes: 165 additions & 0 deletions src/mcTypes.ts
182 changes: 182 additions & 0 deletions src/microsoftAuthflow.ts
107 changes: 107 additions & 0 deletions src/mineflayer/cameraShake.ts
88 changes: 88 additions & 0 deletions src/mineflayer/items.ts
68 changes: 68 additions & 0 deletions src/mineflayer/java-tester/commands.ts
6 changes: 6 additions & 0 deletions src/mineflayer/java-tester/index.ts
23 changes: 23 additions & 0 deletions src/mineflayer/maps.ts
49 changes: 49 additions & 0 deletions src/mineflayer/mc-protocol.ts
105 changes: 105 additions & 0 deletions src/mineflayer/minecraft-protocol-extra.ts
200 changes: 200 additions & 0 deletions src/mineflayer/playerState.ts
204 changes: 204 additions & 0 deletions src/mineflayer/plugins/mouse.ts
50 changes: 50 additions & 0 deletions src/mineflayer/plugins/packetsPatcher.ts
107 changes: 107 additions & 0 deletions src/mineflayer/plugins/packetsRecording.ts
42 changes: 42 additions & 0 deletions src/mineflayer/plugins/ping.ts
6 changes: 6 additions & 0 deletions src/mineflayer/userError.ts
54 changes: 54 additions & 0 deletions src/mineflayer/websocket-core.ts
20 changes: 20 additions & 0 deletions src/mobileShim.ts
269 changes: 269 additions & 0 deletions src/optimizeJson.ts
557 changes: 557 additions & 0 deletions src/optionsGuiScheme.tsx
234 changes: 234 additions & 0 deletions src/optionsStorage.ts
70 changes: 70 additions & 0 deletions src/packetsReplay/packetsReplayLegacy.ts
332 changes: 332 additions & 0 deletions src/packetsReplay/replayPackets.ts
131 changes: 131 additions & 0 deletions src/panorama.ts
54 changes: 54 additions & 0 deletions src/parseServerAddress.ts
1,741 changes: 1,741 additions & 0 deletions src/preflatMap.json
275 changes: 275 additions & 0 deletions src/react/AddServerOrConnect.tsx
81 changes: 81 additions & 0 deletions src/react/AppStatus.tsx
200 changes: 200 additions & 0 deletions src/react/AppStatusProvider.tsx
31 changes: 31 additions & 0 deletions src/react/ArmorBar.css
18 changes: 18 additions & 0 deletions src/react/ArmorBar.stories.tsx
48 changes: 48 additions & 0 deletions src/react/ArmorBar.tsx
26 changes: 26 additions & 0 deletions src/react/BarsCommon.tsx
24 changes: 24 additions & 0 deletions src/react/BedTime.tsx
605 changes: 605 additions & 0 deletions src/react/Book.module.css
38 changes: 38 additions & 0 deletions src/react/Book.module.css.d.ts
25 changes: 25 additions & 0 deletions src/react/Book.stories.tsx
323 changes: 323 additions & 0 deletions src/react/Book.tsx
110 changes: 110 additions & 0 deletions src/react/BookProvider.tsx
44 changes: 44 additions & 0 deletions src/react/BossBarOverlay.css
34 changes: 34 additions & 0 deletions src/react/BossBarOverlay.stories.tsx
58 changes: 58 additions & 0 deletions src/react/BossBarOverlay.tsx
45 changes: 45 additions & 0 deletions src/react/BossBarOverlayProvider.tsx
32 changes: 32 additions & 0 deletions src/react/BreathBar.css
16 changes: 16 additions & 0 deletions src/react/BreathBar.stories.tsx
52 changes: 52 additions & 0 deletions src/react/BreathBar.tsx
18 changes: 18 additions & 0 deletions src/react/Button.stories.tsx
58 changes: 58 additions & 0 deletions src/react/Button.tsx
11 changes: 11 additions & 0 deletions src/react/ButtonAppProvider.tsx
20 changes: 20 additions & 0 deletions src/react/ButtonWithTooltip.stories.tsx
74 changes: 74 additions & 0 deletions src/react/ButtonWithTooltip.tsx
202 changes: 202 additions & 0 deletions src/react/Chat.css
162 changes: 162 additions & 0 deletions src/react/Chat.stories.tsx
344 changes: 344 additions & 0 deletions src/react/Chat.tsx
99 changes: 99 additions & 0 deletions src/react/ChatProvider.tsx
52 changes: 52 additions & 0 deletions src/react/ConceptCommandsGui.stories.tsx
99 changes: 99 additions & 0 deletions src/react/CreateWorld.tsx
70 changes: 70 additions & 0 deletions src/react/CreateWorldProvider.tsx
26 changes: 26 additions & 0 deletions src/react/Crosshair.css
72 changes: 72 additions & 0 deletions src/react/Crosshair.tsx
24 changes: 24 additions & 0 deletions src/react/DeathScreen.stories.tsx
35 changes: 35 additions & 0 deletions src/react/DeathScreen.tsx
61 changes: 61 additions & 0 deletions src/react/DeathScreenProvider.tsx
63 changes: 63 additions & 0 deletions src/react/DebugEdges.tsx
46 changes: 46 additions & 0 deletions src/react/DebugOverlay.module.css
11 changes: 11 additions & 0 deletions src/react/DebugOverlay.module.css.d.ts
244 changes: 244 additions & 0 deletions src/react/DebugOverlay.tsx
76 changes: 76 additions & 0 deletions src/react/DiscordButton.tsx
10 changes: 10 additions & 0 deletions src/react/DiveTransition.module.css
8 changes: 8 additions & 0 deletions src/react/DiveTransition.module.css.d.ts
18 changes: 18 additions & 0 deletions src/react/DiveTransition.stories.tsx
55 changes: 55 additions & 0 deletions src/react/DiveTransition.tsx
45 changes: 45 additions & 0 deletions src/react/EnterFullscreenButton.tsx
69 changes: 69 additions & 0 deletions src/react/FoodBar.css
19 changes: 19 additions & 0 deletions src/react/FoodBar.stories.tsx
77 changes: 77 additions & 0 deletions src/react/FoodBar.tsx
10 changes: 10 additions & 0 deletions src/react/FullScreenWidget.tsx
13 changes: 13 additions & 0 deletions src/react/Fullmap.css
478 changes: 478 additions & 0 deletions src/react/Fullmap.tsx
303 changes: 303 additions & 0 deletions src/react/GameInteractionOverlay.tsx
11 changes: 11 additions & 0 deletions src/react/GamepadUiCursor.module.css
7 changes: 7 additions & 0 deletions src/react/GamepadUiCursor.module.css.d.ts
40 changes: 40 additions & 0 deletions src/react/GamepadUiCursor.tsx
34 changes: 34 additions & 0 deletions src/react/GlobalSearchInput.tsx
106 changes: 106 additions & 0 deletions src/react/GoogleButton.css
24 changes: 24 additions & 0 deletions src/react/GoogleButton.tsx
79 changes: 79 additions & 0 deletions src/react/HealthBar.css
21 changes: 21 additions & 0 deletions src/react/HealthBar.stories.tsx
106 changes: 106 additions & 0 deletions src/react/HealthBar.tsx
45 changes: 45 additions & 0 deletions src/react/HeldMapUi.tsx
236 changes: 236 additions & 0 deletions src/react/HotbarRenderApp.tsx
124 changes: 124 additions & 0 deletions src/react/HudBarsProvider.tsx
35 changes: 35 additions & 0 deletions src/react/IndicatorEffects.css
33 changes: 33 additions & 0 deletions src/react/IndicatorEffects.stories.tsx
127 changes: 127 additions & 0 deletions src/react/IndicatorEffects.tsx
122 changes: 122 additions & 0 deletions src/react/IndicatorEffectsProvider.tsx
53 changes: 53 additions & 0 deletions src/react/Input.tsx
39 changes: 39 additions & 0 deletions src/react/Keybinding.tsx
170 changes: 170 additions & 0 deletions src/react/KeybindingsCustom.tsx
89 changes: 89 additions & 0 deletions src/react/KeybindingsScreen.module.css
27 changes: 27 additions & 0 deletions src/react/KeybindingsScreen.module.css.d.ts
367 changes: 367 additions & 0 deletions src/react/KeybindingsScreen.tsx
22 changes: 22 additions & 0 deletions src/react/KeybindingsScreenProvider.tsx
12 changes: 12 additions & 0 deletions src/react/LoadingChunks.css
43 changes: 43 additions & 0 deletions src/react/LoadingChunks.stories.tsx
60 changes: 60 additions & 0 deletions src/react/LoadingChunks.tsx
194 changes: 194 additions & 0 deletions src/react/MainMenu.tsx
169 changes: 169 additions & 0 deletions src/react/MainMenuRenderApp.tsx
5 changes: 5 additions & 0 deletions src/react/MessageFormatted.css
148 changes: 148 additions & 0 deletions src/react/MessageFormatted.tsx
18 changes: 18 additions & 0 deletions src/react/MessageFormattedString.stories.tsx
35 changes: 35 additions & 0 deletions src/react/MessageFormattedString.tsx
108 changes: 108 additions & 0 deletions src/react/MineflayerPluginConsole.css
109 changes: 109 additions & 0 deletions src/react/MineflayerPluginConsole.tsx
120 changes: 120 additions & 0 deletions src/react/MineflayerPluginHud.tsx
112 changes: 112 additions & 0 deletions src/react/Minimap.tsx
332 changes: 332 additions & 0 deletions src/react/MinimapDrawer.ts
562 changes: 562 additions & 0 deletions src/react/MinimapProvider.tsx
37 changes: 37 additions & 0 deletions src/react/MobileTopButtons.module.css
14 changes: 14 additions & 0 deletions src/react/MobileTopButtons.module.css.d.ts
87 changes: 87 additions & 0 deletions src/react/MobileTopButtons.tsx
69 changes: 69 additions & 0 deletions src/react/ModuleSignsViewer.tsx
50 changes: 50 additions & 0 deletions src/react/NetworkStatus.module.css
14 changes: 14 additions & 0 deletions src/react/NetworkStatus.module.css.d.ts
102 changes: 102 additions & 0 deletions src/react/NetworkStatus.tsx
50 changes: 50 additions & 0 deletions src/react/NoModalFoundProvider.tsx
78 changes: 78 additions & 0 deletions src/react/Notification.tsx
79 changes: 79 additions & 0 deletions src/react/NotificationProvider.tsx
39 changes: 39 additions & 0 deletions src/react/OptionsGroup.tsx
211 changes: 211 additions & 0 deletions src/react/OptionsItems.tsx
14 changes: 14 additions & 0 deletions src/react/OptionsRenderApp.tsx
55 changes: 55 additions & 0 deletions src/react/PacketsReplayProvider.tsx
27 changes: 27 additions & 0 deletions src/react/PauseScreen.module.css
10 changes: 10 additions & 0 deletions src/react/PauseScreen.module.css.d.ts
317 changes: 317 additions & 0 deletions src/react/PauseScreen.tsx
25 changes: 25 additions & 0 deletions src/react/PixelartIcon.tsx
81 changes: 81 additions & 0 deletions src/react/PlayerListOverlay.css
26 changes: 26 additions & 0 deletions src/react/PlayerListOverlay.stories.tsx
42 changes: 42 additions & 0 deletions src/react/PlayerListOverlay.tsx
94 changes: 94 additions & 0 deletions src/react/PlayerListOverlayProvider.tsx
106 changes: 106 additions & 0 deletions src/react/ReplayPanel.stories.tsx
229 changes: 229 additions & 0 deletions src/react/ReplayPanel.tsx
37 changes: 37 additions & 0 deletions src/react/Scoreboard.css
27 changes: 27 additions & 0 deletions src/react/Scoreboard.stories.tsx
38 changes: 38 additions & 0 deletions src/react/Scoreboard.tsx
42 changes: 42 additions & 0 deletions src/react/ScoreboardProvider.tsx
21 changes: 21 additions & 0 deletions src/react/Screen.stories.tsx
23 changes: 23 additions & 0 deletions src/react/Screen.tsx
5 changes: 5 additions & 0 deletions src/react/Select.css
22 changes: 22 additions & 0 deletions src/react/Select.stories.tsx
108 changes: 108 additions & 0 deletions src/react/Select.tsx
48 changes: 48 additions & 0 deletions src/react/SelectGameVersion.tsx
75 changes: 75 additions & 0 deletions src/react/SelectOption.tsx
57 changes: 57 additions & 0 deletions src/react/ServersList.stories.tsx
165 changes: 165 additions & 0 deletions src/react/ServersList.tsx
430 changes: 430 additions & 0 deletions src/react/ServersListProvider.tsx
35 changes: 35 additions & 0 deletions src/react/SharedHudVars.tsx
115 changes: 115 additions & 0 deletions src/react/SignEditor.css
27 changes: 27 additions & 0 deletions src/react/SignEditor.stories.tsx
86 changes: 86 additions & 0 deletions src/react/SignEditor.tsx
79 changes: 79 additions & 0 deletions src/react/SignEditorProvider.tsx
17 changes: 17 additions & 0 deletions src/react/SignInMessage.stories.tsx
125 changes: 125 additions & 0 deletions src/react/SignInMessage.tsx
34 changes: 34 additions & 0 deletions src/react/SignInMessageProvider.tsx
33 changes: 33 additions & 0 deletions src/react/Singleplayer.stories.tsx
271 changes: 271 additions & 0 deletions src/react/Singleplayer.tsx
359 changes: 359 additions & 0 deletions src/react/SingleplayerProvider.tsx
30 changes: 30 additions & 0 deletions src/react/Slider.stories.tsx
86 changes: 86 additions & 0 deletions src/react/Slider.tsx
62 changes: 62 additions & 0 deletions src/react/SoundMuffler.tsx
23 changes: 23 additions & 0 deletions src/react/Tabs.stories.tsx
57 changes: 57 additions & 0 deletions src/react/Tabs.tsx
42 changes: 42 additions & 0 deletions src/react/Title.css
31 changes: 31 additions & 0 deletions src/react/Title.stories.tsx
115 changes: 115 additions & 0 deletions src/react/Title.tsx
157 changes: 157 additions & 0 deletions src/react/TitleProvider.tsx
271 changes: 271 additions & 0 deletions src/react/TouchAreasControls.tsx
22 changes: 22 additions & 0 deletions src/react/TouchAreasControlsProvider.tsx
78 changes: 78 additions & 0 deletions src/react/TouchControls.tsx
19 changes: 19 additions & 0 deletions src/react/TouchInteractionHint.module.css
10 changes: 10 additions & 0 deletions src/react/TouchInteractionHint.module.css.d.ts
46 changes: 46 additions & 0 deletions src/react/TouchInteractionHint.tsx
30 changes: 30 additions & 0 deletions src/react/UIProvider.tsx
33 changes: 33 additions & 0 deletions src/react/XPBar.module.css
12 changes: 12 additions & 0 deletions src/react/XPBar.module.css.d.ts
28 changes: 28 additions & 0 deletions src/react/XPBar.stories.tsx
15 changes: 15 additions & 0 deletions src/react/XPBar.tsx
26 changes: 26 additions & 0 deletions src/react/XPBarProvider.tsx
10 changes: 10 additions & 0 deletions src/react/appStatus.module.css
9 changes: 9 additions & 0 deletions src/react/appStatus.module.css.d.ts
52 changes: 52 additions & 0 deletions src/react/armorValues.ts
Binary file added src/react/book_icons/book-half.webp
Binary file not shown.
Binary file added src/react/book_icons/book.webp
Binary file not shown.
Binary file added src/react/book_icons/next-click.webp
Binary file not shown.
Binary file added src/react/book_icons/next.webp
Binary file not shown.
Binary file added src/react/book_icons/notebook.webp
Binary file not shown.
Binary file added src/react/book_icons/prev-click.webp
Binary file not shown.
Binary file added src/react/book_icons/prev.webp
Binary file not shown.
Binary file added src/react/book_icons/title.webp
Binary file not shown.
76 changes: 76 additions & 0 deletions src/react/button.module.css
8 changes: 8 additions & 0 deletions src/react/button.module.css.d.ts
162 changes: 162 additions & 0 deletions src/react/components/replay/FilterInput.tsx
148 changes: 148 additions & 0 deletions src/react/components/replay/PacketList.tsx
36 changes: 36 additions & 0 deletions src/react/components/replay/ProgressBar.tsx
11 changes: 11 additions & 0 deletions src/react/components/replay/constants.ts
10 changes: 10 additions & 0 deletions src/react/createWorld.module.css
8 changes: 8 additions & 0 deletions src/react/createWorld.module.css.d.ts
33 changes: 33 additions & 0 deletions src/react/deathScreen.css
76 changes: 76 additions & 0 deletions src/react/effectsImages.ts
66 changes: 66 additions & 0 deletions src/react/globals.d.ts
62 changes: 62 additions & 0 deletions src/react/hooks/useScrollBehavior.ts
40 changes: 40 additions & 0 deletions src/react/input.module.css
8 changes: 8 additions & 0 deletions src/react/input.module.css.d.ts
133 changes: 133 additions & 0 deletions src/react/mainMenu.module.css
26 changes: 26 additions & 0 deletions src/react/mainMenu.module.css.d.ts
1 change: 1 addition & 0 deletions src/react/npmReactEntrypoint.ts
9 changes: 9 additions & 0 deletions src/react/options.module.css
55 changes: 55 additions & 0 deletions src/react/packetsFilter.ts
14 changes: 14 additions & 0 deletions src/react/parseKeybindingName.test.ts
17 changes: 17 additions & 0 deletions src/react/parseKeybindingName.ts
974 changes: 974 additions & 0 deletions src/react/pixelartIcons.generated.ts
77 changes: 77 additions & 0 deletions src/react/prosemirror-markdown.ts
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/react/select.module.css
8 changes: 8 additions & 0 deletions src/react/select.module.css.d.ts
115 changes: 115 additions & 0 deletions src/react/serversStorage.ts
56 changes: 56 additions & 0 deletions src/react/simpleHooks.ts
5 changes: 5 additions & 0 deletions src/react/simpleUtils.ts
72 changes: 72 additions & 0 deletions src/react/singleplayer.module.css
29 changes: 29 additions & 0 deletions src/react/singleplayer.module.css.d.ts
115 changes: 115 additions & 0 deletions src/react/slider.module.css
13 changes: 13 additions & 0 deletions src/react/slider.module.css.d.ts
59 changes: 59 additions & 0 deletions src/react/state/packetsReplayState.ts
13 changes: 13 additions & 0 deletions src/react/storageProvider.ts
88 changes: 88 additions & 0 deletions src/react/uiMotion.ts
71 changes: 71 additions & 0 deletions src/react/useLongPress.ts
26 changes: 26 additions & 0 deletions src/react/utils.ts
39 changes: 39 additions & 0 deletions src/react/utilsApp.ts
3 changes: 3 additions & 0 deletions src/react/widgets.ts
257 changes: 257 additions & 0 deletions src/reactUi.tsx
96 changes: 96 additions & 0 deletions src/rendererUtils.ts
595 changes: 595 additions & 0 deletions src/resourcePack.ts
48 changes: 48 additions & 0 deletions src/resourcePackItemDefinitions.ts
24 changes: 24 additions & 0 deletions src/resourcesManager.ts
61 changes: 61 additions & 0 deletions src/resourcesSource.ts
43 changes: 43 additions & 0 deletions src/scaleInterface.ts
62 changes: 62 additions & 0 deletions src/screens.css
28 changes: 28 additions & 0 deletions src/serviceWorker.ts
44 changes: 44 additions & 0 deletions src/shims/crypto.js
25 changes: 22 additions & 3 deletions lib/dns.js → src/shims/dns.js
1 change: 1 addition & 0 deletions src/shims/empty.ts
3 changes: 3 additions & 0 deletions src/shims/fs.js
92 changes: 92 additions & 0 deletions src/shims/minecraftData.ts
10 changes: 10 additions & 0 deletions src/shims/patchShims.ts
File renamed without changes.
7 changes: 7 additions & 0 deletions src/shims/prismarineAuthReplacement.ts
27 changes: 27 additions & 0 deletions src/shims/yggdrasilReplacement.ts
234 changes: 234 additions & 0 deletions src/sounds/botSoundSystem.ts
33 changes: 33 additions & 0 deletions src/sounds/musicSystem.ts
439 changes: 439 additions & 0 deletions src/sounds/soundsMap.ts
8 changes: 8 additions & 0 deletions src/sounds/testSounds.ts
3 changes: 3 additions & 0 deletions src/standaloneUtils.ts
210 changes: 210 additions & 0 deletions src/styles.css
10 changes: 10 additions & 0 deletions src/supportedVersions.mjs
5 changes: 5 additions & 0 deletions src/testCrasher.ts
129 changes: 129 additions & 0 deletions src/topRightStats.ts
106 changes: 106 additions & 0 deletions src/utils.test.ts
187 changes: 187 additions & 0 deletions src/utils.ts
29 changes: 29 additions & 0 deletions src/utilsTs.ts
291 changes: 291 additions & 0 deletions src/viewerConnector.ts
263 changes: 263 additions & 0 deletions src/vr.ts
103 changes: 103 additions & 0 deletions src/watchOptions.ts
36 changes: 36 additions & 0 deletions src/water.ts
4 changes: 4 additions & 0 deletions src/workerWorkaround.ts
120 changes: 0 additions & 120 deletions styles.css

This file was deleted.

7 changes: 0 additions & 7 deletions test/basic.test.js

This file was deleted.

34 changes: 34 additions & 0 deletions tsconfig.json
12 changes: 12 additions & 0 deletions tsconfig.npm.json
6 changes: 6 additions & 0 deletions vercel.json
15 changes: 15 additions & 0 deletions vitest.config.ts
86 changes: 0 additions & 86 deletions webpack.common.js

This file was deleted.

16 changes: 0 additions & 16 deletions webpack.dev.js

This file was deleted.

15 changes: 0 additions & 15 deletions webpack.prod.js

This file was deleted.