mirror of
https://github.com/danbulant/flying-squid
synced 2026-06-11 02:21:18 +00:00
normal position is now possible because I understood it's necessary to stored the last sent position in order to compensate for the loss of precision next time a relative position packet is sent I introduced a features.json which makes it possible to describe high level differences between version and use that to handle multi version code
216 lines
6.6 KiB
JavaScript
216 lines
6.6 KiB
JavaScript
const spiralloop = require('spiralloop')
|
|
|
|
const generations = require('flying-squid').generations
|
|
const { promisify } = require('util')
|
|
const fs = require('fs')
|
|
const { level } = require('prismarine-provider-anvil')
|
|
|
|
const fsStat = promisify(fs.stat)
|
|
const fsMkdir = promisify(fs.mkdir)
|
|
|
|
module.exports.server = async function (serv, {version, worldFolder, generation = {'name': 'diamond_square', 'options': {'worldHeight': 80}}} = {}) {
|
|
const World = require('prismarine-world')(version)
|
|
|
|
const newSeed = generation.options.seed || Math.floor(Math.random() * Math.pow(2, 31))
|
|
let seed
|
|
let regionFolder
|
|
if (worldFolder) {
|
|
regionFolder = worldFolder + '/region'
|
|
try {
|
|
await fsStat(regionFolder)
|
|
} catch (err) {
|
|
await fsMkdir(regionFolder)
|
|
}
|
|
|
|
try {
|
|
const levelData = await level.readLevel(worldFolder + '/level.dat')
|
|
seed = levelData['RandomSeed'][0]
|
|
} catch (err) {
|
|
seed = newSeed
|
|
await level.writeLevel(worldFolder + '/level.dat', {'RandomSeed': [seed, 0]})
|
|
}
|
|
} else { seed = newSeed }
|
|
generation.options.seed = seed
|
|
generation.options.version = version
|
|
serv.emit('seed', generation.options.seed)
|
|
const generationModule = generations[generation.name] ? generations[generation.name] : require(generation.name)
|
|
serv.overworld = new World(generationModule(generation.options), regionFolder)
|
|
serv.netherworld = new World(generations['nether'](generation.options))
|
|
// serv.endworld = new World(generations["end"]({}));
|
|
|
|
// WILL BE REMOVED WHEN ACTUALLY IMPLEMENTED
|
|
serv.overworld.blockEntityData = {}
|
|
serv.netherworld.blockEntityData = {}
|
|
serv.overworld.portals = []
|
|
serv.netherworld.portals = []
|
|
/// ///////////
|
|
|
|
serv.pregenWorld = (world, size = 3) => {
|
|
const promises = []
|
|
for (let x = -size; x < size; x++) {
|
|
for (let z = -size; z < size; z++) {
|
|
promises.push(world.getColumn(x, z))
|
|
}
|
|
}
|
|
return Promise.all(promises)
|
|
}
|
|
|
|
serv.setBlock = async (world, position, blockType, blockData) => {
|
|
serv.players
|
|
.filter(p => p.world === world)
|
|
.forEach(player => player.sendBlock(position, blockType, blockData))
|
|
|
|
await world.setBlockType(position, blockType)
|
|
await world.setBlockData(position, blockData)
|
|
}
|
|
|
|
serv.reloadChunks = (world, chunks) => {
|
|
serv.players
|
|
.filter(player => player.world === world)
|
|
.forEach(oPlayer => {
|
|
chunks
|
|
.filter(({chunkX, chunkZ}) => oPlayer.loadedChunks[chunkX + ',' + chunkZ] !== undefined)
|
|
.forEach(({chunkX, chunkZ}) => oPlayer.unloadChunk(chunkX, chunkZ))
|
|
oPlayer.sendRestMap()
|
|
})
|
|
}
|
|
|
|
// serv.pregenWorld(serv.overworld).then(() => serv.log('Pre-Generated Overworld'));
|
|
// serv.pregenWorld(serv.netherworld).then(() => serv.log('Pre-Generated Nether'));
|
|
}
|
|
|
|
module.exports.player = function (player, serv, settings) {
|
|
player.unloadChunk = (chunkX, chunkZ) => {
|
|
delete player.loadedChunks[chunkX + ',' + chunkZ]
|
|
|
|
if (serv.supportFeature('unloadChunkByEmptyChunk')) {
|
|
player._client.write('map_chunk', {
|
|
x: chunkX,
|
|
z: chunkZ,
|
|
groundUp: true,
|
|
bitMap: 0x0000,
|
|
chunkData: Buffer.alloc(0)
|
|
})
|
|
} else if (serv.supportFeature('unloadChunkDirect')) {
|
|
player._client.write('unload_chunk', {
|
|
chunkX,
|
|
chunkZ
|
|
})
|
|
}
|
|
}
|
|
|
|
player.sendChunk = (chunkX, chunkZ, column) => {
|
|
return player.behavior('sendChunk', {
|
|
x: chunkX,
|
|
z: chunkZ,
|
|
chunk: column
|
|
}, ({x, z, chunk}) => {
|
|
player._client.write('map_chunk', {
|
|
x: x,
|
|
z: z,
|
|
groundUp: true,
|
|
bitMap: 0xffff,
|
|
chunkData: chunk.dump(),
|
|
blockEntities: []
|
|
})
|
|
return Promise.resolve()
|
|
})
|
|
}
|
|
|
|
function spiral (arr) {
|
|
const t = []
|
|
spiralloop(arr, (x, z) => {
|
|
t.push([x, z])
|
|
})
|
|
return t
|
|
}
|
|
|
|
player.sendNearbyChunks = (view, group) => {
|
|
player.lastPositionChunkUpdated = player.position
|
|
const playerChunkX = Math.floor(player.position.x / 16)
|
|
const playerChunkZ = Math.floor(player.position.z / 16)
|
|
|
|
Object.keys(player.loadedChunks)
|
|
.map((key) => key.split(',').map(a => parseInt(a)))
|
|
.filter(([x, z]) => Math.abs(x - playerChunkX) > view || Math.abs(z - playerChunkZ) > view)
|
|
.forEach(([x, z]) => player.unloadChunk(x, z))
|
|
|
|
return spiral([view * 2, view * 2])
|
|
.map(t => ({
|
|
chunkX: playerChunkX + t[0] - view,
|
|
chunkZ: playerChunkZ + t[1] - view
|
|
}))
|
|
.filter(({chunkX, chunkZ}) => {
|
|
const key = chunkX + ',' + chunkZ
|
|
const loaded = player.loadedChunks[key]
|
|
if (!loaded) player.loadedChunks[key] = 1
|
|
return !loaded
|
|
})
|
|
.reduce((acc, {chunkX, chunkZ}) => {
|
|
const p = acc
|
|
.then(() => player.world.getColumn(chunkX, chunkZ))
|
|
.then((column) => player.sendChunk(chunkX, chunkZ, column))
|
|
return group ? p.then(() => sleep(5)) : p
|
|
}
|
|
, Promise.resolve())
|
|
}
|
|
|
|
function sleep (ms = 0) {
|
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
}
|
|
|
|
player.sendMap = () => {
|
|
return player.sendNearbyChunks(Math.min(3, settings['view-distance']))
|
|
.catch((err) => setTimeout(() => { throw err }), 0)
|
|
}
|
|
|
|
player.sendRestMap = () => {
|
|
player.sendingChunks = true
|
|
player.sendNearbyChunks(Math.min(player.view, settings['view-distance']), true)
|
|
.then(() => { player.sendingChunks = false })
|
|
.catch((err) => setTimeout(() => { throw err }, 0))
|
|
}
|
|
|
|
player.sendSpawnPosition = () => {
|
|
player._client.write('spawn_position', {
|
|
'location': player.spawnPoint
|
|
})
|
|
}
|
|
|
|
player.changeWorld = async (world, opt) => {
|
|
if (player.world === world) return Promise.resolve()
|
|
opt = opt || {}
|
|
player.world = world
|
|
player.loadedChunks = {}
|
|
if (typeof opt.gamemode !== 'undefined') player.gameMode = opt.gamemode
|
|
player._client.write('respawn', {
|
|
dimension: opt.dimension || 0,
|
|
difficulty: opt.difficulty || serv.difficulty,
|
|
gamemode: opt.gamemode || player.gameMode,
|
|
levelType: 'default'
|
|
})
|
|
await player.findSpawnPoint()
|
|
player.position = player.spawnPoint
|
|
player.sendSpawnPosition()
|
|
player.updateAndSpawn()
|
|
|
|
await player.sendMap()
|
|
|
|
player.sendSelfPosition()
|
|
player.emit('change_world')
|
|
|
|
await player.waitPlayerLogin()
|
|
player.sendRestMap()
|
|
}
|
|
|
|
player.commands.add({
|
|
base: 'changeworld',
|
|
info: 'to change world',
|
|
usage: '/changeworld overworld|nether',
|
|
op: true,
|
|
action (world) {
|
|
if (world === 'nether') player.changeWorld(serv.netherworld, {dimension: -1})
|
|
if (world === 'overworld') player.changeWorld(serv.overworld, {dimension: 0})
|
|
}
|
|
})
|
|
}
|