Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ For building the project yourself / contributing, see [Development, Debugging &

> **Note**: You can deploy it on your own server in less than a minute using a one-liner script from [Minecraft Everywhere repo](https://github.com/zardoy/minecraft-everywhere)


### Big Features

- Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely.
Expand Down Expand Up @@ -57,6 +56,7 @@ Howerver, it's known that these browsers have issues:

Server versions 1.8 - 1.21.4 are supported.
First class versions (most of the features are tested on these versions):

- 1.19.4
- 1.21.4

Expand Down Expand Up @@ -125,11 +125,11 @@ 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.
- If you type `debugToggle`, press enter in console - It will 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.
- `world` - Three.js world instance, basically does all the rendering (part of renderer backend).
- `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.
Expand All @@ -139,7 +139,7 @@ Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can u

- `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 `camera.position` to see the camera position and so on.
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 `world.getCameraPosition()` to see the camera position and so on.

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

Expand Down
3 changes: 2 additions & 1 deletion config.mcraft-only.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"alwaysReconnectButton": true,
"reportBugButtonWithReconnect": true
"reportBugButtonWithReconnect": true,
"allowAutoConnect": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
"mc-assets": "^0.2.62",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
"mineflayer-mouse": "^0.1.11",
"mineflayer-mouse": "^0.1.14",
"mineflayer-pathfinder": "^2.4.4",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",
Expand Down
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions renderer/viewer/baseGraphicsBackend.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { proxy } from 'valtio'
import { NonReactiveState, RendererReactiveState } from '../../src/appViewer'

export const getDefaultRendererState = (): {
reactive: RendererReactiveState
nonReactive: NonReactiveState
} => {
return {
reactive: {
reactive: proxy({
world: {
chunksLoaded: new Set(),
heightmaps: new Map(),
Expand All @@ -15,7 +16,7 @@ export const getDefaultRendererState = (): {
},
renderer: '',
preventEscapeMenu: false
},
}),
nonReactive: {
world: {
chunksLoaded: new Set(),
Expand Down
2 changes: 1 addition & 1 deletion renderer/viewer/lib/worldDataEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
if (bot?.time?.timeOfDay) {
this.emitter.emit('time', bot.time.timeOfDay)
}
if (bot.entity) {
if (bot?.entity) {
this.emitter.emit('playerEntity', bot.entity)
}
this.emitterGotConnected()
Expand Down
3 changes: 2 additions & 1 deletion renderer/viewer/three/documentRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ export class DocumentRenderer {
this.previousCanvasWidth = this.canvas.width
this.previousCanvasHeight = this.canvas.height

const supportsWebGL2 = 'WebGL2RenderingContext' in window
// Only initialize stats and DOM-related features in main thread
if (!externalCanvas) {
if (!externalCanvas && supportsWebGL2) {
this.stats = new TopRightStats(this.canvas as HTMLCanvasElement, this.config.statsVisible)
this.setupFpsTracking()
}
Expand Down
72 changes: 42 additions & 30 deletions renderer/viewer/three/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ export class Entities {
return typeof component === 'string' ? component : component.text ?? ''
}

getItemMesh (item, specificProps: ItemSpecificContextProperties, previousModel?: string) {
getItemMesh (item, specificProps: ItemSpecificContextProperties, faceCamera = false, previousModel?: string) {
if (!item.nbt && item.nbtData) item.nbt = item.nbtData
const textureUv = this.worldRenderer.getItemRenderData(item, specificProps)
if (previousModel && previousModel === textureUv?.modelName) return undefined
Expand Down Expand Up @@ -757,26 +757,37 @@ export class Entities {
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 + (sizeX), 1 - v - sizeY)
const material = new THREE.MeshStandardMaterial({
map: itemsTexture,
transparent: true,
alphaTest: 0.1,
})
const materialFlipped = new THREE.MeshStandardMaterial({
map: itemsTextureFlipped,
transparent: true,
alphaTest: 0.1,
})
const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [
// top left and right bottom are black box materials others are transparent
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
material, materialFlipped,
])
let mesh: THREE.Object3D
let itemsTextureFlipped: THREE.Texture | undefined
if (faceCamera) {
const spriteMat = new THREE.SpriteMaterial({
map: itemsTexture,
transparent: true,
alphaTest: 0.1,
})
mesh = new THREE.Sprite(spriteMat)
} else {
itemsTextureFlipped = itemsTexture.clone()
itemsTextureFlipped.repeat.x *= -1
itemsTextureFlipped.needsUpdate = true
itemsTextureFlipped.offset.set(u + (sizeX), 1 - v - sizeY)
const material = new THREE.MeshStandardMaterial({
map: itemsTexture,
transparent: true,
alphaTest: 0.1,
})
const materialFlipped = new THREE.MeshStandardMaterial({
map: itemsTextureFlipped,
transparent: true,
alphaTest: 0.1,
})
mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [
// top left and right bottom are black box materials others are transparent
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
material, materialFlipped,
])
}
let SCALE = 1
if (specificProps['minecraft:display_context'] === 'ground') {
SCALE = 0.5
Expand Down Expand Up @@ -805,8 +816,6 @@ export class Entities {
}

update (entity: SceneEntity['originalEntity'], overrides) {
const justAdded = !this.entities[entity.id]

const isPlayerModel = entity.name === 'player'
if (entity.name === 'zombie_villager' || entity.name === 'husk') {
overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}`
Expand All @@ -817,6 +826,7 @@ export class Entities {
}
// this can be undefined in case where packet entity_destroy was sent twice (so it was already deleted)
let e = this.entities[entity.id]
const justAdded = !e

if (entity.delete) {
if (!e) return
Expand All @@ -836,30 +846,32 @@ export class Entities {
if (e === undefined) {
const group = new THREE.Group() as unknown as SceneEntity
group.originalEntity = entity
if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block') {
const item = entity.name === 'tnt'
? { name: 'tnt' }
if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block' || entity.name === 'snowball'
|| entity.name === 'egg' || entity.name === 'ender_pearl' || entity.name === 'experience_bottle'
|| entity.name === 'splash_potion' || entity.name === 'lingering_potion') {
const item = entity.name === 'tnt' || entity.type === 'projectile'
? { name: entity.name }
: entity.name === 'falling_block'
? { blockState: entity['objectData'] }
: entity.metadata?.find((m: any) => typeof m === 'object' && m?.itemCount)
if (item) {
const object = this.getItemMesh(item, {
'minecraft:display_context': 'ground',
})
}, entity.type === 'projectile')
if (object) {
mesh = object.mesh
if (entity.name === 'item') {
if (entity.name === 'item' || entity.type === 'projectile') {
mesh.scale.set(0.5, 0.5, 0.5)
mesh.position.set(0, 0.2, 0)
mesh.position.set(0, entity.name === 'item' ? 0.2 : 0.1, 0)
} else {
mesh.scale.set(2, 2, 2)
mesh.position.set(0, 0.5, 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()
if (entity.name === 'item') {
const clock = new THREE.Clock()
mesh.onBeforeRender = () => {
const delta = clock.getDelta()
mesh!.rotation.y += delta
Expand Down
2 changes: 1 addition & 1 deletion renderer/viewer/three/holdingBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export default class HoldingBlock {
'minecraft:display_context': 'firstperson',
'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks,
'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks,
}, this.lastItemModelName)
}, false, this.lastItemModelName)
if (result) {
const { mesh: itemMesh, isBlock, modelName } = result
if (isBlock) {
Expand Down
3 changes: 2 additions & 1 deletion renderer/viewer/three/panorama.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { loadThreeJsTextureFromUrl, loadThreeJsTextureFromUrlSync } from './thre
import { WorldRendererThree } from './worldrendererThree'
import { EntityMesh } from './entity/EntityMesh'
import { DocumentRenderer } from './documentRenderer'
import { PANORAMA_VERSION } from './panoramaShared'

const panoramaFiles = [
'panorama_3.png', // right (+x)
Expand Down Expand Up @@ -156,7 +157,7 @@ export class PanoramaRenderer {
}

async worldBlocksPanorama () {
const version = '1.21.4'
const version = PANORAMA_VERSION
const fullResourceManager = this.options.resourcesManager as ResourcesManager
fullResourceManager.currentConfig = { version, noInventoryGui: true, }
await fullResourceManager.updateAssetsData({ })
Expand Down
1 change: 1 addition & 0 deletions renderer/viewer/three/panoramaShared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const PANORAMA_VERSION = '1.21.4'
11 changes: 9 additions & 2 deletions rsbuild.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ const appConfig = defineConfig({
// 50kb limit for data uri
dataUriLimit: SINGLE_FILE_BUILD ? 1 * 1024 * 1024 * 1024 : 50 * 1024
},
performance: {
// prefetch: {
// include(filename) {
// return filename.includes('mc-data') || filename.includes('mc-assets')
// },
// },
},
source: {
entry: {
index: './src/index.ts',
Expand All @@ -154,7 +161,7 @@ const appConfig = defineConfig({
'process.platform': '"browser"',
'process.env.GITHUB_URL':
JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` || githubRepositoryFallback}`),
'process.env.DEPS_VERSIONS': JSON.stringify({}),
'process.env.ALWAYS_MINIMAL_SERVER_UI': JSON.stringify(process.env.ALWAYS_MINIMAL_SERVER_UI),
'process.env.RELEASE_TAG': JSON.stringify(releaseTag),
'process.env.RELEASE_LINK': JSON.stringify(releaseLink),
'process.env.RELEASE_CHANGELOG': JSON.stringify(releaseChangelog),
Expand Down Expand Up @@ -190,7 +197,7 @@ const appConfig = defineConfig({
childProcess.execSync('tsx ./scripts/optimizeBlockCollisions.ts', { stdio: 'inherit' })
}
// childProcess.execSync(['tsx', './scripts/genLargeDataAliases.ts', ...(SINGLE_FILE_BUILD ? ['--compressed'] : [])].join(' '), { stdio: 'inherit' })
genLargeDataAliases(SINGLE_FILE_BUILD)
genLargeDataAliases(SINGLE_FILE_BUILD || process.env.ALWAYS_COMPRESS_LARGE_DATA === 'true')
fsExtra.copySync('./node_modules/mc-assets/dist/other-textures/latest/entity', './dist/textures/entity')
fsExtra.copySync('./assets/background', './dist/background')
fs.copyFileSync('./assets/favicon.png', './dist/favicon.png')
Expand Down
5 changes: 4 additions & 1 deletion scripts/genLargeDataAliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const genLargeDataAliases = async (isCompressed: boolean) => {

let str = `${decoderCode}\nexport const importLargeData = async (mod: ${Object.keys(modules).map(x => `'${x}'`).join(' | ')}) => {\n`
for (const [module, { compressed, raw }] of Object.entries(modules)) {
let importCode = `(await import('${isCompressed ? compressed : raw}')).default`;
const chunkName = module === 'mcData' ? 'mc-data' : 'mc-assets';
let importCode = `(await import(/* webpackChunkName: "${chunkName}" */ '${isCompressed ? compressed : raw}')).default`;
if (isCompressed) {
importCode = `JSON.parse(decompressFromBase64(${importCode}))`
}
Expand All @@ -30,6 +31,8 @@ export const genLargeDataAliases = async (isCompressed: boolean) => {
const decoderCode = /* ts */ `
import pako from 'pako';

globalThis.pako = { inflate: pako.inflate.bind(pako) }

function decompressFromBase64(input) {
console.time('decompressFromBase64')
// Decode the Base64 string
Expand Down
Loading
Loading