minecraft-renderer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +297 -0
- package/dist/index.html +83 -0
- package/dist/static/image/arrow.6f27b59f.png +0 -0
- package/dist/static/image/blocksAtlasLatest.7850afa3.png +0 -0
- package/dist/static/image/blocksAtlasLegacy.5c76823d.png +0 -0
- package/dist/static/image/itemsAtlasLatest.36036f95.png +0 -0
- package/dist/static/image/itemsAtlasLegacy.dcb1b58d.png +0 -0
- package/dist/static/image/tipped_arrow.6f27b59f.png +0 -0
- package/dist/static/js/365.f05233ab.js +8462 -0
- package/dist/static/js/365.f05233ab.js.LICENSE.txt +52 -0
- package/dist/static/js/async/738.efa27644.js +1 -0
- package/dist/static/js/index.092ec5be.js +56 -0
- package/dist/static/js/lib-polyfill.98986ac5.js +1 -0
- package/dist/static/js/lib-react.5c9129e0.js +2 -0
- package/dist/static/js/lib-react.5c9129e0.js.LICENSE.txt +39 -0
- package/package.json +104 -0
- package/src/assets/destroy_stage_0.png +0 -0
- package/src/assets/destroy_stage_1.png +0 -0
- package/src/assets/destroy_stage_2.png +0 -0
- package/src/assets/destroy_stage_3.png +0 -0
- package/src/assets/destroy_stage_4.png +0 -0
- package/src/assets/destroy_stage_5.png +0 -0
- package/src/assets/destroy_stage_6.png +0 -0
- package/src/assets/destroy_stage_7.png +0 -0
- package/src/assets/destroy_stage_8.png +0 -0
- package/src/assets/destroy_stage_9.png +0 -0
- package/src/examples/README.md +146 -0
- package/src/examples/appViewerExample.ts +205 -0
- package/src/examples/initialMenuStart.ts +161 -0
- package/src/graphicsBackend/appViewer.ts +297 -0
- package/src/graphicsBackend/config.ts +119 -0
- package/src/graphicsBackend/index.ts +10 -0
- package/src/graphicsBackend/playerState.ts +61 -0
- package/src/graphicsBackend/types.ts +143 -0
- package/src/index.ts +97 -0
- package/src/lib/DebugGui.ts +190 -0
- package/src/lib/animationController.ts +85 -0
- package/src/lib/buildSharedConfig.mjs +1 -0
- package/src/lib/cameraBobbing.ts +94 -0
- package/src/lib/canvas2DOverlay.example.ts +361 -0
- package/src/lib/canvas2DOverlay.quickstart.ts +242 -0
- package/src/lib/canvas2DOverlay.ts +381 -0
- package/src/lib/cleanupDecorator.ts +29 -0
- package/src/lib/createPlayerObject.ts +55 -0
- package/src/lib/frameTimingCollector.ts +164 -0
- package/src/lib/guiRenderer.ts +283 -0
- package/src/lib/items.ts +140 -0
- package/src/lib/mesherlogReader.ts +131 -0
- package/src/lib/moreBlockDataGenerated.json +714 -0
- package/src/lib/preflatMap.json +1741 -0
- package/src/lib/simpleUtils.ts +40 -0
- package/src/lib/smoothSwitcher.ts +168 -0
- package/src/lib/spiral.ts +29 -0
- package/src/lib/ui/newStats.ts +120 -0
- package/src/lib/utils/proxy.ts +23 -0
- package/src/lib/utils/skins.ts +63 -0
- package/src/lib/utils.ts +76 -0
- package/src/lib/workerProxy.ts +342 -0
- package/src/lib/worldrendererCommon.ts +1088 -0
- package/src/mesher/mesher.ts +253 -0
- package/src/mesher/models.ts +769 -0
- package/src/mesher/modelsGeometryCommon.ts +142 -0
- package/src/mesher/shared.ts +80 -0
- package/src/mesher/standaloneRenderer.ts +270 -0
- package/src/mesher/test/a.ts +3 -0
- package/src/mesher/test/mesherTester.ts +76 -0
- package/src/mesher/test/playground.ts +19 -0
- package/src/mesher/test/test-perf.ts +74 -0
- package/src/mesher/test/tests.test.ts +56 -0
- package/src/mesher/world.ts +294 -0
- package/src/mesher/worldConstants.ts +1 -0
- package/src/modules/index.ts +11 -0
- package/src/modules/starfield.ts +313 -0
- package/src/modules/types.ts +110 -0
- package/src/playerState/playerState.ts +78 -0
- package/src/playerState/types.ts +36 -0
- package/src/playground/allEntitiesDebug.ts +170 -0
- package/src/playground/baseScene.ts +587 -0
- package/src/playground/mobileControls.tsx +268 -0
- package/src/playground/playground.html +83 -0
- package/src/playground/playground.ts +11 -0
- package/src/playground/playgroundUi.tsx +140 -0
- package/src/playground/reactUtils.ts +71 -0
- package/src/playground/scenes/allEntities.ts +13 -0
- package/src/playground/scenes/entities.ts +37 -0
- package/src/playground/scenes/floorRandom.ts +33 -0
- package/src/playground/scenes/frequentUpdates.ts +148 -0
- package/src/playground/scenes/geometryExport.ts +142 -0
- package/src/playground/scenes/index.ts +12 -0
- package/src/playground/scenes/lightingStarfield.ts +40 -0
- package/src/playground/scenes/main.ts +313 -0
- package/src/playground/scenes/railsCobweb.ts +14 -0
- package/src/playground/scenes/rotationIssue.ts +7 -0
- package/src/playground/scenes/slabsOptimization.ts +16 -0
- package/src/playground/scenes/transparencyIssue.ts +11 -0
- package/src/playground/shared.ts +79 -0
- package/src/resourcesManager/index.ts +5 -0
- package/src/resourcesManager/resourcesManager.ts +314 -0
- package/src/shims/minecraftData.ts +41 -0
- package/src/sign-renderer/index.html +21 -0
- package/src/sign-renderer/index.ts +216 -0
- package/src/sign-renderer/noop.js +1 -0
- package/src/sign-renderer/playground.ts +38 -0
- package/src/sign-renderer/tests.test.ts +69 -0
- package/src/sign-renderer/vite.config.ts +10 -0
- package/src/three/appShared.ts +75 -0
- package/src/three/bannerRenderer.ts +275 -0
- package/src/three/cameraShake.ts +120 -0
- package/src/three/cinimaticScript.ts +350 -0
- package/src/three/documentRenderer.ts +491 -0
- package/src/three/entities.ts +1580 -0
- package/src/three/entity/EntityMesh.ts +707 -0
- package/src/three/entity/animations.js +171 -0
- package/src/three/entity/armorModels.json +204 -0
- package/src/three/entity/armorModels.ts +36 -0
- package/src/three/entity/entities.json +6230 -0
- package/src/three/entity/exportedModels.js +38 -0
- package/src/three/entity/externalTextures.json +1 -0
- package/src/three/entity/models/allay.obj +325 -0
- package/src/three/entity/models/arrow.obj +60 -0
- package/src/three/entity/models/axolotl.obj +509 -0
- package/src/three/entity/models/blaze.obj +601 -0
- package/src/three/entity/models/boat.obj +417 -0
- package/src/three/entity/models/camel.obj +1061 -0
- package/src/three/entity/models/cat.obj +509 -0
- package/src/three/entity/models/chicken.obj +371 -0
- package/src/three/entity/models/cod.obj +371 -0
- package/src/three/entity/models/creeper.obj +279 -0
- package/src/three/entity/models/dolphin.obj +371 -0
- package/src/three/entity/models/ender_dragon.obj +2993 -0
- package/src/three/entity/models/enderman.obj +325 -0
- package/src/three/entity/models/endermite.obj +187 -0
- package/src/three/entity/models/fox.obj +463 -0
- package/src/three/entity/models/frog.obj +739 -0
- package/src/three/entity/models/ghast.obj +463 -0
- package/src/three/entity/models/goat.obj +601 -0
- package/src/three/entity/models/guardian.obj +1015 -0
- package/src/three/entity/models/horse.obj +1061 -0
- package/src/three/entity/models/llama.obj +509 -0
- package/src/three/entity/models/minecart.obj +233 -0
- package/src/three/entity/models/parrot.obj +509 -0
- package/src/three/entity/models/piglin.obj +739 -0
- package/src/three/entity/models/pillager.obj +371 -0
- package/src/three/entity/models/rabbit.obj +555 -0
- package/src/three/entity/models/sheep.obj +555 -0
- package/src/three/entity/models/shulker.obj +141 -0
- package/src/three/entity/models/sniffer.obj +693 -0
- package/src/three/entity/models/spider.obj +509 -0
- package/src/three/entity/models/tadpole.obj +95 -0
- package/src/three/entity/models/turtle.obj +371 -0
- package/src/three/entity/models/vex.obj +325 -0
- package/src/three/entity/models/villager.obj +509 -0
- package/src/three/entity/models/warden.obj +463 -0
- package/src/three/entity/models/witch.obj +647 -0
- package/src/three/entity/models/wolf.obj +509 -0
- package/src/three/entity/models/zombie_villager.obj +463 -0
- package/src/three/entity/objModels.js +1 -0
- package/src/three/fireworks.ts +661 -0
- package/src/three/fireworksRenderer.ts +434 -0
- package/src/three/globals.d.ts +7 -0
- package/src/three/graphicsBackend.ts +274 -0
- package/src/three/graphicsBackendOffThread.ts +107 -0
- package/src/three/hand.ts +89 -0
- package/src/three/holdingBlock.ts +926 -0
- package/src/three/index.ts +20 -0
- package/src/three/itemMesh.ts +427 -0
- package/src/three/modules.d.ts +14 -0
- package/src/three/panorama.ts +308 -0
- package/src/three/panoramaShared.ts +1 -0
- package/src/three/renderSlot.ts +82 -0
- package/src/three/skyboxRenderer.ts +406 -0
- package/src/three/starField.ts +13 -0
- package/src/three/threeJsMedia.ts +731 -0
- package/src/three/threeJsMethods.ts +15 -0
- package/src/three/threeJsParticles.ts +160 -0
- package/src/three/threeJsSound.ts +95 -0
- package/src/three/threeJsUtils.ts +90 -0
- package/src/three/waypointSprite.ts +435 -0
- package/src/three/waypoints.ts +163 -0
- package/src/three/world/cursorBlock.ts +172 -0
- package/src/three/world/vr.ts +257 -0
- package/src/three/worldGeometryExport.ts +259 -0
- package/src/three/worldGeometryHandler.ts +279 -0
- package/src/three/worldRendererThree.ts +1381 -0
- package/src/worldView/index.ts +6 -0
- package/src/worldView/types.ts +66 -0
- package/src/worldView/worldView.ts +424 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared WorldView types used by both main thread and mesher worker.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Vec3 } from 'vec3'
|
|
6
|
+
|
|
7
|
+
/** Chunk position key format: "x,z" e.g. "16,16" */
|
|
8
|
+
export type ChunkPosKey = string
|
|
9
|
+
|
|
10
|
+
/** Chunk position object */
|
|
11
|
+
export type ChunkPos = { x: number; z: number }
|
|
12
|
+
|
|
13
|
+
/** World size parameters sent with chunk data */
|
|
14
|
+
export interface WorldSizeParams {
|
|
15
|
+
minY: number
|
|
16
|
+
worldHeight: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Block update event data */
|
|
20
|
+
export interface BlockUpdateData {
|
|
21
|
+
pos: Vec3
|
|
22
|
+
stateId: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Chunk load event data */
|
|
26
|
+
export interface LoadChunkData {
|
|
27
|
+
x: number
|
|
28
|
+
z: number
|
|
29
|
+
chunk: string
|
|
30
|
+
blockEntities: any
|
|
31
|
+
worldConfig: WorldSizeParams
|
|
32
|
+
isLightUpdate: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Chunk unload event data */
|
|
36
|
+
export interface UnloadChunkData {
|
|
37
|
+
x: number
|
|
38
|
+
z: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Biome update event data */
|
|
42
|
+
export interface BiomeUpdateData {
|
|
43
|
+
biome: any
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* WorldView events emitted to the renderer.
|
|
48
|
+
*/
|
|
49
|
+
export type WorldViewEvents = {
|
|
50
|
+
chunkPosUpdate: (data: { pos: Vec3 }) => void
|
|
51
|
+
blockUpdate: (data: BlockUpdateData) => void
|
|
52
|
+
entity: (data: any) => void
|
|
53
|
+
entityMoved: (data: any) => void
|
|
54
|
+
playerEntity: (data: any) => void
|
|
55
|
+
time: (data: number) => void
|
|
56
|
+
renderDistance: (viewDistance: number) => void
|
|
57
|
+
blockEntities: (data: Record<string, any> | { blockEntities: Record<string, any> }) => void
|
|
58
|
+
markAsLoaded: (data: ChunkPos) => void
|
|
59
|
+
unloadChunk: (data: UnloadChunkData) => void
|
|
60
|
+
loadChunk: (data: LoadChunkData) => void
|
|
61
|
+
updateLight: (data: { pos: Vec3 }) => void
|
|
62
|
+
onWorldSwitch: () => void
|
|
63
|
+
end: () => void
|
|
64
|
+
biomeUpdate: (data: BiomeUpdateData) => void
|
|
65
|
+
biomeReset: () => void
|
|
66
|
+
}
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorldView - World data emitter for the renderer.
|
|
3
|
+
* Renamed from WorldDataEmitter for clarity.
|
|
4
|
+
*
|
|
5
|
+
* Handles chunk loading/unloading, block updates, and world events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from 'events'
|
|
9
|
+
import { Vec3 } from 'vec3'
|
|
10
|
+
import TypedEmitter from 'typed-emitter'
|
|
11
|
+
import type { WorldViewEvents, ChunkPosKey, WorldSizeParams } from './types'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Helper to calculate chunk position from absolute position.
|
|
15
|
+
*/
|
|
16
|
+
export const chunkPos = (pos: { x: number; z: number } | Vec3): [number, number] => {
|
|
17
|
+
return [Math.floor(pos.x / 16), Math.floor(pos.z / 16)]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Helper to calculate section position from absolute position.
|
|
22
|
+
*/
|
|
23
|
+
export const sectionPos = (pos: { x: number; y: number; z: number }): [number, number, number] => {
|
|
24
|
+
return [Math.floor(pos.x / 16), Math.floor(pos.y / 16), Math.floor(pos.z / 16)]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates a spiral matrix of chunk positions for loading chunks in order from center.
|
|
29
|
+
*/
|
|
30
|
+
export const generateSpiralMatrix = (viewDistance: number): [number, number][] => {
|
|
31
|
+
const result: [number, number][] = []
|
|
32
|
+
const size = viewDistance * 2 + 1
|
|
33
|
+
let x = 0
|
|
34
|
+
let z = 0
|
|
35
|
+
let dx = 0
|
|
36
|
+
let dz = -1
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < size * size; i++) {
|
|
39
|
+
if (-viewDistance <= x && x <= viewDistance && -viewDistance <= z && z <= viewDistance) {
|
|
40
|
+
result.push([x, z])
|
|
41
|
+
}
|
|
42
|
+
if (x === z || (x < 0 && x === -z) || (x > 0 && x === 1 - z)) {
|
|
43
|
+
const temp = dx
|
|
44
|
+
dx = -dz
|
|
45
|
+
dz = temp
|
|
46
|
+
}
|
|
47
|
+
x += dx
|
|
48
|
+
z += dz
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Delayed iterator for chunk loading with configurable delay.
|
|
56
|
+
*/
|
|
57
|
+
export const delayedIterator = async <T>(
|
|
58
|
+
arr: T[],
|
|
59
|
+
delay: number,
|
|
60
|
+
exec: (item: T, index: number) => Promise<void>,
|
|
61
|
+
chunkSize = 1
|
|
62
|
+
): Promise<void> => {
|
|
63
|
+
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
64
|
+
if (delay) {
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, delay))
|
|
66
|
+
}
|
|
67
|
+
await exec(arr[i], i)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* WorldView for worker thread communication.
|
|
73
|
+
* This is a lightweight version that receives events from the main thread.
|
|
74
|
+
*/
|
|
75
|
+
export class WorldViewWorker extends (EventEmitter as new () => TypedEmitter<WorldViewEvents>) {
|
|
76
|
+
static readonly restorerName = 'WorldViewWorker'
|
|
77
|
+
|
|
78
|
+
static restoreTransferred(data: any, worker?: Worker): WorldViewWorker {
|
|
79
|
+
const worldView = new WorldViewWorker()
|
|
80
|
+
if (worker) {
|
|
81
|
+
worker.addEventListener('message', ({ data }) => {
|
|
82
|
+
if (data.class === WorldViewWorker.restorerName) {
|
|
83
|
+
if (data.type === 'event') {
|
|
84
|
+
worldView.emit(data.eventName, ...data.args)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
return worldView
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* World data provider interface for different world implementations.
|
|
95
|
+
*/
|
|
96
|
+
export interface WorldProvider {
|
|
97
|
+
getColumnAt(pos: Vec3): any | null
|
|
98
|
+
setBlockStateId(pos: Vec3, stateId: number): void | Promise<void>
|
|
99
|
+
getBiome?(pos: Vec3): number
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* WorldView - Main world data emitter for the renderer.
|
|
104
|
+
*
|
|
105
|
+
* Responsible for:
|
|
106
|
+
* - Loading/unloading chunks based on view distance
|
|
107
|
+
* - Emitting block updates to the renderer
|
|
108
|
+
* - Managing loaded chunks state
|
|
109
|
+
* - Spiral chunk loading for optimal player experience
|
|
110
|
+
*/
|
|
111
|
+
export class WorldView extends (EventEmitter as new () => TypedEmitter<WorldViewEvents>) {
|
|
112
|
+
spiralNumber = 0
|
|
113
|
+
gotPanicLastTime = false
|
|
114
|
+
panicChunksReload = () => { }
|
|
115
|
+
loadedChunks: Record<ChunkPosKey, boolean> = {}
|
|
116
|
+
private inLoading = false
|
|
117
|
+
private chunkReceiveTimes: number[] = []
|
|
118
|
+
private lastChunkReceiveTime = 0
|
|
119
|
+
public lastChunkReceiveTimeAvg = 0
|
|
120
|
+
private panicTimeout?: ReturnType<typeof setTimeout>
|
|
121
|
+
readonly lastPos: Vec3
|
|
122
|
+
private eventListeners: Record<string, any> = {}
|
|
123
|
+
debugChunksInfo: Record<ChunkPosKey, {
|
|
124
|
+
loads: Array<{
|
|
125
|
+
dataLength: number
|
|
126
|
+
reason: string
|
|
127
|
+
time: number
|
|
128
|
+
}>
|
|
129
|
+
}> = {}
|
|
130
|
+
|
|
131
|
+
waitingSpiralChunksLoad: Record<ChunkPosKey, (value: boolean) => void> = {}
|
|
132
|
+
|
|
133
|
+
addWaitTime = 1
|
|
134
|
+
keepChunksDistance = 0
|
|
135
|
+
isPlayground = false
|
|
136
|
+
allowPositionUpdate = true
|
|
137
|
+
|
|
138
|
+
constructor(
|
|
139
|
+
public world: WorldProvider,
|
|
140
|
+
public viewDistance: number,
|
|
141
|
+
position: Vec3 = new Vec3(0, 0, 0)
|
|
142
|
+
) {
|
|
143
|
+
super()
|
|
144
|
+
this.lastPos = new Vec3(0, 0, 0).update(position)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Prepare this WorldView for transfer to a worker thread.
|
|
149
|
+
*/
|
|
150
|
+
prepareForTransfer(worker?: Worker): { __restorer: string } {
|
|
151
|
+
if (worker) {
|
|
152
|
+
const oldEmit = this.emit.bind(this) as any
|
|
153
|
+
this.emit = ((eventName: keyof WorldViewEvents, ...args: any[]) => {
|
|
154
|
+
oldEmit(eventName, ...args)
|
|
155
|
+
worker.postMessage({
|
|
156
|
+
class: WorldViewWorker.restorerName,
|
|
157
|
+
type: 'event',
|
|
158
|
+
eventName,
|
|
159
|
+
args,
|
|
160
|
+
})
|
|
161
|
+
}) as any
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
__restorer: WorldViewWorker.restorerName,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Set a block state and emit update to renderer.
|
|
170
|
+
*/
|
|
171
|
+
setBlockStateId(position: Vec3, stateId: number): void {
|
|
172
|
+
const val = this.world.setBlockStateId(position, stateId)
|
|
173
|
+
if (val && typeof (val as any).then === 'function') {
|
|
174
|
+
throw new Error('setBlockStateId returned promise (not supported)')
|
|
175
|
+
}
|
|
176
|
+
this.emit('blockUpdate', { pos: position, stateId })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Update the view distance and notify renderer.
|
|
181
|
+
*/
|
|
182
|
+
updateViewDistance(viewDistance: number): void {
|
|
183
|
+
this.viewDistance = viewDistance
|
|
184
|
+
this.emit('renderDistance', viewDistance)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Initialize the world view and start loading chunks.
|
|
189
|
+
*/
|
|
190
|
+
async init(pos: Vec3, bot?: any): Promise<void> {
|
|
191
|
+
console.log('WorldView init')
|
|
192
|
+
this.updateViewDistance(this.viewDistance)
|
|
193
|
+
this.emit('chunkPosUpdate', { pos })
|
|
194
|
+
|
|
195
|
+
// Emit time and player entity if bot is provided
|
|
196
|
+
if (bot?.time?.timeOfDay !== undefined) {
|
|
197
|
+
this.emit('time', bot.time.timeOfDay)
|
|
198
|
+
}
|
|
199
|
+
if (bot?.entity) {
|
|
200
|
+
this.emit('playerEntity', bot.entity)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Emit block entities if not in offscreen/worker context
|
|
204
|
+
this.emitterGotConnected(bot)
|
|
205
|
+
|
|
206
|
+
const [botX, botZ] = chunkPos(pos)
|
|
207
|
+
const positions = generateSpiralMatrix(this.viewDistance).map(
|
|
208
|
+
([x, z]) => new Vec3((botX + x) * 16, 0, (botZ + z) * 16)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
this.lastPos.update(pos)
|
|
212
|
+
await this._loadChunks(positions, pos)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private chunkProgress(): void {
|
|
216
|
+
if (this.panicTimeout) clearTimeout(this.panicTimeout)
|
|
217
|
+
if (this.chunkReceiveTimes.length >= 5) {
|
|
218
|
+
const avgReceiveTime = this.chunkReceiveTimes.reduce((a, b) => a + b, 0) / this.chunkReceiveTimes.length
|
|
219
|
+
this.lastChunkReceiveTimeAvg = avgReceiveTime
|
|
220
|
+
const timeoutDelay = avgReceiveTime * 2 + 1000
|
|
221
|
+
|
|
222
|
+
if (this.panicTimeout) clearTimeout(this.panicTimeout)
|
|
223
|
+
|
|
224
|
+
this.panicTimeout = setTimeout(() => {
|
|
225
|
+
if (!this.gotPanicLastTime && this.inLoading) {
|
|
226
|
+
console.warn('Chunk loading seems stuck, triggering panic reload')
|
|
227
|
+
this.gotPanicLastTime = true
|
|
228
|
+
this.panicChunksReload()
|
|
229
|
+
}
|
|
230
|
+
}, timeoutDelay)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private async _loadChunks(positions: Vec3[], centerPos: Vec3): Promise<void> {
|
|
235
|
+
this.spiralNumber++
|
|
236
|
+
const { spiralNumber } = this
|
|
237
|
+
|
|
238
|
+
// Stop loading previous chunks
|
|
239
|
+
for (const pos of Object.keys(this.waitingSpiralChunksLoad)) {
|
|
240
|
+
this.waitingSpiralChunksLoad[pos](false)
|
|
241
|
+
delete this.waitingSpiralChunksLoad[pos]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let continueLoading = true
|
|
245
|
+
this.inLoading = true
|
|
246
|
+
|
|
247
|
+
await delayedIterator(positions, this.addWaitTime, async (pos) => {
|
|
248
|
+
if (!continueLoading || this.loadedChunks[`${pos.x},${pos.z}`]) return
|
|
249
|
+
|
|
250
|
+
// Wait for chunk to be available from server
|
|
251
|
+
if (!this.world.getColumnAt(pos)) {
|
|
252
|
+
continueLoading = await new Promise<boolean>(resolve => {
|
|
253
|
+
this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] = resolve
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
if (!continueLoading) return
|
|
257
|
+
await this.loadChunk(pos, undefined, `spiral ${spiralNumber} from ${centerPos.x},${centerPos.z}`)
|
|
258
|
+
this.chunkProgress()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if (this.panicTimeout) clearTimeout(this.panicTimeout)
|
|
262
|
+
this.inLoading = false
|
|
263
|
+
this.gotPanicLastTime = false
|
|
264
|
+
this.chunkReceiveTimes = []
|
|
265
|
+
this.lastChunkReceiveTime = 0
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Load a chunk at the given position.
|
|
270
|
+
*/
|
|
271
|
+
async loadChunk(
|
|
272
|
+
pos: { x: number; z: number; y?: number },
|
|
273
|
+
isLightUpdate = false,
|
|
274
|
+
reason = 'spiral'
|
|
275
|
+
): Promise<void> {
|
|
276
|
+
const [botX, botZ] = chunkPos(this.lastPos)
|
|
277
|
+
const dx = Math.abs(botX - Math.floor(pos.x / 16))
|
|
278
|
+
const dz = Math.abs(botZ - Math.floor(pos.z / 16))
|
|
279
|
+
|
|
280
|
+
if (dx <= this.viewDistance && dz <= this.viewDistance) {
|
|
281
|
+
const column = await this.world.getColumnAt(
|
|
282
|
+
pos.y !== undefined ? (pos as Vec3) : new Vec3(pos.x, 0, pos.z)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if (column) {
|
|
286
|
+
const chunk = column.toJson()
|
|
287
|
+
const worldConfig: WorldSizeParams = {
|
|
288
|
+
minY: column.minY ?? 0,
|
|
289
|
+
worldHeight: column.worldHeight ?? 256,
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.emit('loadChunk', {
|
|
293
|
+
x: pos.x,
|
|
294
|
+
z: pos.z,
|
|
295
|
+
chunk,
|
|
296
|
+
blockEntities: column.blockEntities,
|
|
297
|
+
worldConfig,
|
|
298
|
+
isLightUpdate
|
|
299
|
+
})
|
|
300
|
+
this.loadedChunks[`${pos.x},${pos.z}`] = true
|
|
301
|
+
|
|
302
|
+
this.debugChunksInfo[`${pos.x},${pos.z}`] ??= { loads: [] }
|
|
303
|
+
this.debugChunksInfo[`${pos.x},${pos.z}`].loads.push({
|
|
304
|
+
dataLength: chunk.length,
|
|
305
|
+
reason,
|
|
306
|
+
time: Date.now(),
|
|
307
|
+
})
|
|
308
|
+
} else if (this.isPlayground) {
|
|
309
|
+
this.emit('markAsLoaded', { x: pos.x, z: pos.z })
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Unload all chunks.
|
|
316
|
+
*/
|
|
317
|
+
unloadAllChunks(): void {
|
|
318
|
+
for (const coords of Object.keys(this.loadedChunks)) {
|
|
319
|
+
const [x, z] = coords.split(',').map(Number)
|
|
320
|
+
this.unloadChunk({ x, z })
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Unload a specific chunk.
|
|
326
|
+
*/
|
|
327
|
+
unloadChunk(pos: { x: number; z: number }): void {
|
|
328
|
+
this.emit('unloadChunk', { x: pos.x, z: pos.z })
|
|
329
|
+
delete this.loadedChunks[`${pos.x},${pos.z}`]
|
|
330
|
+
delete this.debugChunksInfo[`${pos.x},${pos.z}`]
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Emit block entities when connected.
|
|
335
|
+
* Only works in main thread (not offscreen/worker context).
|
|
336
|
+
*/
|
|
337
|
+
emitterGotConnected(bot?: any): void {
|
|
338
|
+
// Skip if in offscreen/worker context
|
|
339
|
+
const isOffscreen = typeof (globalThis as any).WorkerGlobalScope !== 'undefined' &&
|
|
340
|
+
globalThis instanceof (globalThis as any).WorkerGlobalScope
|
|
341
|
+
|
|
342
|
+
if (isOffscreen || !bot) return
|
|
343
|
+
|
|
344
|
+
this.emit('blockEntities', new Proxy({}, {
|
|
345
|
+
get(_target, posKey, receiver) {
|
|
346
|
+
if (typeof posKey !== 'string') return
|
|
347
|
+
const [x, y, z] = posKey.split(',').map(Number)
|
|
348
|
+
return bot.world.getBlock(new Vec3(x, y, z))?.entity
|
|
349
|
+
},
|
|
350
|
+
}))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private lastBiomeId: number | null = null
|
|
354
|
+
|
|
355
|
+
private updateBiome(pos: Vec3): void {
|
|
356
|
+
try {
|
|
357
|
+
if (!this.world.getBiome) return
|
|
358
|
+
const biomeId = this.world.getBiome(pos)
|
|
359
|
+
if (biomeId !== this.lastBiomeId) {
|
|
360
|
+
this.lastBiomeId = biomeId
|
|
361
|
+
// Note: Biome data lookup would need to be provided externally
|
|
362
|
+
// This is a simplified version
|
|
363
|
+
this.emit('biomeReset')
|
|
364
|
+
}
|
|
365
|
+
} catch (e) {
|
|
366
|
+
console.error('error updating biome', e)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private lastPosCheck: Vec3 | null = null
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Update position and load/unload chunks as needed.
|
|
374
|
+
*/
|
|
375
|
+
async updatePosition(pos: Vec3, force = false): Promise<void> {
|
|
376
|
+
if (!this.allowPositionUpdate) return
|
|
377
|
+
const posFloored = pos.floored()
|
|
378
|
+
if (!force && this.lastPosCheck && this.lastPosCheck.equals(posFloored)) return
|
|
379
|
+
this.lastPosCheck = posFloored
|
|
380
|
+
|
|
381
|
+
this.updateBiome(pos)
|
|
382
|
+
|
|
383
|
+
const [lastX, lastZ] = chunkPos(this.lastPos)
|
|
384
|
+
const [botX, botZ] = chunkPos(pos)
|
|
385
|
+
|
|
386
|
+
if (lastX !== botX || lastZ !== botZ || force) {
|
|
387
|
+
this.emit('chunkPosUpdate', { pos })
|
|
388
|
+
|
|
389
|
+
// Unload chunks that are no longer in view
|
|
390
|
+
const chunksToUnload: Vec3[] = []
|
|
391
|
+
const viewDistanceWithBuffer = this.viewDistance + this.keepChunksDistance
|
|
392
|
+
|
|
393
|
+
for (const coords of Object.keys(this.loadedChunks)) {
|
|
394
|
+
const [x, z] = coords.split(',').map(Number)
|
|
395
|
+
const p = new Vec3(x, 0, z)
|
|
396
|
+
const [chunkX, chunkZ] = chunkPos(p)
|
|
397
|
+
const dx = Math.abs(botX - chunkX)
|
|
398
|
+
const dz = Math.abs(botZ - chunkZ)
|
|
399
|
+
if (dx > viewDistanceWithBuffer || dz > viewDistanceWithBuffer) {
|
|
400
|
+
chunksToUnload.push(p)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
for (const p of chunksToUnload) {
|
|
405
|
+
this.unloadChunk(p)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Load new chunks
|
|
409
|
+
const positions = generateSpiralMatrix(this.viewDistance)
|
|
410
|
+
.map(([x, z]) => {
|
|
411
|
+
const newPos = new Vec3((botX + x) * 16, 0, (botZ + z) * 16)
|
|
412
|
+
if (!this.loadedChunks[`${newPos.x},${newPos.z}`]) return newPos
|
|
413
|
+
return undefined!
|
|
414
|
+
})
|
|
415
|
+
.filter(a => !!a)
|
|
416
|
+
|
|
417
|
+
this.lastPos.update(pos)
|
|
418
|
+
void this._loadChunks(positions, pos)
|
|
419
|
+
} else {
|
|
420
|
+
this.emit('chunkPosUpdate', { pos })
|
|
421
|
+
this.lastPos.update(pos)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|