diff --git a/index.html b/index.html index 8182f783e..304ee0a6c 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,7 @@ + diff --git a/package.json b/package.json index 5337cebfb..bd18bc038 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "filesize": "^10.0.12", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.36", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.38", "fs-extra": "^11.1.1", "google-drive-browserfs": "github:zardoy/browserfs#google-drive", "jszip": "^3.10.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8075be60e..77810ab07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,8 +117,8 @@ importers: specifier: ^10.0.12 version: 10.0.12 flying-squid: - specifier: npm:@zardoy/flying-squid@^0.0.36 - version: '@zardoy/flying-squid@0.0.36(encoding@0.1.13)' + specifier: npm:@zardoy/flying-squid@^0.0.38 + version: '@zardoy/flying-squid@0.0.38(encoding@0.1.13)' fs-extra: specifier: ^11.1.1 version: 11.1.1 @@ -3390,8 +3390,8 @@ packages: resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - '@zardoy/flying-squid@0.0.36': - resolution: {integrity: sha512-d4clMPDpw723SDF5P2mMVNfbthUFLX6OT+vTCECAMshX8/M7CyMq/q9BfBQoeJcBL0H9nplhwtFbnx3Edb2fzA==} + '@zardoy/flying-squid@0.0.38': + resolution: {integrity: sha512-xz/ZuWmva3mlT1cigOudOMqa5iQF2sWsUUVeBNUoqfHscXoXl0TIOXnRScBeEGZjY2fD7meJ24nlcInewgNfZg==} engines: {node: '>=8'} hasBin: true @@ -13366,7 +13366,7 @@ snapshots: '@types/emscripten': 1.39.8 tslib: 1.14.1 - '@zardoy/flying-squid@0.0.36(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.38(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 change-case: 4.1.2 diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.ts similarity index 87% rename from prismarine-viewer/viewer/lib/entities.js rename to prismarine-viewer/viewer/lib/entities.ts index 59d1164a4..281a442e5 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.ts @@ -17,10 +17,9 @@ import { disposeObject } from './threeJsUtils' export const TWEEN_DURATION = 120 -/** - * @param {string} username - */ -function getUsernameTexture(username, { fontFamily = 'sans-serif' }) { +type PlayerObjectType = PlayerObject & { animation?: PlayerAnimation } + +function getUsernameTexture (username: string, { fontFamily = 'sans-serif' }: any) { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') if (!ctx) throw new Error('Could not get 2d context') @@ -64,7 +63,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 @@ -94,23 +93,41 @@ function getEntityMesh(entity, scene, options, overrides) { return cube } +export type SceneEntity = THREE.Object3D & { + playerObject?: PlayerObject & { + animation?: PlayerAnimation + } + username?: string + additionalCleanup?: () => void +} + export class Entities extends EventEmitter { - constructor(scene) { + entities = {} as Record + entitiesOptions: { + fontFamily?: string + } = {} + debugMode: string + onSkinUpdate: () => void + clock = new THREE.Clock() + rendering = true + itemsTexture: THREE.Texture | null = null + getItemUv: undefined | ((idOrName: number | string) => { + texture: THREE.Texture; + u: number; + v: number; + su?: number; + sv?: number; + size?: number; + }) + + constructor (public scene: THREE.Scene) { super() - /** @type {THREE.Scene} */ - this.scene = scene - this.entities = {} this.entitiesOptions = {} this.debugMode = 'none' this.onSkinUpdate = () => { } - this.clock = new THREE.Clock() - this.rendering = true - /** @type {THREE.Texture | null} */ - this.itemsTexture = null - this.getItemUv = undefined } - clear() { + clear () { for (const mesh of Object.values(this.entities)) { this.scene.remove(mesh) disposeObject(mesh) @@ -118,10 +135,10 @@ export class Entities extends EventEmitter { this.entities = {} } - setDebugMode(mode, /** @type {THREE.Object3D?} */entity = null) { + setDebugMode (mode: string, entity: THREE.Object3D | null = null) { this.debugMode = mode for (const mesh of entity ? [entity] : Object.values(this.entities)) { - const boxHelper = mesh.children.find(c => c.name === 'debug') + const boxHelper = mesh.children.find(c => c.name === 'debug')! boxHelper.visible = false if (this.debugMode === 'basic') { boxHelper.visible = true @@ -130,7 +147,7 @@ export class Entities extends EventEmitter { } } - setRendering(rendering, /** @type {THREE.Object3D?} */entity = null) { + setRendering (rendering: boolean, entity: THREE.Object3D | null = null) { this.rendering = rendering for (const ent of entity ? [entity] : Object.values(this.entities)) { if (rendering) { @@ -141,7 +158,7 @@ export class Entities extends EventEmitter { } } - render() { + render () { const dt = this.clock.getDelta() for (const entityId of Object.keys(this.entities)) { const playerObject = this.getPlayerObject(entityId) @@ -151,9 +168,8 @@ export class Entities extends EventEmitter { } } - getPlayerObject(entityId) { - /** @type {(PlayerObject & { animation?: PlayerAnimation }) | undefined} */ - const playerObject = this.entities[entityId]?.playerObject + getPlayerObject (entityId: string | number) { + const playerObject = this.entities[entityId]?.playerObject as PlayerObjectType | undefined return playerObject } @@ -161,7 +177,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: string | number, username: string | undefined, skinUrl: string | true, capeUrl: string | true | undefined = undefined) { let playerObject = this.getPlayerObject(entityId) if (!playerObject) return // const username = this.entities[entityId].username @@ -188,7 +204,6 @@ export class Entities extends EventEmitter { skinTexture.magFilter = THREE.NearestFilter skinTexture.minFilter = THREE.NearestFilter skinTexture.needsUpdate = true - //@ts-expect-error playerObject.skin.map = skinTexture playerObject.skin.modelType = inferModelType(skinTexture.image) @@ -244,14 +259,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, animation: 'walking' | 'running' | 'oneSwing' | 'idle') { const playerObject = this.getPlayerObject(entityPlayerId) if (!playerObject) return @@ -271,7 +286,7 @@ export class Entities extends EventEmitter { } - parseEntityLabel(jsonLike) { + parseEntityLabel (jsonLike) { if (!jsonLike) return try { const parsed = typeof jsonLike === 'string' ? mojangson.simplify(mojangson.parse(jsonLike)) : nbt.simplify(jsonLike) @@ -282,22 +297,24 @@ export class Entities extends EventEmitter { } } - getItemMesh(item) { + getItemMesh (item) { const textureUv = this.getItemUv?.(item.itemId ?? item.blockId) if (textureUv) { // todo use geometry buffer uv instead! const { u, v, size, su, sv, texture } = textureUv const itemsTexture = texture.clone() itemsTexture.flipY = true - itemsTexture.offset.set(u, 1 - v - (sv ?? size)) - itemsTexture.repeat.set(su ?? size, sv ?? size) + const sizeY = (sv ?? size)! + const sizeX = (su ?? size)! + itemsTexture.offset.set(u, 1 - v - sizeY) + itemsTexture.repeat.set(sizeX, sizeY) itemsTexture.needsUpdate = true itemsTexture.magFilter = THREE.NearestFilter itemsTexture.minFilter = THREE.NearestFilter const itemsTextureFlipped = itemsTexture.clone() itemsTextureFlipped.repeat.x *= -1 itemsTextureFlipped.needsUpdate = true - itemsTextureFlipped.offset.set(u + (su ?? size), 1 - v - (sv ?? size)) + itemsTextureFlipped.offset.set(u + (sizeX), 1 - v - sizeY) const material = new THREE.MeshStandardMaterial({ map: itemsTexture, transparent: true, @@ -322,7 +339,7 @@ export class Entities extends EventEmitter { } } - update(/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) { + update (entity: import('prismarine-entity').Entity & { delete?; pos }, overrides) { let isPlayerModel = entity.name === 'player' if (entity.name === 'zombie' || entity.name === 'zombie_villager' || entity.name === 'husk') { isPlayerModel = true @@ -332,21 +349,20 @@ export class Entities extends EventEmitter { const group = new THREE.Group() let mesh if (entity.name === 'item') { - /** @type {any} */ - //@ts-expect-error - const item = entity.metadata?.find(m => typeof m === 'object' && m?.itemCount) + const item = entity.metadata?.find((m: any) => typeof m === 'object' && m?.itemCount) if (item) { const object = this.getItemMesh(item) if (object) { - object.scale.set(0.5, 0.5, 0.5) - object.position.set(0, 0.2, 0) + mesh = object.mesh + mesh.scale.set(0.5, 0.5, 0.5) + mesh.position.set(0, 0.2, 0) // set faces // mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5) // viewer.scene.add(mesh) const clock = new THREE.Clock() - object.onBeforeRender = () => { + mesh.onBeforeRender = () => { const delta = clock.getDelta() - object.rotation.y += delta + mesh.rotation.y += delta } //@ts-expect-error group.additionalCleanup = () => { @@ -359,8 +375,7 @@ export class Entities extends EventEmitter { } else if (isPlayerModel) { // CREATE NEW PLAYER ENTITY const wrapper = new THREE.Group() - /** @type {PlayerObject & { animation?: PlayerAnimation }} */ - const playerObject = new PlayerObject() + const playerObject = new PlayerObject() as PlayerObjectType playerObject.position.set(0, 16, 0) //@ts-expect-error @@ -470,9 +485,7 @@ export class Entities extends EventEmitter { } if (e?.playerObject && overrides?.rotation?.head) { - /** @type {PlayerObject} */ - // eslint-disable-next-line prefer-destructuring - const playerObject = e.playerObject + const playerObject = e.playerObject as PlayerObjectType const headRotationDiff = overrides.rotation.head.y ? overrides.rotation.head.y - entity.yaw : 0 playerObject.skin.head.rotation.y = -headRotationDiff playerObject.skin.head.rotation.x = overrides.rotation.head.x ? - overrides.rotation.head.x : 0 @@ -497,7 +510,7 @@ export class Entities extends EventEmitter { } } - handleDamageEvent(entityId, damageAmount) { + handleDamageEvent (entityId, damageAmount) { const entityMesh = this.entities[entityId]?.children.find(c => c.name === 'mesh') if (entityMesh) { entityMesh.traverse((child) => { diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index 88505c06a..8b9115e79 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -282,6 +282,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: const aos: number[] = [] const neighborPos = position.plus(new Vec3(...dir)) + // 10% const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15 for (const pos of corners) { let vertex = [ @@ -290,7 +291,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: (pos[2] ? maxz : minz) ] - if (!needTiles) { + if (!needTiles) { // 10% vertex = vecadd3(matmul3(localMatrix, vertex), localShift) vertex = vecadd3(matmul3(globalMatrix, vertex), globalShift) vertex = vertex.map(v => v / 16) @@ -411,7 +412,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { // todo this can be removed here signs: {}, // isFull: true, - highestBlocks: {}, + highestBlocks: {}, // todo migrate to map for 2% boost perf hadErrors: false } @@ -449,7 +450,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { } const biome = block.biome.name - if (world.preflat) { + if (world.preflat) { // 10% perf const patchProperties = preflatBlockCalculation(block, world, cursor) if (patchProperties) { block._originalProperties ??= block._properties @@ -505,6 +506,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { const model = modelVars[useVariant] ?? modelVars[0] if (!model) continue + // #region 10% let globalMatrix = null as any let globalShift = null as any for (const axis of ['x', 'y', 'z'] as const) { @@ -518,6 +520,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { globalShift = [8, 8, 8] globalShift = vecsub3(globalShift, matmul3(globalMatrix, globalShift)) } + // #endregion for (const element of model.elements ?? []) { const ao = model.ao ?? true @@ -527,6 +530,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { renderElement(world, pos, element, ao, attr, globalMatrix, globalShift, block, biome) }) } else { + // 60% renderElement(world, cursor, element, ao, attr, globalMatrix, globalShift, block, biome) } } diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index deec264e4..17abfad95 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -29,10 +29,6 @@ export class WorldRendererThree extends WorldRendererCommon { super(config) this.starField = new StarField(scene) this.holdingBlock = new HoldingBlock(this.scene) - this.onHandItemSwitch({ - name: 'furnace', - properties: {} - }) this.renderUpdateEmitter.on('textureDownloaded', () => { if (this.holdingBlock.toBeRenderedItem) { diff --git a/src/entities.ts b/src/entities.ts index b5daa2206..de97fb578 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -47,10 +47,10 @@ customEvents.on('gameLoaded', () => { window.debugEntityMetadata ??= {} window.debugEntityMetadata[e.username] = e // todo entity spawn timing issue, check perf - if (viewer.entities.entities[e.id]?.playerObject) { + const playerObject = viewer.entities.entities[e.id]?.playerObject + if (playerObject) { // todo throttle! bot.tracker.trackEntity(e) - const { playerObject } = viewer.entities.entities[e.id] playerObject.backEquipment = e.equipment.some((item) => item?.name === 'elytra') ? 'elytra' : 'cape' if (playerObject.cape.map === null) { playerObject.cape.visible = false diff --git a/src/index.ts b/src/index.ts index 39a110a38..da73abea3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -185,7 +185,7 @@ viewer.entities.getItemUv = (idOrName: number | string) => { u: 0, v: 0, size: 16 / viewer.world.material.map!.image.width, - texture: viewer.world.material.map + texture: viewer.world.material.map! } } } diff --git a/src/react/AppStatus.tsx b/src/react/AppStatus.tsx index ca83c29af..31f086415 100644 --- a/src/react/AppStatus.tsx +++ b/src/react/AppStatus.tsx @@ -2,8 +2,18 @@ import { useEffect, useState } from 'react' import styles from './appStatus.module.css' import Button from './Button' import Screen from './Screen' +import LoadingChunks from './LoadingChunks' -export default ({ status, isError, hideDots = false, lastStatus = '', backAction = undefined as undefined | (() => void), description = '', actionsSlot = null as React.ReactNode | null }) => { +export default ({ + status, + isError, + hideDots = false, + lastStatus = '', + backAction = undefined as undefined | (() => void), + description = '', + actionsSlot = null as React.ReactNode | null, + children +}) => { const [loadingDots, setLoadingDots] = useState('') useEffect(() => { @@ -52,6 +62,7 @@ export default ({ status, isError, hideDots = false, lastStatus = '', backAction