minecraft-clone / src /stores /worldStore.ts
TomatitoToho's picture
Upload src/stores/worldStore.ts with huggingface_hub
7e70951 verified
Raw
History Blame Contribute Delete
8.06 kB
'use client'
import { create } from 'zustand'
import type { ChunkData, World } from '@/types'
import {
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH,
BLOCK_AIR, MIN_BUILD_HEIGHT,
} from '@/engine/constants'
import { TerrainGenerator } from '@/engine/terrain/generator'
import { NetherGenerator } from '@/engine/terrain/dimensionGenerator'
import { EndGenerator } from '@/engine/terrain/dimensionGenerator'
// ═══════════════════════════════════════════════════════════════
// DIMENSION CONSTANTS
// ═══════════════════════════════════════════════════════════════
const NETHER_CHUNK_HEIGHT = 128
const END_CHUNK_HEIGHT = 128
type DimensionType = 'overworld' | 'nether' | 'end'
function chunkKey(cx: number, cz: number): string {
return `${cx},${cz}`
}
// ═══════════════════════════════════════════════════════════════
// STORE INTERFACE
// ═══════════════════════════════════════════════════════════════
interface WorldStore {
world: World | null
chunks: Map<string, ChunkData>
dimension: DimensionType
netherSeed: number
endSeed: number
// Generators (lazy-initialized)
overworldGenerator: TerrainGenerator | null
netherGenerator: NetherGenerator | null
endGenerator: EndGenerator | null
initWorld: (seed: number) => void
setChunk: (key: string, chunk: ChunkData) => void
removeChunk: (key: string) => void
getBlock: (x: number, y: number, z: number) => number
setBlock: (x: number, y: number, z: number, blockId: number) => void
updateWorldTime: () => void
switchDimension: (dimension: string) => void
generateChunk: (cx: number, cz: number) => ChunkData
getCurrentChunkHeight: () => number
}
// ═══════════════════════════════════════════════════════════════
// STORE IMPLEMENTATION
// ═══════════════════════════════════════════════════════════════
export const useWorldStore = create<WorldStore>((set, get) => ({
world: null,
chunks: new Map(),
dimension: 'overworld',
netherSeed: 0,
endSeed: 0,
overworldGenerator: null,
netherGenerator: null,
endGenerator: null,
initWorld: (seed: number) => {
// Derive dimension seeds from main seed
const netherSeed = (seed * 0x5bd1e995 + 0xDEAD) | 0
const endSeed = (seed * 0x27d4eb2d + 0xBEEF) | 0
const world: World = {
seed,
time: 6000, // noon
gameTime: 0,
spawnPoint: [8, 80, 8],
weather: { raining: false, thundering: false, rainTime: 0, thunderTime: 0 },
dimension: 'overworld',
}
set({
world,
chunks: new Map(),
dimension: 'overworld',
netherSeed,
endSeed,
overworldGenerator: new TerrainGenerator(seed),
netherGenerator: new NetherGenerator(netherSeed),
endGenerator: new EndGenerator(endSeed),
})
},
setChunk: (key, chunk) => {
const chunks = new Map(get().chunks)
chunks.set(key, chunk)
set({ chunks })
},
removeChunk: (key) => {
const chunks = new Map(get().chunks)
chunks.delete(key)
set({ chunks })
},
getBlock: (x, y, z) => {
const { dimension } = get()
// Different height ranges per dimension
if (dimension === 'overworld') {
if (y < MIN_BUILD_HEIGHT || y > 319) return BLOCK_AIR
} else {
// Nether and End: 0-127
if (y < 0 || y > 127) return BLOCK_AIR
}
const cx = Math.floor(x / CHUNK_WIDTH)
const cz = Math.floor(z / CHUNK_DEPTH)
const chunk = get().chunks.get(chunkKey(cx, cz))
if (!chunk) return BLOCK_AIR
const lx = ((x % CHUNK_WIDTH) + CHUNK_WIDTH) % CHUNK_WIDTH
const lz = ((z % CHUNK_DEPTH) + CHUNK_DEPTH) % CHUNK_DEPTH
// Y offset depends on dimension
let ly: number
if (dimension === 'overworld') {
ly = y - MIN_BUILD_HEIGHT // offset for negative Y
} else {
ly = y // nether/end start at 0
}
const chunkH = get().getCurrentChunkHeight()
if (ly < 0 || ly >= chunkH) return BLOCK_AIR
const idx = lx + lz * CHUNK_WIDTH + ly * CHUNK_WIDTH * CHUNK_DEPTH
return chunk.blocks[idx] || BLOCK_AIR
},
setBlock: (x, y, z, blockId) => {
const { dimension } = get()
if (dimension === 'overworld') {
if (y < MIN_BUILD_HEIGHT || y > 319) return
} else {
if (y < 0 || y > 127) return
}
const cx = Math.floor(x / CHUNK_WIDTH)
const cz = Math.floor(z / CHUNK_DEPTH)
const key = chunkKey(cx, cz)
const chunk = get().chunks.get(key)
if (!chunk) return
const lx = ((x % CHUNK_WIDTH) + CHUNK_WIDTH) % CHUNK_WIDTH
const lz = ((z % CHUNK_DEPTH) + CHUNK_DEPTH) % CHUNK_DEPTH
let ly: number
if (dimension === 'overworld') {
ly = y - MIN_BUILD_HEIGHT
} else {
ly = y
}
const chunkH = get().getCurrentChunkHeight()
if (ly < 0 || ly >= chunkH) return
const idx = lx + lz * CHUNK_WIDTH + ly * CHUNK_WIDTH * CHUNK_DEPTH
chunk.blocks[idx] = blockId
chunk.dirty = true
chunk.meshVersion++
set({ chunks: new Map(get().chunks) })
},
updateWorldTime: () => {
const { world } = get()
if (!world) return
world.time = (world.time + 1) % 24000
world.gameTime++
set({ world })
},
switchDimension: (dimension: string) => {
const { world } = get()
if (!world) return
const dim = dimension as DimensionType
if (dim === get().dimension) return
// Update the world's dimension
world.dimension = dim
// Set spawn point based on dimension
let spawnPoint: [number, number, number]
switch (dim) {
case 'nether':
spawnPoint = [8, 64, 8]
break
case 'end':
spawnPoint = [0, 65, 0]
break
default:
spawnPoint = [8, 80, 8]
}
// Clear all chunks — they will be regenerated by the renderer
// using the appropriate dimension generator
set({
world,
chunks: new Map(),
dimension: dim,
})
// Update spawn point
world.spawnPoint = spawnPoint
set({ world })
},
generateChunk: (cx: number, cz: number): ChunkData => {
const { dimension, overworldGenerator, netherGenerator, endGenerator } = get()
switch (dimension) {
case 'nether':
if (!netherGenerator) {
// Lazy init if needed
const { netherSeed } = get()
const gen = new NetherGenerator(netherSeed)
set({ netherGenerator: gen })
return gen.generateChunk(cx, cz)
}
return netherGenerator.generateChunk(cx, cz)
case 'end':
if (!endGenerator) {
const { endSeed } = get()
const gen = new EndGenerator(endSeed)
set({ endGenerator: gen })
return gen.generateChunk(cx, cz)
}
return endGenerator.generateChunk(cx, cz)
case 'overworld':
default:
if (!overworldGenerator) {
const { world } = get()
const seed = world?.seed ?? 12345
const gen = new TerrainGenerator(seed)
set({ overworldGenerator: gen })
return gen.generateChunk(cx, cz)
}
return overworldGenerator.generateChunk(cx, cz)
}
},
getCurrentChunkHeight: (): number => {
const { dimension } = get()
switch (dimension) {
case 'nether':
return NETHER_CHUNK_HEIGHT
case 'end':
return END_CHUNK_HEIGHT
default:
return CHUNK_HEIGHT
}
},
}))