need to release (#135)

This commit is contained in:
Vitaly 2024-06-02 01:19:50 +03:00 committed by GitHub
commit 0c032cdc57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 452 additions and 208 deletions

View file

@ -1,15 +1,5 @@
/// <reference types="cypress" />
import type { AppOptions } from '../../src/optionsStorage'
const cleanVisit = (url?) => {
cy.clearLocalStorage()
visit(url)
}
const visit = (url = '/') => {
window.localStorage.cypress = 'true'
cy.visit(url)
}
import { setOptions, cleanVisit, visit } from './shared'
// todo use ssl
@ -31,14 +21,8 @@ const testWorldLoad = () => {
})
}
const setOptions = (options: Partial<AppOptions>) => {
cy.window().then(win => {
Object.assign(win['options'], options)
})
}
it('Loads & renders singleplayer', () => {
visit('/?singleplayer=1')
cleanVisit('/?singleplayer=1')
setOptions({
localServerOptions: {
generation: {
@ -69,10 +53,3 @@ it('Loads & renders zip world', () => {
cy.get('input[type="file"]').selectFile('cypress/superflat.zip', { force: true })
testWorldLoad()
})
it.skip('Performance test', () => {
// select that world
// from -2 85 24
// await bot.loadPlugin(pathfinder.pathfinder)
// bot.pathfinder.goto(new pathfinder.goals.GoalXZ(28, -28))
})

15
cypress/e2e/shared.ts Normal file
View file

@ -0,0 +1,15 @@
import { AppOptions } from '../../src/optionsStorage'
export const cleanVisit = (url?) => {
cy.clearLocalStorage()
visit(url)
}
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)
})
}

View file

@ -94,9 +94,7 @@
</head>
<body>
<div id="react-root"></div>
<div id="ui-root">
<pmui-playscreen id="play-screen" style="display: none;"></pmui-playscreen>
</div>
<div id="ui-root"></div>
<!-- inject script -->
</body>
</html>

View file

@ -118,7 +118,7 @@
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"constants-browserify": "^1.0.0",
"contro-max": "^0.1.7",
"contro-max": "^0.1.8",
"crypto-browserify": "^3.12.0",
"cypress": "^10.11.0",
"cypress-esbuild-preprocessor": "^1.0.2",

72
pnpm-lock.yaml generated
View file

@ -268,8 +268,8 @@ importers:
specifier: ^1.0.0
version: 1.0.0
contro-max:
specifier: ^0.1.7
version: 0.1.7(typescript@5.5.0-beta)
specifier: ^0.1.8
version: 0.1.8(typescript@5.5.0-beta)
crypto-browserify:
specifier: ^3.12.0
version: 3.12.0
@ -299,7 +299,7 @@ importers:
version: 1.0.0
minecraft-inventory-gui:
specifier: github:zardoy/minecraft-inventory-gui#next
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c(@types/react@18.2.20)(react@18.2.0)
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/200902aca941475e7feb610070e662b172a000b5(@types/react@18.2.20)(react@18.2.0)
mineflayer:
specifier: github:PrismarineJS/mineflayer
version: https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/5a544cf2547a6e0f1f17786962d77a33c661c02f(encoding@0.1.13)
@ -3808,8 +3808,8 @@ packages:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
contro-max@0.1.7:
resolution: {integrity: sha512-HIYF1Dl50tUyTKaDsX+mPMDv2OjleNMVedYuBTX0n1wKNm9WxjWu2w74ATjz/8fHVL9GgmziIxAlFStd2je6kg==}
contro-max@0.1.8:
resolution: {integrity: sha512-5SoeudO8Zzfj/gbFTDrMRFJny02+MY1lBtb2NyCNiBLtHAfvhWZxZs/Z3yJvKL2rY/qKUZs9gTQOIDygBcBrdw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
convert-source-map@1.9.0:
@ -6035,14 +6035,19 @@ packages:
minecraft-folder-path@1.2.0:
resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==}
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c:
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c}
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/200902aca941475e7feb610070e662b172a000b5:
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/200902aca941475e7feb610070e662b172a000b5}
version: 1.0.1
minecraft-protocol@1.47.0:
resolution: {integrity: sha512-IHL8faXLLIWv1O+2v2NgyKlooilu/OiSL9orI8Kqed/rZvVOrFPzs2PwMAYjpQX9gxLPhiSU19KqZ8CjfNuqhg==}
engines: {node: '>=14'}
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc:
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc}
version: 1.47.0
engines: {node: '>=14'}
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7:
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7}
version: 1.47.0
@ -6717,6 +6722,11 @@ packages:
prismarine-chat@1.9.1:
resolution: {integrity: sha512-x7WWa5MNhiLZSO6tw+YyKpzquFZ+DNISVgiV6K3SU0GsishMXe+nto02WhF/4AuFerKdugm9u1d/r4C4zSkJOg==}
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16:
resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16}
version: 1.35.0
engines: {node: '>=14'}
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/f32234a724a5c2482ffbaf85edc5e91c7ab9b38f:
resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/f32234a724a5c2482ffbaf85edc5e91c7ab9b38f}
version: 1.35.0
@ -11936,11 +11946,11 @@ snapshots:
flatmap: 0.0.3
long: 5.2.3
minecraft-data: 3.65.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13)
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13)
mkdirp: 2.1.6
node-gzip: 1.1.2
node-rsa: 1.1.1
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/f32234a724a5c2482ffbaf85edc5e91c7ab9b38f(minecraft-data@3.65.0)
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0)
prismarine-entity: 2.3.1
prismarine-item: 1.14.0
prismarine-nbt: 2.5.0
@ -12839,7 +12849,7 @@ snapshots:
content-type@1.0.5: {}
contro-max@0.1.7(typescript@5.5.0-beta):
contro-max@0.1.8(typescript@5.5.0-beta):
dependencies:
events: 3.3.0
lodash-es: 4.17.21
@ -13206,7 +13216,7 @@ snapshots:
diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/915fce8e27fe8eb45464d89b9563956afa4f7687:
dependencies:
minecraft-data: 3.65.0
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/f32234a724a5c2482ffbaf85edc5e91c7ab9b38f(minecraft-data@3.65.0)
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0)
random-seed: 0.3.0
vec3: 0.1.8
@ -15668,7 +15678,7 @@ snapshots:
minecraft-folder-path@1.2.0: {}
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c(@types/react@18.2.20)(react@18.2.0):
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/200902aca941475e7feb610070e662b172a000b5(@types/react@18.2.20)(react@18.2.0):
dependencies:
valtio: 1.11.2(@types/react@18.2.20)(react@18.2.0)
transitivePeerDependencies:
@ -15700,6 +15710,31 @@ snapshots:
- encoding
- supports-color
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13):
dependencies:
'@types/readable-stream': 4.0.12
aes-js: 3.1.2
buffer-equal: 1.0.1
debug: 4.3.4(supports-color@8.1.1)
endian-toggle: 0.0.0
lodash.get: 4.4.2
lodash.merge: 4.6.2
minecraft-data: 3.65.0
minecraft-folder-path: 1.2.0
node-fetch: 2.7.0(encoding@0.1.13)
node-rsa: 0.4.2
prismarine-auth: 2.4.2(encoding@0.1.13)
prismarine-chat: 1.10.1
prismarine-nbt: 2.5.0
prismarine-realms: 1.3.2(encoding@0.1.13)
protodef: 1.15.0
readable-stream: 4.5.2
uuid-1345: 1.0.2
yggdrasil: 1.7.0(encoding@0.1.13)
transitivePeerDependencies:
- encoding
- supports-color
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/ccab9fb39681f3ebe0d264e2a3f833aa3c5a1ac7(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13):
dependencies:
'@types/readable-stream': 4.0.12
@ -16520,6 +16555,19 @@ snapshots:
prismarine-nbt: 2.5.0
prismarine-registry: 1.7.0
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0):
dependencies:
prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0
prismarine-nbt: 2.5.0
prismarine-registry: 1.7.0
smart-buffer: 4.2.0
uint4: 0.1.2
vec3: 0.1.8
xxhash-wasm: 0.4.2
transitivePeerDependencies:
- minecraft-data
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/f32234a724a5c2482ffbaf85edc5e91c7ab9b38f(minecraft-data@3.65.0):
dependencies:
prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0)

