UNPKG

voxel-async-simulation

Version:

Load voxel.js chunks asynchronously from a server

376 lines (348 loc) 12.7 kB
var createGame = require('voxel-engine') var highlight = require('voxel-highlight') var player = require('voxel-player') var voxel = require('voxel'); var extend = require('extend') var fly = require('voxel-fly') var walk = require('voxel-walk'); var createWeather = require('voxel-weather'); var Generator = require('voxel-generators'); var uuid = require('uuid'); var isElectron = require('is-electron'); //var random = require('seedable-random'); //var skmeans = require("skmeans"); var Emitter = require("extended-emitter"); var Generators = require("voxel-generators"); function handlePart(index, part, list){ list.filter(function(item){ return item.indexOf('_'+part) !== -1; }).forEach(function(item){ var name = item.substring(item.indexOf('_'+part)); if(!index[name])byName[name] = {}; index[name][part] = item; }); } var fs; VoxelSimulation.lookupMaterials = function(texturePack, cb){ fs.readdir( process.cwd()+ '/texture-packs/'+ texturePack+ '/assets/minecraft/textures/blocks', function(err, list){ var byName = {}; handlePart(byName, 'side', list); handlePart(byName, 'end', list); handlePart(byName, 'top', list); handlePart(byName, 'bottom', list); list.filter(function(item){ return item.indexOf('_side') === -1 && item.indexOf('_end') === -1 && item.indexOf('_top') === -1 && item.indexOf('_bottom') === -1 ; }).forEach(function(item){ if(!index[name])byName[name] = {}; index[name].main = item; }); var result = Object.keys(byName).map(function(name){ return byName[name]; }).sort(function(a, b){ return Object.keys(a).length >= Object.keys(b).length; }); cb(undefined, result); } ); } VoxelSimulation.setMaterials = function(materials){ VoxelSimulation.lookupMaterials = function(texturePack, cb){ setTimeout(function(){ cb(undefined, materials); }, 1); } }; function VoxelSimulation(options){ var ob = this; this.options = options || {}; (new Emitter()).onto(this); var domain = options.domain || '.'; if(!this.options.seed) this.options.seed = uuid.v1(); //if(!this.options.texturePack) throw Error() this.lookupMaterials = this.options.lookupMaterials || VoxelSimulation.lookupMaterials; options.texturePath = domain+'/texture-packs/'+ this.options.texturePack+ '/assets/minecraft/textures/blocks/'; if(!options.delayedBuild) this.build(function(){ }); //SHAMEFUL INPUT HACK this.mouseDown = [0, 0, 0, 0, 0, 0, 0, 0, 0], this.mouseDownCount = 0; ob = this; document.body.onmousedown = function(evt) { ++ob.mouseDown[evt.button]; ++ob.mouseDownCount; } document.body.onmouseup = function(evt) { --ob.mouseDown[evt.button]; --ob.mouseDownCount; } }; VoxelSimulation.prototype.simulateMouseButton = function(button){ if(button === false){ this.mouseDown = this.mouseDown.map(function(item){ return 0; }) }else{ this.mouseDown[button] = 1; } }; VoxelSimulation.prototype.destroy = function(cb){ setTimeout(function(){ if(cb) cb(); }, 0); }; VoxelSimulation.prototype.build = function(cb){ var ob = this; var options = ob.options; this.emit('load-start'); this.createWorld(this.options, function(err, world){ ob.emit('load-complete'); ob.world = world; if(!ob.options.randomSeed) ob.options.randomSeed = uuid.v4(); if(!ob.options.graphicsQuality) ob.options.graphicsQuality = 3; ob.player = player(world)(ob.options.playerSkin || 'player.png'); ob.player.possess(); ob.player.yaw.position.set(2, 14, 4); var dirtyChunks = []; world.on('dirtyChunkUpdate', function(chunk){ if(dirtyChunks.indexOf(chunk) === -1) dirtyChunks.push(chunk); }) // timer outside game timings, debounced a little // current players will get stream updates, // joining players might get out of sync (mb event on file change?) setInterval(function(){ if(options.chunkSaver && dirtyChunks.length){ options.chunkSaver(dirtyChunks.shift(), function(err){ if(err) console.log('ERROR SAVING', err); console.log('SAVED CHUNK'); }); } }, 200); world.on('chunk-loaded', function(item){ ob.emit('chunk-loaded', item); }); var makeFly = fly(world) var target = world.controls.target() world.flyer = makeFly(target); //todo: investigate why this flag is needed var loaded; ob.once('chunk-loaded', { key:'0|0|0' }, function(chunk){ if(!loaded){ setTimeout(function(){ if(cb) cb(undefined, target, world, ob.options); }, 2000); loaded = true; } }); //attach view var container = options.container || document.body; world.appendTo(container) if (world.notCapable()) return; //setup var showSky = (options.quality && options.quality > 1); var showWeather = (options.quality && options.quality > 2); var weather = createWeather(world, showSky, showWeather); weather(ob.options.weatherCycle || [ 'clear', 'cloudy', 'sprinkle', 'rain', 'stormy', 'rain', 'sprinkle', 'cloudy', 'clear', 'clear', 'clear', 'clear' ]); // highlight blocks when you look at them, hold <Ctrl> for block placement var blockPosPlace, blockPosErase var lastMode; var hl = world.highlighter = highlight(world, { color: options.highlightColor || 0xff0000, selectActive: function(){ //return !!ob.placing; return ob.mouseDown[1]; }, adjacentActive: function(){ //return !!ob.placing; return ob.mouseDown[0]; } }) ob.hl = hl; hl.on('highlight', function (voxelPos) { blockPosErase = voxelPos }); hl.on('remove', function (voxelPos) { blockPosErase = null }); hl.on('highlight-adjacent', function(voxelPos){ blockPosPlace = voxelPos }); hl.on('remove-adjacent', function(voxelPos){ blockPosPlace = null }); world.on('fire', function(target, state) { setTimeout(function(){ var position = blockPosPlace; if(ob.mouseDown[2]){ world.emit('destroy-voxel', blockPosErase); } if(ob.mouseDown[1]){ world.emit('inspect-voxel', blockPosErase); } if(ob.mouseDown[0]){ world.emit('empty-voxel', blockPosPlace); } }, 100); }); }); }; //Sphere, Noise, DenseNoise, Checker, Hill, Valley, HillyTerrain VoxelSimulation.Generators = Generators; VoxelSimulation.prototype.deselect = function(){ this.hl.selectStart = null } VoxelSimulation.prototype.createWorld = function(options, cb){ console.log('OPT', options) var ob = this; var chunkCache = {}; this.initizationOptions(function(err, initOptions){ var chunkDistance = options.quality?Math.max(options.quality, 2): (options.chunkDistance || 2); var gameOptions = { generateVoxelChunk: function(low, high, x, y, z) { var key = [x,y,z].join('|'); var chunk = chunkCache[key]; if (!chunk) { chunk = { position:[x,y,z], voxels:new Int8Array(32*32*32), dims:[32,32,32], empty:true }; chunkCache[key] = chunk; if(options.chunkLoader) options.chunkLoader(chunk, function(err, result){ result.key = key; result.position = [x,y,z]; ob.emit('recieve-chunk', result); }); } return chunk; }, materials: initOptions.materials, chunkDistance: chunkDistance, texturePath: options.texturePath, materialFlatColor: !(options.useTextures || options.texturePath), worldOrigin: options.origin || [0, 0, 0], fogDisabled: (options.quality && options.quality > 2)?false:true, skyColor: options.skyColor|| 0x6666CC, lightsDisabled: (options.quality && options.quality == 1)?false:true, controls: { discreteFire: true } } console.log('CREATE WORLD', gameOptions); var game = createGame(gameOptions); ob.on('recieve-chunk', function(chunk){ chunkCache[chunk.key] = { //overwrite the old, empty dummy position: chunk.position, voxels: chunk.blocks, dims: [32,32,32] }; game.voxels.emit( 'missingChunk', chunk.position ); (function(){ ob.emit('chunk-loaded', chunk); })() }) cb(undefined, game); }); }; function comparable(a, o){ var r = o.indexOf(a); if(r === -1) return r; return a.length - r; } VoxelSimulation.prototype.initizationOptions = function(cb){ var ob = this; if(ob.initOpts){ setTimeout(function(){ cb(undefined, ob.initOpts); }, 1); }else{ ob.lookupMaterials( ob.options.texturePack || 'freeture', function(err, materials){ if(!materials.length) (console.warn || console.log)('No materials recieved') ob.initOpts = {}; ob.initOpts.materials = materials; cb(undefined, ob.initOpts); } ); } }; VoxelSimulation.Client = function(options){ if(!options) options = {}; console.log('@@', options); var request = require('browser-request'); var thisSim; var domain = options.domain || '.'; if(!options.chunkLoader) options.chunkLoader = function(placeholderChunk, complete){ var url = domain+'/chunk/'+thisSim.options.seed+'/'+ placeholderChunk.position[0]+'/'+ placeholderChunk.position[1]+'/'+ placeholderChunk.position[2]; request({ uri :url, json : true }, function(err, response, data){ if(err) throw(err); if(data.error) throw(new Error(data.message || ( typeof data.error == 'boolean'? 'Error requesting url:'+url: data.error ))); var results = new Int8Array(32*32*32); var blocks = data.blocks || data.voxels; for(var lcv=0; lcv<results.length; lcv++){ results[lcv] = blocks[lcv]; } data.blocks = results; complete(undefined, data); }) }; if(options.save === true && (!options.chunkSaver)) options.chunkSaver = function(chunk, complete){ var url = domain+'/chunk/'+thisSim.options.seed+'/'+chunk.position[0]+'/'+ chunk.position[1]+'/'+chunk.position[2]; request({ uri :url, method: 'post', json : chunk }, function(err, response, data){ if(err) console.log('Could not save chunk:', err); if(!(data && data.success)) console.log('Failed to save chunk'); complete(err); }) }; if(!options.lookupMaterials) options.lookupMaterials = function(texturePack, cb){ var url = domain+'/assets/'+texturePack+'/blocks'; request({ uri :url, json : true }, function(err, response, data){ if(err) return cb(err); if(data){ return cb(undefined, data); } cb(undefined, undefined); }) }; thisSim = new VoxelSimulation(options); return thisSim; } module.exports = VoxelSimulation;