View file

@ -1,6 +0,0 @@
module.exports = {
dispose3 (o) {
o.geometry?.dispose()
o.dispose?.()
}
}

View file

@ -2,7 +2,6 @@
import * as THREE from 'three'
import * as TWEEN from '@tweenjs/tween.js'
import * as Entity from './entity/EntityMesh'
import { dispose3 } from './dispose'
import nbt from 'prismarine-nbt'
import EventEmitter from 'events'
import { PlayerObject, PlayerAnimation } from 'skinview3d'
@ -14,10 +13,11 @@ import { NameTagObject } from 'skinview3d/libs/nametag'
import { flat, fromFormattedString } from '@xmcl/text-component'
import mojangson from 'mojangson'
import externalTexturesJson from './entity/externalTextures.json'
import { disposeObject } from './threeJsUtils'
export const TWEEN_DURATION = 50 // todo should be 100
function getUsernameTexture(username, { fontFamily = 'sans-serif' }) {
function getUsernameTexture (username, { fontFamily = 'sans-serif' }) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Could not get 2d context')
@ -61,7 +61,7 @@ const addNametag = (entity, options, mesh) => {
// todo cleanup
const nametags = {}
function getEntityMesh(entity, scene, options, overrides) {
function getEntityMesh (entity, scene, options, overrides) {
if (entity.name) {
try {
// https://github.com/PrismarineJS/prismarine-viewer/pull/410
@ -105,15 +105,15 @@ export class Entities extends EventEmitter {
this.getItemUv = undefined
}
clear() {
clear () {
for (const mesh of Object.values(this.entities)) {
this.scene.remove(mesh)
dispose3(mesh)
disposeObject(mesh)
}
this.entities = {}
}
setDebugMode(mode, /** @type {THREE.Object3D?} */entity = null) {
setDebugMode (mode, /** @type {THREE.Object3D?} */entity = null) {
this.debugMode = mode
for (const mesh of entity ? [entity] : Object.values(this.entities)) {
const boxHelper = mesh.children.find(c => c.name === 'debug')
@ -125,14 +125,14 @@ export class Entities extends EventEmitter {
}
}
setVisible(visible, /** @type {THREE.Object3D?} */entity = null) {
setVisible (visible, /** @type {THREE.Object3D?} */entity = null) {
this.visible = visible
for (const mesh of entity ? [entity] : Object.values(this.entities)) {
mesh.visible = visible
}
}
render() {
render () {
const dt = this.clock.getDelta()
for (const entityId of Object.keys(this.entities)) {
const playerObject = this.getPlayerObject(entityId)
@ -142,7 +142,7 @@ export class Entities extends EventEmitter {
}
}
getPlayerObject(entityId) {
getPlayerObject (entityId) {
/** @type {(PlayerObject & { animation?: PlayerAnimation }) | undefined} */
const playerObject = this.entities[entityId]?.playerObject
return playerObject
@ -152,7 +152,7 @@ export class Entities extends EventEmitter {
defaultSteveTexture
// true means use default skin url
updatePlayerSkin(entityId, username, /** @type {string | true} */skinUrl, /** @type {string | true | undefined} */capeUrl = undefined) {
updatePlayerSkin (entityId, username, /** @type {string | true} */skinUrl, /** @type {string | true | undefined} */capeUrl = undefined) {
let playerObject = this.getPlayerObject(entityId)
if (!playerObject) return
// const username = this.entities[entityId].username
@ -235,14 +235,14 @@ export class Entities extends EventEmitter {
playerObject.cape.map = null
}
function isCanvasBlank(canvas) {
function isCanvasBlank (canvas) {
return !canvas.getContext('2d')
.getImageData(0, 0, canvas.width, canvas.height).data
.some(channel => channel !== 0)
}
}
playAnimation(entityPlayerId, /** @type {'walking' | 'running' | 'oneSwing' | 'idle'} */animation) {
playAnimation (entityPlayerId, /** @type {'walking' | 'running' | 'oneSwing' | 'idle'} */animation) {
const playerObject = this.getPlayerObject(entityPlayerId)
if (!playerObject) return
@ -262,14 +262,14 @@ export class Entities extends EventEmitter {
}
displaySimpleText(jsonLike) {
displaySimpleText (jsonLike) {
if (!jsonLike) return
const parsed = typeof jsonLike === 'string' ? mojangson.simplify(mojangson.parse(jsonLike)) : nbt.simplify(jsonLike)
const text = flat(parsed).map(x => x.text)
return text.join('')
}
update(/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) {
update (/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) {
let isPlayerModel = entity.name === 'player'
if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') {
isPlayerModel = true
@ -456,7 +456,7 @@ export class Entities extends EventEmitter {
if (e.additionalCleanup) e.additionalCleanup()
this.emit('remove', entity)
this.scene.remove(e)
dispose3(e)
disposeObject(e)
// todo dispose textures as well ?
delete this.entities[entity.id]
}

View file

@ -1,6 +1,5 @@
const THREE = require('three')
const { MeshLine, MeshLineMaterial } = require('three.meshline')
const { dispose3 } = require('./dispose')
function getMesh (primitive, camera) {
if (primitive.type === 'line') {
@ -48,7 +47,7 @@ function getMesh (primitive, camera) {
}
class Primitives {
constructor (scene, camera) {
constructor(scene, camera) {
this.scene = scene
this.camera = camera
this.primitives = {}
@ -57,7 +56,7 @@ class Primitives {
clear () {
for (const mesh of Object.values(this.primitives)) {
this.scene.remove(mesh)
dispose3(mesh)
disposeObject(mesh)
}
this.primitives = {}
}
@ -65,7 +64,7 @@ class Primitives {
update (primitive) {
if (this.primitives[primitive.id]) {
this.scene.remove(this.primitives[primitive.id])
dispose3(this.primitives[primitive.id])
disposeObject(this.primitives[primitive.id])
delete this.primitives[primitive.id]
}

View file

@ -0,0 +1,12 @@
import * as THREE from 'three';
export const disposeObject = (obj: THREE.Object3D) => {
// not cleaning texture there as it might be used by other objects, but would be good to also do that
if (obj instanceof THREE.Mesh) {
obj.geometry?.dispose?.();
obj.material?.dispose?.();
}
if (obj.children) {
obj.children.forEach(disposeObject);
}
}

View file

@ -1,13 +1,13 @@
import * as THREE from 'three'
import { Vec3 } from 'vec3'
import nbt from 'prismarine-nbt'
import { dispose3 } from './dispose'
import PrismarineChatLoader from 'prismarine-chat'
import { renderSign } from '../sign-renderer/'
import { chunkPos, sectionPos } from './simpleUtils'
import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon'
import * as tweenJs from '@tweenjs/tween.js'
import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass } from 'three-stdlib'
import { disposeObject } from './threeJsUtils'
export class WorldRendererThree extends WorldRendererCommon {
outputFormat = 'threeJs' as const
@ -62,7 +62,7 @@ export class WorldRendererThree extends WorldRendererCommon {
let object: THREE.Object3D = this.sectionObjects[data.key]
if (object) {
this.scene.remove(object)
dispose3(object)
disposeObject(object)
delete this.sectionObjects[data.key]
}
@ -263,7 +263,7 @@ export class WorldRendererThree extends WorldRendererCommon {
const mesh = this.sectionObjects[key]
if (mesh) {
this.scene.remove(mesh)
dispose3(mesh)
disposeObject(mesh)
}
delete this.sectionObjects[key]
}
@ -277,11 +277,23 @@ export class WorldRendererThree extends WorldRendererCommon {
class StarField {
points?: THREE.Points
private _enabled = true
get enabled () {
return this._enabled
}
set enabled (value) {
this._enabled = value
if (this.points) {
this.points.visible = value
}
}
constructor(private scene: THREE.Scene) {
}
addToScene () {
if (this.points || !this.enabled) return
const radius = 80
const depth = 50
const count = 7000
@ -315,7 +327,6 @@ class StarField {
material.blending = THREE.AdditiveBlending
material.depthTest = false
material.transparent = true
// material.unifo
// Create points and add them to the scene
this.points = new THREE.Points(geometry, material)
@ -332,6 +343,7 @@ class StarField {
if (this.points) {
this.points.geometry.dispose();
(this.points.material as THREE.Material).dispose();
this.scene.remove(this.points)
this.points = undefined;
}

View file

@ -19,9 +19,10 @@ const warnings = new Set<string>()
Promise.resolve().then(async () => {
generateItemsAtlases()
console.time('generateTextures')
for (const version of mcAssets.versions as typeof mcAssets['versions']) {
const versions = process.argv.includes('-l') ? [mcAssets.versions.at(-1)!] : mcAssets.versions
for (const version of versions as typeof mcAssets['versions']) {
// for debugging (e.g. when above is overridden)
if (!mcAssets.versions.includes(version)) {
if (!versions.includes(version)) {
throw new Error(`Version ${version} is not supported by minecraft-assets`)
}
if (versionToNumber(version) < versionToNumber('1.13')) {
@ -45,7 +46,7 @@ Promise.resolve().then(async () => {
fs.copySync(assets.directory, path.resolve(texturesPath, version), { overwrite: true })
}
fs.writeFileSync(path.join(publicPath, 'supportedVersions.json'), '[' + mcAssets.versions.map(v => `"${v}"`).toString() + ']')
fs.writeFileSync(path.join(publicPath, 'supportedVersions.json'), '[' + versions.map(v => `"${v}"`).toString() + ']')
warnings.forEach(x => console.warn(x))
console.timeEnd('generateTextures')
})

View file

@ -1,6 +1,5 @@
import Jimp from 'jimp'
import minecraftData from 'minecraft-data'
import prismarineRegistry from 'prismarine-registry'
import { McAssets } from './modelsBuilder'
import path from 'path'
import fs from 'fs'
@ -11,8 +10,6 @@ const twoTileTextures: string[] = []
let currentImage: Jimp
let currentBlockName: string
let currentMcAssets: McAssets
let isPreFlattening = false
const postFlatenningRegistry = prismarineRegistry('1.13')
const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url)))
type SidesType = {
@ -24,9 +21,9 @@ type SidesType = {
"down": string
}
const getBlockStates = (name: string, postFlatenningName = name) => {
const mcData = isPreFlattening ? postFlatenningRegistry : minecraftData(currentMcAssets.version)
return mcData.blocksByName[isPreFlattening ? postFlatenningName : name]?.states
const getBlockStates = (name: string) => {
const mcData = minecraftData(currentMcAssets.version)
return mcData.blocksByName[name]?.states
}
export const addBlockCustomSidesModel = (name: string, sides: SidesType) => {
@ -124,7 +121,7 @@ const handleShulkerBox = async (dataBase: string, match: RegExpExecArray) => {
}
const handleSign = async (dataBase: string, match: RegExpExecArray) => {
const states = getBlockStates(currentBlockName, currentBlockName === 'wall_sign' ? 'wall_sign' : 'sign')
const states = getBlockStates(currentBlockName)
if (!states) return
const [, signMaterial = ''] = match
@ -375,17 +372,11 @@ const handleChest = async (dataBase: string, match: RegExpExecArray) => {
if (modelName.endsWith('_left')) chestTextureName = `${chestTextureName}_left`
if (modelName.endsWith('_right')) chestTextureName = `${chestTextureName}_right`
// reading latest version since the texture wasn't changed, but in pre-flatenning need custom mapping for doubled_chest
const texture = path.join(currentMcAssets.directory, `../1.19.1/entity/chest/${chestTextureName}.png`)
currentImage = await Jimp.read(texture)
const model = structuredClone(chestModels[modelName])
// todo < 1.9
if (currentMcAssets.version === '1.8.8') {
// doesn't have definition of block yet
model.parent = undefined
}
model.textures.particle = particle
const newModelName = `${currentBlockName}_${modelName}`
for (const variant of blockStatesVariants) {
@ -438,7 +429,6 @@ export const tryHandleBlockEntity = async (dataBase, blockName) => {
export const prepareMoreGeneratedBlocks = async (mcAssets: McAssets) => {
const mcData = minecraftData(mcAssets.version)
isPreFlattening = !mcData.supportFeature('blockStateId')
const allTheBlocks = mcData.blocksArray.map(x => x.name)
currentMcAssets = mcAssets

View file

@ -40,7 +40,7 @@ export const startWatchingHmr = () => {
const mesherSharedPlugins = [
{
name: 'minecraft-data',
setup (build) {
setup(build) {
build.onLoad({
filter: /data[\/\\]pc[\/\\]common[\/\\]legacy.json$/,
}, async (args) => {
@ -59,7 +59,7 @@ const plugins = [
...mesherSharedPlugins,
{
name: 'strict-aliases',
setup (build) {
setup(build) {
build.onResolve({
filter: /^minecraft-protocol$/,
}, async ({ kind, resolveDir }) => {
@ -110,7 +110,7 @@ const plugins = [
},
{
name: 'data-assets',
setup (build) {
setup(build) {
build.onResolve({
filter: /.*/,
}, async ({ path, ...rest }) => {
@ -161,7 +161,7 @@ const plugins = [
},
{
name: 'prevent-incorrect-linking',
setup (build) {
setup(build) {
build.onResolve({
filter: /.+/,
}, async ({ resolveDir, path, importer, kind, pluginData }) => {
@ -184,7 +184,7 @@ const plugins = [
},
{
name: 'watch-notify',
setup (build) {
setup(build) {
let count = 0
let time
let prevHash
@ -234,7 +234,7 @@ const plugins = [
},
{
name: 'esbuild-readdir',
setup (build) {
setup(build) {
build.onResolve({
filter: /^esbuild-readdir:.+$/,
}, ({ resolveDir, path }) => {
@ -262,7 +262,7 @@ const plugins = [
},
{
name: 'esbuild-import-glob',
setup (build) {
setup(build) {
build.onResolve({
filter: /^esbuild-import-glob\(path:(.+),skipFiles:(.+)\)+$/,
}, ({ resolveDir, path }) => {
@ -292,7 +292,7 @@ const plugins = [
},
{
name: 'fix-dynamic-require',
setup (build) {
setup(build) {
build.onResolve({
filter: /1\.14\/chunk/,
}, async ({ resolveDir, path }) => {
@ -321,7 +321,7 @@ const plugins = [
},
{
name: 'react-displayname',
setup (build) {
setup(build) {
build.onLoad({
filter: /.tsx$/,
}, async ({ path }) => {

View file

@ -6,10 +6,10 @@ const fns = {
async getAlias () {
const aliasesRaw = process.env.ALIASES
if (!aliasesRaw) throw new Error('No aliases found')
const aliases = aliasesRaw.split('\n').map((x) => x.split('='))
const aliases = aliasesRaw.split('\n').map((x) => x.trim().split('='))
const githubActionsPull = process.env.PULL_URL?.split('/').at(-1)
if (!githubActionsPull) throw new Error(`Not a pull request, got ${process.env.GITHUB_REF}`)
const prNumber = githubActionsPull[1]
if (!githubActionsPull) throw new Error(`Not a pull request, got ${process.env.PULL_URL}`)
const prNumber = githubActionsPull
const alias = aliases.find((x) => x[0] === prNumber)
if (alias) {
// set github output
@ -18,7 +18,7 @@ const fns = {
}
}
function setOutput(key, value) {
function setOutput (key, value) {
// Temporary hack until core actions library catches up with github new recommendations
const output = process.env['GITHUB_OUTPUT']
fs.appendFileSync(output, `${key}=${value}${os.EOL}`)

View file

@ -6,7 +6,7 @@ import { proxy, subscribe } from 'valtio'
import { ControMax } from 'contro-max/build/controMax'
import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types'
import { stringStartsWith } from 'contro-max/build/stringUtils'
import { UserOverridesConfig } from 'contro-max/build/types/store'
import { UserOverrideCommand, UserOverridesConfig } from 'contro-max/build/types/store'
import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCurrentModal, miscUiState } from './globalState'
import { goFullscreen, pointerLock, reloadChunks } from './utils'
import { options } from './optionsStorage'
@ -19,6 +19,7 @@ import { showOptionsModal } from './react/SelectOption'
import widgets from './react/widgets'
import { getItemFromBlock } from './botUtils'
import { gamepadUiCursorState, moveGamepadCursorByPx } from './react/GamepadUiCursor'
import { updateBinds } from './react/KeybindingsScreenProvider'
export const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) as UserOverridesConfig
@ -86,7 +87,7 @@ export const contro = new ControMax({
window.controMax = contro
export type Command = CommandEventArgument<typeof contro['_commandsRaw']>['command']
// updateCustomBinds()
updateBinds(customKeymaps)
const updateDoPreventDefault = () => {
controlOptions.preventDefault = miscUiState.gameLoaded && !activeModalStack.length
@ -296,19 +297,18 @@ function cycleHotbarSlot (dir: 1 | -1) {
bot.setQuickBarSlot(newHotbarSlot)
}
// custom commands hamdler
const customCommandsHandler = (buttonData: { code?: string, button?: string, state: boolean }) => {
if (!buttonData.state || !isGameActive(true)) return
// custom commands handler
const customCommandsHandler = ({ command }) => {
const [section, name] = command.split('.')
if (!isGameActive(true) || section !== 'custom') return
const codeOrButton = buttonData.code ?? buttonData.button
const inputType = buttonData.code ? 'keys' : 'gamepad'
for (const value of Object.values(contro.userConfig!.custom ?? {})) {
if (value[inputType]?.includes(codeOrButton!)) {
customCommandsConfig[(value as CustomCommand).type].handler((value as CustomCommand).inputs)
}
if (contro.userConfig?.custom) {
customCommandsConfig[(contro.userConfig.custom[name] as CustomCommand).type].handler(
(contro.userConfig.custom[name] as CustomCommand).inputs
)
}
}
contro.on('pressedKeyOrButtonChanged', customCommandsHandler)
contro.on('trigger', customCommandsHandler)
contro.on('trigger', ({ command }) => {
const willContinue = !isGameActive(true)
@ -651,6 +651,24 @@ window.addEventListener('keydown', (e) => {
}
})
window.addEventListener('keydown', (e) => {
if (e.code !== 'F2' || e.repeat || !isGameActive(true)) return
e.preventDefault()
const canvas = document.getElementById('viewer-canvas') as HTMLCanvasElement
if (!canvas) return
const link = document.createElement('a')
link.href = canvas.toDataURL('image/png')
const date = new Date()
link.download = `screenshot ${date.toLocaleString().replaceAll('.', '-').replace(',', '')}.png`
link.click()
})
window.addEventListener('keydown', (e) => {
if (e.code !== 'F1' || e.repeat || !isGameActive(true)) return
e.preventDefault()
miscUiState.showUI = !miscUiState.showUI
})
// #region experimental debug things
window.addEventListener('keydown', (e) => {
if (e.code === 'F11') {

View file

@ -29,7 +29,7 @@ window.len = (obj) => Object.keys(obj).length
window.inspectPacket = (packetName, full = false) => {
const listener = (...args) => console.log('packet', packetName, full ? args : args[0])
const attach = () => {
bot?.on(packetName, listener)
bot?._client.on(packetName, listener)
}
attach()
customEvents.on('mineflayerBotCreated', attach)

View file

@ -14,6 +14,7 @@ const updateAutoJump = () => {
jumpOnAllEdges: options.autoParkour,
// strictBlockCollision: true,
})
if (autoJump === bot.autoJumper.enabled) return
if (autoJump) {
bot.autoJumper.enable()
} else {

View file

@ -138,6 +138,7 @@ export const miscUiState = proxy({
wanOpened: false,
/** wether game hud is shown (in playing state) */
gameLoaded: false,
showUI: true,
loadedServerIndex: '',
/** currently trying to load or loaded mc version, after all data is loaded */
loadedDataVersion: null as string | null,

View file

@ -108,6 +108,8 @@ let renderer: THREE.WebGLRenderer
try {
renderer = new THREE.WebGLRenderer({
powerPreference: options.gpuPreference,
preserveDrawingBuffer: true,
logarithmicDepthBuffer: true,
})
} catch (err) {
console.error(err)
@ -142,24 +144,35 @@ new THREE.TextureLoader().load(itemsPng, (texture) => {
viewer.entities.itemsTexture = texture
// todo unify
viewer.entities.getItemUv = (id) => {
const name = loadedData.items[id]?.name
const uv = itemsAtlases.latest.textures[name]
if (!uv) {
const variant = viewer.world.downloadedBlockStatesData[name]?.variants?.['']
if (!variant) return
const uvBlock = (Array.isArray(variant) ? variant[0] : variant).model?.elements?.[0]?.faces?.north.texture
if (!uvBlock) return
try {
const name = loadedData.items[id]?.name
const uv = itemsAtlases.latest.textures[name]
if (!uv) {
const variant = viewer.world.downloadedBlockStatesData[name]?.variants?.['']
if (!variant) return
const faces = (Array.isArray(variant) ? variant[0] : variant).model?.elements?.[0]?.faces
const uvBlock = faces?.north?.texture ?? faces?.up?.texture ?? faces?.down?.texture ?? faces?.west?.texture ?? faces?.east?.texture ?? faces?.south?.texture
if (!uvBlock) return
return {
...uvBlock,
size: Math.abs(uvBlock.su),
texture: viewer.world.material.map
}
}
return {
...uvBlock,
size: Math.abs(uvBlock.su),
...uv,
size: itemsAtlases.latest.size,
texture: viewer.entities.itemsTexture
}
} catch (err) {
reportError?.(err)
return {
u: 0,
v: 0,
size: 16 / viewer.world.material.map!.image.width,
texture: viewer.world.material.map
}
}
return {
...uv,
size: itemsAtlases.latest.size,
texture: viewer.entities.itemsTexture
}
}
})
viewer.entities.entitiesOptions = {

View file

@ -69,6 +69,7 @@ export const guiOptionsScheme: {
enableWarning: 'Enabling it will make chunks load ~4x slower',
disabledDuringGame: true
},
starfieldRendering: {}
},
],
main: [

View file

@ -30,24 +30,7 @@ const defaultOptions = {
touchButtonsSize: 40,
touchButtonsOpacity: 80,
touchButtonsPosition: 12,
touchControlsPositions: {
action: [
70,
85
],
sneak: [
90,
85
],
break: [
70,
65
],
jump: [
90,
65
],
} as Record<string, [number, number]>,
touchControlsPositions: getDefaultTouchControlsPositions(),
touchControlsType: 'classic' as 'classic' | 'joystick-buttons',
gpuPreference: 'default' as 'default' | 'high-performance' | 'low-power',
backgroundRendering: '20fps' as 'full' | '20fps' | '5fps',
@ -59,6 +42,7 @@ const defaultOptions = {
dayCycleAndLighting: true,
loadPlayerSkins: true,
lowMemoryMode: false,
starfieldRendering: true,
// antiAliasing: false,
showChunkBorders: false, // todo rename option
@ -68,11 +52,14 @@ const defaultOptions = {
excludeCommunicationDebugEvents: [],
preventDevReloadWhilePlaying: false,
numWorkers: 4,
localServerOptions: {} as any,
localServerOptions: {
gameMode: 1
} as any,
preferLoadReadonly: false,
disableLoadPrompts: false,
guestUsername: 'guest',
askGuestName: true,
errorReporting: true,
/** Actually might be useful */
showCursorBlockInSpectator: false,
renderEntities: true,
@ -91,6 +78,27 @@ const defaultOptions = {
wysiwygSignEditor: 'auto' as 'auto' | 'always' | 'never',
}
function getDefaultTouchControlsPositions () {
return {
action: [
70,
85
],
sneak: [
90,
85
],
break: [
70,
65
],
jump: [
90,
65
],
} as Record<string, [number, number]>
}
const qsOptionsRaw = new URLSearchParams(location.search).getAll('setting')
export const qsOptions = Object.fromEntries(qsOptionsRaw.map(o => {
const [key, value] = o.split(':')

View file

@ -74,7 +74,7 @@ export default () => {
if (items[0].match) items = items.map(i => i.match)
}
if (completeValue === '/') {
if (!items[0].startsWith('/')) {
if (!items[0]?.startsWith('/')) {
// normalize
items = items.map(item => `/${item}`)
}

View file

@ -8,17 +8,19 @@ import styles from './createWorld.module.css'
// const worldTypes = ['default', 'flat', 'largeBiomes', 'amplified', 'customized', 'buffet', 'debug_all_block_states']
const worldTypes = ['default', 'flat'/* , 'void' */]
const gameModes = ['survival', 'creative'/* , 'adventure', 'spectator' */]
export const creatingWorldState = proxy({
title: '',
type: worldTypes[0],
gameMode: gameModes[0],
version: ''
})
export default ({ cancelClick, createClick, customizeClick, versions, defaultVersion }) => {
const [quota, setQuota] = useState('')
const { title, type, version } = useSnapshot(creatingWorldState)
const { title, type, version, gameMode } = useSnapshot(creatingWorldState)
useEffect(() => {
creatingWorldState.version = defaultVersion
void navigator.storage?.estimate?.().then(({ quota, usage }) => {
@ -54,9 +56,15 @@ export default ({ cancelClick, createClick, customizeClick, versions, defaultVer
<Button onClick={() => {
const index = worldTypes.indexOf(type)
creatingWorldState.type = worldTypes[index === worldTypes.length - 1 ? 0 : index + 1]
}}>{type}</Button>
<Button onClick={() => customizeClick()} disabled>
}}>World Type: {type}</Button>
{/* <Button onClick={() => customizeClick()} disabled>
Customize
</Button> */}
<Button onClick={() => {
const index = gameModes.indexOf(gameMode)
creatingWorldState.gameMode = gameModes[index === gameModes.length - 1 ? 0 : index + 1]
}}>
Gamemode: {gameMode}
</Button>
</div>
<div className='muted' style={{ fontSize: 8 }}>Default and other world types are WIP</div>

View file

@ -3,8 +3,8 @@ import { hideCurrentModal, showModal } from '../globalState'
import defaultLocalServerOptions from '../defaultLocalServerOptions'
import { mkdirRecursive, uniqueFileNameFromWorldName } from '../browserfs'
import CreateWorld, { WorldCustomize, creatingWorldState } from './CreateWorld'
import { useIsModalActive } from './utilsApp'
import { getWorldsPath } from './SingleplayerProvider'
import { useIsModalActive } from './utilsApp'
export default () => {
const activeCreate = useIsModalActive('create-world')
@ -23,7 +23,7 @@ export default () => {
}}
createClick={async () => {
// create new world
const { title, type, version } = creatingWorldState
const { title, type, version, gameMode } = creatingWorldState
// todo display path in ui + disable if exist
const savePath = await uniqueFileNameFromWorldName(title, getWorldsPath())
await mkdirRecursive(savePath)
@ -51,7 +51,8 @@ export default () => {
levelName: title,
version,
generation,
'worldFolder': savePath
'worldFolder': savePath,
gameMode: gameMode === 'survival' ? 0 : 1,
},
}))
}}

View file

@ -1,12 +1,25 @@
.crosshair {
z-index: -1;
width: 16px;
height: 16px;
background: var(--gui-icons);
background-size: calc(256px * var(--crosshair-scale));
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
image-rendering: pixelated;
}
z-index: -1;
width: 16px;
height: 16px;
background: var(--gui-icons);
background-size: calc(256px * var(--crosshair-scale));
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
image-rendering: pixelated;
pointer-events: none;
}
.crosshair-indicator {
z-index: -1;
width: var(--crosshair-indicator-size);
height: 1.5px;
position: fixed;
top: calc(50% + 8px);
left: 50%;
transform: translate(-50%, -50%);
background: lightgray;
pointer-events: none;
}

View file

@ -1,8 +1,70 @@
import { useEffect, useState } from 'react'
import './Crosshair.css'
import { proxy, useSnapshot } from 'valtio'
import SharedHudVars from './SharedHudVars'
// todo move to mineflayer
export const itemBeingUsed = proxy({
name: null as string | null,
hand: 0 as 0 | 1
})
export default () => {
const { name: usingItem, hand } = useSnapshot(itemBeingUsed)
const [displayIndicator, setDisplayIndicator] = useState(false)
const [indicatorProgress, setIndicatorProgress] = useState(0)
const [alternativeIndicator, setAlternativeIndicator] = useState(false)
const boxMaxTimeMs = 1000
// todo add sword indicator
const indicatorSize = 20
useEffect(() => {
bot.on('heldItemChanged' as any, () => {
const displayBar = (item: import('prismarine-item').Item | null) => {
const itemName = item?.name
if (!itemName) return
return loadedData.foodsArray.map((food) => food.name).includes(itemName) || itemName === 'bow' || itemName === 'shield' || itemName === 'crossbow'
}
setDisplayIndicator(displayBar(bot.heldItem) || displayBar(bot.inventory.slots[45]) || false)
})
}, [])
useEffect(() => {
setAlternativeIndicator(usingItem === 'shield')
if (!usingItem) return
const startTime = Date.now()
let maxTime = 0
if (usingItem === 'bow' || usingItem === 'crossbow') {
maxTime = 1000
}
const isFood = loadedData.foodsArray.some((food) => food.name === usingItem)
if (isFood) {
maxTime = 32 * 50
}
if (!maxTime) return
const id = setInterval(() => {
const progress = (Date.now() - startTime) / boxMaxTimeMs
if (progress >= 1) {
clearInterval(id)
} else {
setIndicatorProgress(progress)
}
}, 1000 / 60)
return () => {
setIndicatorProgress(0)
clearInterval(id)
}
}, [usingItem])
return <SharedHudVars>
<div className='crosshair'/>
<div className='crosshair' />
{displayIndicator && <div className='crosshair-indicator' style={{
//@ts-expect-error
'--crosshair-indicator-size': `${indicatorSize}px`,
borderLeft: `solid ${indicatorSize * indicatorProgress}px white`,
backgroundColor: alternativeIndicator ? 'dodgerblue' : undefined,
}} />}
</SharedHudVars>
}

View file

@ -93,7 +93,7 @@ export default () => {
},
} as any)
const { canvasManager } = inv
inv.inventory.supportsOffhand = bot.supportFeature('doesntHaveOffHandSlot')
inv.inventory.supportsOffhand = !bot.supportFeature('doesntHaveOffHandSlot')
inv.pwindow.disablePicking = true
canvasManager.children[0].disableHighlight = true

View file

@ -1,5 +1,6 @@
import { useState, useEffect, useRef, createContext, useContext } from 'react'
import { UserOverridesConfig } from 'contro-max/build/types/store'
import { ModifierOnlyKeys } from 'contro-max/build/types/keyCodes'
import { contro as controEx } from '../controls'
import { hideModal } from '../globalState'
import triangle from './ps_icons/playstation_triangle_console_controller_gamepad_icon.svg'
@ -96,22 +97,35 @@ export default (
updateBindMap()
}, [userConfig])
const updateBinding = (data) => {
if (data.state === true || !awaitingInputType) return
const updateBinding = (data: any) => {
if ((!data.state && awaitingInputType) || !awaitingInputType) {
setAwaitingInputType(null)
return
}
if ('code' in data) {
if (data.state && [...contro.pressedKeys].includes(data.code)) return
if (data.code === 'Escape' || ['Mouse0', 'Mouse1', 'Mouse2'].includes(data.code)) {
setAwaitingInputType(null)
return
}
setBinding({ code: data.code, state: true }, groupName, actionName, buttonNum)
const pressedModifiers = [...contro.pressedKeys].filter(
key => /^(Meta|Control|Alt|Shift)?$/.test(key)
)
setBinding(
{ code: pressedModifiers.length ? `${pressedModifiers[0]}+${data.code}` : data.code, state: true },
groupName,
actionName,
buttonNum
)
}
if ('button' in data) {
contro.enabled = false
void Promise.resolve().then(() => { contro.enabled = true })
setBinding(data, groupName, actionName, buttonNum)
}
setAwaitingInputType(null)
}
const updateBindMap = () => {
@ -368,8 +382,12 @@ const parseActionName = (action: string) => {
const parseBindingName = (binding: string | undefined) => {
if (!binding) return ''
const cut = binding.replaceAll(/(Numpad|Digit|Key)/g, '')
const parts = cut.split(/(?=[A-Z\d])/)
return parts.reverse().join(' ')
const parts = cut.includes('+') ? cut.split('+') : [cut]
for (let i = 0; i < parts.length; i++) {
parts[i] = parts[i].split(/(?=[A-Z\d])/).reverse().join(' ')
}
return parts.join(' + ')
}
const buttonsMap = {

View file

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'
import { openURL } from 'prismarine-viewer/viewer/lib/simpleUtils'
import { haveDirectoryPicker } from '../utils'
import { activeModalStack } from '../globalState'
import styles from './mainMenu.module.css'
import Button from './Button'
import ButtonWithTooltip from './ButtonWithTooltip'
@ -17,12 +18,24 @@ interface Props {
mapsProvider?: string
}
const refreshApp = async () => {
const refreshApp = async (failedUpdate = false) => {
const registration = await navigator.serviceWorker.getRegistration()
await registration?.unregister()
window.justReloaded = true
sessionStorage.justReloaded = true
window.location.reload()
if (failedUpdate) {
await new Promise(resolve => {
setTimeout(resolve, 2000)
})
}
if (activeModalStack.length !== 0) return
if (failedUpdate) {
sessionStorage.justReloaded = false
// try to force bypass cache
location.search = '?update=true'
} else {
window.justReloaded = true
sessionStorage.justReloaded = true
window.location.reload()
}
}
const httpsRegex = /^https?:\/\//
@ -40,10 +53,10 @@ export default ({ connectToServerAction, mapsProvider, singleplayerAction, optio
const contents = await f.text()
const isLatest = contents === process.env.BUILD_VERSION
if (!isLatest && sessionStorage.justReloaded) {
// try to force bypass cache
location.search = '?update=true'
setVersionStatus('(force reloading, wait)')
void refreshApp(true)
return
}
sessionStorage.justReloaded = false
setVersionStatus(`(${isLatest ? 'latest' : 'new version available'})`)
setVersionTitle(`Loaded: ${process.env.BUILD_VERSION}. Remote: ${contents}`)
}, () => { })

View file

@ -186,6 +186,12 @@ const Inner = () => {
}
}, [serverEditScreen])
useDidUpdateEffect(() => {
if (!isEditScreenModal) {
setServerEditScreen(null)
}
}, [isEditScreenModal])
if (isEditScreenModal) {
return <AddServerOrConnect
defaults={{

View file

@ -23,6 +23,7 @@ export default () => {
const [openActionBar, setOpenActionBar] = useState(false)
useMemo(() => {
// todo move to mineflayer
bot._client.on('set_title_text', (packet) => {
setTitle(JSON.parse(packet.text))
setOpenTitle(true)

View file

@ -95,29 +95,33 @@ const InGameComponent = ({ children }) => {
}
const InGameUi = () => {
const { gameLoaded } = useSnapshot(miscUiState)
const { gameLoaded, showUI } = useSnapshot(miscUiState)
if (!gameLoaded) return
return <>
<RobustPortal to={document.querySelector('#ui-root')}>
{/* apply scaling */}
<DeathScreenProvider />
<DebugOverlay />
<MobileTopButtons />
<PlayerListOverlayProvider />
<ChatProvider />
<SoundMuffler />
<TitleProvider />
<ScoreboardProvider />
<IndicatorEffectsProvider />
<TouchAreasControlsProvider />
<Crosshair />
<div style={{ display: showUI ? 'block' : 'none' }}>
<DeathScreenProvider />
<DebugOverlay />
<MobileTopButtons />
<PlayerListOverlayProvider />
<ChatProvider />
<SoundMuffler />
<TitleProvider />
<ScoreboardProvider />
<IndicatorEffectsProvider />
<Crosshair />
<TouchAreasControlsProvider />
</div>
<PauseScreen />
<XPBarProvider />
<HudBarsProvider />
<HotbarRenderApp />
<BedTime />
<div style={{ display: showUI ? 'block' : 'none' }}>
<XPBarProvider />
<HudBarsProvider />
<BedTime />
</div>
{showUI && <HotbarRenderApp />}
</RobustPortal>
<PerComponentErrorBoundary>
<SignEditorProvider />
@ -125,7 +129,7 @@ const InGameUi = () => {
</PerComponentErrorBoundary>
<RobustPortal to={document.body}>
{/* because of z-index */}
<TouchControls />
{showUI && <TouchControls />}
<GlobalSearchInput />
</RobustPortal>
</>

View file

@ -57,4 +57,9 @@ export const watchOptionsAfterViewerInit = () => {
customEvents.on('gameLoaded', () => {
viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting
})
watchValue(options, o => {
if (!(viewer.world instanceof WorldRendererThree)) return
viewer.world.starField.enabled = o.starfieldRendering
})
}

4
src/workerWorkaround.ts Normal file
View file

@ -0,0 +1,4 @@
//@ts-nocheck
// eslint-disable-next-line no-global-assign
global = globalThis
globalThis.window = globalThis

View file

@ -17,6 +17,7 @@ import { LineMaterial, Wireframe, LineSegmentsGeometry } from 'three-stdlib'
import { hideCurrentModal, isGameActive, showModal } from './globalState'
import { assertDefined } from './utils'
import { options } from './optionsStorage'
import { itemBeingUsed } from './react/Crosshair'
function getViewDirection (pitch, yaw) {
const csPitch = Math.cos(pitch)
@ -133,6 +134,9 @@ class WorldInteraction {
}
this.lastDugBlock = null
})
bot.on('heldItemChanged' as any, () => {
itemBeingUsed.name = null
})
const upLineMaterial = () => {
const inCreative = bot.game.gameMode === 'creative'
@ -191,16 +195,20 @@ class WorldInteraction {
// Place / interact / activate
if (this.buttons[2] && this.lastBlockPlaced >= 4) {
const activate = bot.heldItem && ['egg', 'fishing_rod', 'firework_rocket',
'fire_charge', 'snowball', 'ender_pearl', 'experience_bottle', 'potion',
'glass_bottle', 'bucket', 'water_bucket', 'lava_bucket', 'milk_bucket',
'minecart', 'boat', 'tnt_minecart', 'chest_minecart', 'hopper_minecart',
'command_block_minecart', 'armor_stand', 'lead', 'name_tag',
//
'writable_book', 'written_book', 'compass', 'clock', 'filled_map', 'empty_map', 'map',
'shears', 'carrot_on_a_stick', 'warped_fungus_on_a_stick',
'spawn_egg', 'trident', 'crossbow', 'elytra', 'shield', 'turtle_helmet',
].includes(bot.heldItem.name)
const activatableItems = (itemName: string) => {
return ['egg', 'fishing_rod', 'firework_rocket',
'fire_charge', 'snowball', 'ender_pearl', 'experience_bottle', 'potion',
'glass_bottle', 'bucket', 'water_bucket', 'lava_bucket', 'milk_bucket',
'minecart', 'boat', 'tnt_minecart', 'chest_minecart', 'hopper_minecart',
'command_block_minecart', 'armor_stand', 'lead', 'name_tag',
//
'writable_book', 'written_book', 'compass', 'clock', 'filled_map', 'empty_map', 'map',
'shears', 'carrot_on_a_stick', 'warped_fungus_on_a_stick',
'spawn_egg', 'trident', 'crossbow', 'elytra', 'shield', 'turtle_helmet', 'bow', 'crossbow', 'bucket_of_cod',
...loadedData.foodsArray.map((f) => f.name),
].includes(itemName)
}
const activate = bot.heldItem && activatableItems(bot.heldItem.name)
let stop = false
if (!bot.controlState.sneak) {
if (cursorBlock?.name === 'bed' || cursorBlock?.name.endsWith('_bed')) {
@ -223,6 +231,7 @@ class WorldInteraction {
})
}
}
// todo placing with offhand
if (cursorBlock && !activate && !stop) {
const vecArray = [new Vec3(0, -1, 0), new Vec3(0, 1, 0), new Vec3(0, 0, -1), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(1, 0, 0)]
//@ts-expect-error
@ -242,10 +251,21 @@ class WorldInteraction {
}).catch(console.warn)
}
} else if (!stop) {
bot.activateItem() // todo offhand
const offhand = activate ? false : activatableItems(bot.inventory.slots[45]?.name ?? '')
bot.activateItem(offhand) // todo offhand
itemBeingUsed.name = (offhand ? bot.inventory.slots[45]?.name : bot.heldItem?.name) ?? null
itemBeingUsed.hand = offhand ? 1 : 0
}
this.lastBlockPlaced = 0
}
// stop using activated item (cancel)
if (itemBeingUsed.name && !this.buttons[2]) {
itemBeingUsed.name = null
// "only foods and bow can be deactivated" - not true, shields also can be deactivated and client always sends this
// if (bot.heldItem && (loadedData.foodsArray.map((f) => f.name).includes(bot.heldItem.name) || bot.heldItem.name === 'bow')) {
bot.deactivateItem()
// }
}
// Stop break
if ((!this.buttons[0] && this.lastButtons[0]) || cursorChanged) {

View file

@ -16,6 +16,7 @@
"useUnknownInCatchVariables": false,
"skipLibCheck": true,
"experimentalDecorators": true,
"strictBindCallApply": true,
// this the only options that allows smooth transition from js to ts (by not dropping types from js files)
// however might need to consider includeing *only needed libraries* instead of using this
"maxNodeModuleJsDepth": 1,