diff --git a/package.json b/package.json index ad7e743..f1808f8 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "prismarine-block": "0.1.0", "prismarine-chunk": "0.2.1", "prismarine-entity": "0.1.0", - "prismarine-world": "0.3.1", + "prismarine-world": "0.3.3", "prismarine-world-sync": "0.1.0", "random-seed": "^0.2.0", "request-promise": "^0.4.3", diff --git a/src/lib/playerPlugins/commands.js b/src/lib/playerPlugins/commands.js index 9033db0..81924dc 100644 --- a/src/lib/playerPlugins/commands.js +++ b/src/lib/playerPlugins/commands.js @@ -307,6 +307,24 @@ function inject(serv, player) { } }); + base.add({ + base: 'spawn', + info: 'Spawn an entity', + usage: '/spawn ', + parse(str) { + var results=str.match(/(\d+)/); + if (!results) return false; + return { + id: parseInt(results[1]) + } + }, + action({id}) { + serv.spawnMob(id, player.world, player.entity.position.scaled(1/32), { + velocity: Vec3((Math.random() - 0.5) * 10, Math.random()*10 + 10, (Math.random() - 0.5) * 10) + }); + } + }) + serv.commands = base; player.handleCommand = (str) => { diff --git a/src/lib/playerPlugins/communication.js b/src/lib/playerPlugins/communication.js index 430f5fb..0138f41 100644 --- a/src/lib/playerPlugins/communication.js +++ b/src/lib/playerPlugins/communication.js @@ -8,15 +8,17 @@ function inject(serv,player) .forEach((otherPlayer) => otherPlayer._client.write(packetName, packetFields)); player._writeOthersNearby = (packetName, packetFields) => - serv._writeArray(packetName, packetFields, player.nearbyPlayers); + serv._writeArray(packetName, packetFields, player.nearbyPlayers()); player.getOthers = () => serv.players.filter((otherPlayer) => otherPlayer != player); - player.getNearby = () => serv - .getNearby({ - world: player.world, - position: player.entity.position, - radius: player.playerViewDistance*32 - }) - .filter((p) => p != player); + player.getNearbyPlayers = (radius=player.entity.viewDistance*32) => serv.getNearby({ + world: player.world, + position: player.position, + radius: radius + }); + + player.nearbyPlayers = (radius=player.entity.viewDistance*32) => player.entity.nearbyEntities + .filter(e => e.type == 'player') + .map(e => e.player); } \ No newline at end of file diff --git a/src/lib/playerPlugins/digging.js b/src/lib/playerPlugins/digging.js index 56aa35a..e022820 100644 --- a/src/lib/playerPlugins/digging.js +++ b/src/lib/playerPlugins/digging.js @@ -70,12 +70,19 @@ function inject(serv,player) }); } - function completeDigging(location) + async function completeDigging(location) { clearInterval(animationInterval); var diggingTime=new Date()-startDiggingTime; - if(expectedDiggingTime-diggingTime<100) + if(expectedDiggingTime-diggingTime<100) { player.changeBlock(location,0,0); + // Drop block + serv.spawnObject(2, player.world, location.offset(0.5, 0.5, 0.5), { + velocity: Vec3(Math.random()*4 - 2, Math.random()*2 + 2, Math.random()*4 - 2), + itemId: currentlyDugBlock.type, + itemDamage: currentlyDugBlock.metadata + }); + } else { player._client.write("block_change",{ diff --git a/src/lib/playerPlugins/login.js b/src/lib/playerPlugins/login.js index 626440b..8f24092 100644 --- a/src/lib/playerPlugins/login.js +++ b/src/lib/playerPlugins/login.js @@ -7,46 +7,25 @@ function inject(serv,player) { function addPlayer() { - serv.entityMaxId++; - player.entity=new Entity(serv.entityMaxId); - serv.entities[player.entity.id]=player.entity; + player.entity=serv.initEntity('player', null, serv.overworld, Vec3(0,0,0)); + player.entity.type = 'player'; player.entity.player=player; player.entity.health = 20; player.entity.food = 20; player.entity.crouching = false; // Needs added in prismarine-entity later - player.playerViewDistance = 150; player.view=10; player.world=serv.overworld; player.username=player._client.username; serv.players.push(player); serv.uuidToPlayer[player._client.uuid] = player; player.loadedChunks={}; - player.nearbyPlayers=[]; } - player.updateAndSpawnNearbyPlayers = () => - { - player.lastPositionPlayersUpdated=player.entity.position; - var updatedPlayers=player.getNearby(); - var playersToAdd=updatedPlayers.filter(p => player.nearbyPlayers.indexOf(p)==-1); - var playersToRemove=player.nearbyPlayers.filter(p => updatedPlayers.indexOf(p)==-1); - player.despawnPlayers(playersToRemove); - playersToAdd.forEach(player.spawnAPlayer); - - playersToRemove.forEach(p => p.despawnPlayers([player])); - playersToRemove.forEach(p => p.nearbyPlayers=p.getNearby()); - playersToAdd.forEach(p => p.spawnAPlayer(player)); - playersToAdd.forEach(p => p.nearbyPlayers=p.getNearby()); - - player.nearbyPlayers=updatedPlayers; - - }; - function sendPlayersWhenMove() { player.on("positionChanged",() => { if(player.entity.position.distanceTo(player.lastPositionPlayersUpdated)>2*32) - player.updateAndSpawnNearbyPlayers(); + player.entity.updateAndSpawn(); }); } @@ -173,7 +152,7 @@ function inject(serv,player) updateTime(); fillTabList(); - player.updateAndSpawnNearbyPlayers(); + player.entity.updateAndSpawn(); announceJoin(); player.emit("spawned"); diff --git a/src/lib/playerPlugins/logout.js b/src/lib/playerPlugins/logout.js index 5ac9a68..99b6e6b 100644 --- a/src/lib/playerPlugins/logout.js +++ b/src/lib/playerPlugins/logout.js @@ -8,6 +8,10 @@ function inject(serv,player) }); }; + player.despawnEntities = entities => player._client.write('entity_destroy', { + 'entityIds': entities.map(e => e.id) + }); + player._client.on('end', () => { if(player.entity) { serv.broadcast(player.username + ' quit the game.', "yellow"); @@ -17,7 +21,7 @@ function inject(serv,player) UUID: player._client.uuid }] }); - player.nearbyPlayers.forEach(otherPlayer => otherPlayer.despawnPlayers([player])); + player.nearbyPlayers().forEach(otherPlayer => otherPlayer.despawnPlayers([player])); delete serv.entities[player.entity.id]; player.emit('disconnected'); var index = serv.players.indexOf(player); diff --git a/src/lib/playerPlugins/placeBlock.js b/src/lib/playerPlugins/placeBlock.js index 7f7ec42..26ed2e6 100644 --- a/src/lib/playerPlugins/placeBlock.js +++ b/src/lib/playerPlugins/placeBlock.js @@ -1,5 +1,16 @@ +var blocks=require("minecraft-data")(require("../version")).blocks; var vec3 = require("vec3"); +var materialToSound = { + undefined: 'stone', + 'rock': 'stone', + 'dirt': 'grass', + 'plant': 'grass', + 'wool': 'cloth', + 'web': 'cloth', + 'wood': 'wood' +} + module.exports=inject; function inject(serv,player) @@ -11,7 +22,10 @@ function inject(serv,player) var placedPosition=referencePosition.plus(directionVector); player.world.getBlockType(referencePosition).then((id) => { if([25].indexOf(id) != -1) return; - serv.playSound('random.click', player.world, placedPosition.clone().add(vec3(0.5, 0.5, 0.5))); + var sound = 'dig.' + (materialToSound[blocks[heldItem.blockId].material] || 'stone'); + serv.playSound(sound, player.world, placedPosition.clone().add(vec3(0.5, 0.5, 0.5)), { + pitch: 0.8 + }); if(heldItem.blockId!=323){ player.changeBlock(placedPosition,heldItem.blockId,heldItem.itemDamage); }else if(direction==1){ diff --git a/src/lib/playerPlugins/pvp.js b/src/lib/playerPlugins/pvp.js index aae4132..01c875b 100644 --- a/src/lib/playerPlugins/pvp.js +++ b/src/lib/playerPlugins/pvp.js @@ -14,8 +14,9 @@ function inject(serv, player) function attackEntity(entityId) { + if (!serv.entities[entityId]) return; // ????? var attackedPlayer = serv.entities[entityId].player; - if(attackedPlayer.gameMode!=0) return; + if(!attackedPlayer || attackedPlayer.gameMode!=0) return; attackedPlayer.updateHealth(attackedPlayer.entity.health - 1); serv.playSound('game.player.hurt', player.world, attackedPlayer.entity.position.scaled(1/32)); diff --git a/src/lib/playerPlugins/updatePositions.js b/src/lib/playerPlugins/updatePositions.js index f0158e5..d9c6bb5 100644 --- a/src/lib/playerPlugins/updatePositions.js +++ b/src/lib/playerPlugins/updatePositions.js @@ -12,10 +12,10 @@ function inject(serv,player) // float (degrees) --> byte (1/256 "degrees") function conv(f){ - var b = (f % 360) * 256 / 360; + var b = Math.floor((f % 360) * 256 / 360); if (b < -128) b += 256; else if (b > 127) b -= 256; - return Math.floor(b); + return b; } function sendLook(yaw,pitch,onGround) { diff --git a/src/lib/playerPlugins/world.js b/src/lib/playerPlugins/world.js index 6944826..73e5c51 100644 --- a/src/lib/playerPlugins/world.js +++ b/src/lib/playerPlugins/world.js @@ -5,18 +5,18 @@ module.exports = inject; function inject(serv, player) { - player.spawnAPlayer = spawnedPlayer => { - player._client.write('named_entity_spawn', { - entityId: spawnedPlayer.entity.id, - playerUUID: spawnedPlayer._client.uuid, - x: spawnedPlayer.entity.position.x, - y: spawnedPlayer.entity.position.y, - z: spawnedPlayer.entity.position.z, - yaw: spawnedPlayer.entity.yaw, - pitch: spawnedPlayer.entity.pitch, - currentItem: 0, - metadata: spawnedPlayer.entity.metadata - }); + player.spawnEntity = entity => { + player._client.write(entity.spawnPacketName, entity.getSpawnPacket()); + if (typeof entity.itemId != 'undefined') { + entity.setMetadata([{ + "key": 10, + "type": 5, + "value": { + blockId: entity.itemId, + itemDamage: entity.itemDamage + } + }]); + } }; player.sendChunk = (chunkX,chunkZ,column) => @@ -96,6 +96,7 @@ function inject(serv, player) { if(player.world == world) return Promise.resolve(); opt = opt || {}; player.world = world; + player.entity.world = world; player.loadedChunks={}; if (typeof opt.gamemode != 'undefined') player.gameMode = opt.gamemode; player._client.write("respawn",{ @@ -106,7 +107,7 @@ function inject(serv, player) { }); player.entity.position=player.spawnPoint.toFixedPosition(); player.sendSpawnPosition(); - player.updateAndSpawnNearbyPlayers(); + player.entity.updateAndSpawn(); await player.sendMap(); diff --git a/src/lib/serverPlugins/communication.js b/src/lib/serverPlugins/communication.js index efd1e08..e58f414 100644 --- a/src/lib/serverPlugins/communication.js +++ b/src/lib/serverPlugins/communication.js @@ -11,8 +11,15 @@ function inject(serv,settings) serv._writeNearby= (packetName, packetFields, loc) => serv._writeArray(packetName, packetFields, serv.getNearby(loc)); - serv.getNearby= loc => serv.players.filter( player => - player.world == loc.world && - player.entity.position.distanceTo(loc.position) <= loc.radius + serv.getNearby= ({world,position,radius=8*16*32}) => serv.players.filter( player => + player.world == world && + player.entity.position.distanceTo(position) <= radius + ); + + serv.getNearbyEntities= ({world,position,radius=8*16*32}) => Object.keys(serv.entities) + .map(eId => serv.entities[eId]) + .filter(entity => + entity.world == world && + entity.position.distanceTo(position) <= radius ); } \ No newline at end of file diff --git a/src/lib/serverPlugins/daycycle.js b/src/lib/serverPlugins/daycycle.js index 3f5788b..e5606fd 100644 --- a/src/lib/serverPlugins/daycycle.js +++ b/src/lib/serverPlugins/daycycle.js @@ -14,7 +14,7 @@ function inject(serv, settings) { serv.time = 0; - serv.on('tick', (count) => { + serv.on('tick', (delta,count) => { if (!serv.doDaylightCycle) return; if (count % 20 == 0) { serv.setTime((serv.time + 20) % 24000); // Vanilla only does it every second diff --git a/src/lib/serverPlugins/entities.js b/src/lib/serverPlugins/entities.js new file mode 100644 index 0000000..74f8e9d --- /dev/null +++ b/src/lib/serverPlugins/entities.js @@ -0,0 +1,280 @@ +var Entity=require("prismarine-entity"); +var blocks=require("minecraft-data")(require("../version")).blocks; +var mobs=require("minecraft-data")(require("../version")).entitiesByName; +var vec3 = require("vec3"); +var EventEmitter = require('events').EventEmitter; +var util = require('util'); + +module.exports = inject; + +function inject(serv) { + + util.inherits(Entity, EventEmitter); + + serv.initEntity = (type, entityType, world, position) => { + serv.entityMaxId++; + var entity = new Entity(serv.entityMaxId); + EventEmitter.call(entity); + entity.type = type; + entity.spawnPacketName = ''; + entity.entityType = entityType; + entity.world = world; + entity.position = position; + entity.lastPositionPlayersUpdated = entity.position.clone(); + entity.nearbyEntities = []; + entity.viewDistance = 150; + + entity.bornTime = Date.now(); + serv.entities[entity.id] = entity; + + if (entity.type == 'player') entity.spawnPacketName = 'named_entity_spawn'; + else if (entity.type == 'object') entity.spawnPacketName = 'spawn_entity'; + else if (entity.type == 'mob') entity.spawnPacketName = 'spawn_entity_living'; + + entity.on("positionChanged",() => { + if(entity.position.distanceTo(entity.lastPositionPlayersUpdated)>2*32) + entity.updateAndSpawn(); + }); + + entity.setMetadata = (data) => { + serv._writeNearby('entity_metadata', { + entityId: entity.id, + metadata: data + }, entity); + }; + + entity.destroy = () => { + serv.destroyEntity(entity); + }; + + entity.calculatePhysics = async (delta) => { + if (entity.gravity) { + addGravity(entity, 'x', delta); + addGravity(entity, 'y', delta); + addGravity(entity, 'z', delta); + } + + var vSign = getSign(entity.velocity); + var sizeSigned = vec3(vSign.x * entity.size.x, vSign.y * entity.size.y, vSign.z * entity.size.z); + + var xVec = entity.position.offset(entity.velocity.x*delta + sizeSigned.x/2, 0, 0).scaled(1/32).floored(); + var yVec = entity.position.offset(0, entity.velocity.y*delta + sizeSigned.y/2, 0).scaled(1/32).floored(); + var zVec = entity.position.offset(0, 0, entity.velocity.z*delta + sizeSigned.z/2).scaled(1/32).floored(); + + // Get block for each (x/y/z)Vec, check to avoid duplicate getBlockTypes + var xBlock = blocks[await entity.world.getBlockType(xVec)].boundingBox == 'block'; + var yBlock = yVec.equals(xVec) ? xBlock : blocks[await entity.world.getBlockType(yVec)].boundingBox == 'block'; + var zBlock = zVec.equals(yVec) ? yBlock : (zVec.equals(xVec) ? xBlock : blocks[await entity.world.getBlockType(zVec)].boundingBox == 'block'); + + var old = entity.position.clone(); + + if (xBlock || yBlock || zBlock) { + entity.velocity.x = getFriction(entity.velocity.x, entity.friction.x, delta); + entity.velocity.z = getFriction(entity.velocity.x, entity.friction.x, delta); + } + + var oldPos = entity.position.clone(); + + entity.position.x += getMoveAmount('x', xBlock, entity, delta, sizeSigned.x); + entity.position.y += getMoveAmount('y', yBlock, entity, delta, sizeSigned.y); + entity.position.z += getMoveAmount('z', zBlock, entity, delta, sizeSigned.z); + + //serv.emitParticle(30, serv.overworld, entity.position.scaled(1/32), { size: vec3(0, 0, 0) }); + return { oldPos: oldPos, onGround: yBlock} + }; + + entity.sendPosition = ({oldPos,onGround}) => { + var diff = entity.position.minus(oldPos); + + if(diff.abs().x>127 || diff.abs().y>127 || diff.abs().z>127) + serv._writeNearby('entity_teleport', { + entityId: entity.id, + x: entity.position.x, + y: entity.position.y, + z: entity.position.z, + yaw: entity.yaw, + pitch: entity.pitch, + onGround: onGround + }, entity); + else serv._writeNearby('rel_entity_move', { + entityId: entity.id, + dX: diff.x, + dY: diff.y, + dZ: diff.z, + onGround: onGround + }, entity); + + entity.emit('positionChanged', oldPos); + } + + entity.getSpawnPacket = () => { + var scaledVelocity = entity.velocity.scaled(8000/32/20).floored(); // from fixed-position/second to unit => 1/8000 blocks per tick + if (entity.type == 'player') { + return { + entityId: entity.id, + playerUUID: entity.player._client.uuid, + x: entity.position.x, + y: entity.position.y, + z: entity.position.z, + yaw: entity.yaw, + pitch: entity.pitch, + currentItem: 0, + metadata: entity.metadata + } + } else if (entity.type == 'object') { + return { + entityId: entity.id, + type: entity.entityType, + x: entity.position.x, + y: entity.position.y, + z: entity.position.z, + pitch: entity.pitch, + yaw: entity.yaw, + objectData: { + intField: entity.data, + velocityX: scaledVelocity.x, + velocityY: scaledVelocity.y, + velocityZ: scaledVelocity.z + } + } + } else if (entity.type == 'mob') { + return { + entityId: entity.id, + type: entity.entityType, + x: entity.position.x, + y: entity.position.y, + z: entity.position.z, + yaw: entity.yaw, + pitch: entity.pitch, + headPitch: entity.headPitch, + velocityX: scaledVelocity.x, + velocityY: scaledVelocity.y, + velocityZ: scaledVelocity.z, + metadata: entity.metadata + } + } + }; + + entity.getNearby = () => serv + .getNearbyEntities({ + world: entity.world, + position: entity.position, + radius: entity.viewDistance*32 + }) + .filter((e) => e != entity); + + entity.updateAndSpawn = () => { + var updatedEntities=entity.getNearby(); + var entitiesToAdd=updatedEntities.filter(e => entity.nearbyEntities.indexOf(e)==-1); + var entitiesToRemove=entity.nearbyEntities.filter(e => updatedEntities.indexOf(e)==-1); + if (entity.type == 'player') { + entity.player.despawnEntities(entitiesToRemove); + entitiesToAdd.forEach(entity.player.spawnEntity); + entity.player.lastPositionPlayersUpdated=entity.position.clone(); + } else { + entity.lastPositionPlayersUpdated=entity.position.clone(); + } + + var playersToAdd = entitiesToAdd.filter(e => e.type == 'player').map(e => e.player); + var playersToRemove = entitiesToRemove.filter(e => e.type == 'player').map(e => e.player); + + playersToRemove.forEach(p => p.despawnEntities([entity])); + playersToRemove.forEach(p => p.entity.nearbyEntities=p.entity.getNearby()); + playersToAdd.forEach(p => p.spawnEntity(entity)); + playersToAdd.forEach(p => p.entity.nearbyEntities=p.entity.getNearby()); + + entity.nearbyEntities=updatedEntities; + }; + + return entity; + } + + serv.spawnObject = (type, world, position, {pitch=0,yaw=0,velocity=vec3(0,0,0),data=1,itemId,itemDamage=0}={}) => { + var object = serv.initEntity('object', type, world, position.scaled(32).floored()); + object.data = data; + object.velocity = velocity.scaled(32).floored(); + object.pitch = pitch; + object.yaw = yaw; + object.gravity = vec3(0, -20*32, 0); + object.terminalvelocity = vec3(27*32, 27*32, 27*32); + object.friction = vec3(10*32, 0, 10*32).floored(); + object.size = vec3(0.25*32, 0.25*32, 0.25*32); // Hardcoded, will be dependent on type! + object.deathTime = 60*1000; // 60 seconds + object.itemId = itemId; + object.itemDamage = itemDamage; + + object.updateAndSpawn(); + } + + serv.spawnMob = (type, world, position, {pitch=0,yaw=0,headPitch=0,velocity=vec3(0,0,0),metadata=[]}={}) => { + var mob = serv.initEntity('mob', type, world, position.scaled(32).floored()); + mob.velocity = velocity.scaled(32).floored(); + mob.pitch = pitch; + mob.headPitch = headPitch; + mob.yaw = yaw; + mob.gravity = vec3(0, -20*32, 0); + mob.terminalvelocity = vec3(27*32, 27*32, 27*32); + mob.friction = vec3(10*32, 0, 10*32); + mob.size = vec3(0.75, 1.75, 0.75); + mob.metadata = metadata; + + mob.updateAndSpawn(); + } + + serv.on('tick', function(delta) { + Promise.all( + Object.keys(serv.entities).map(async (id) => { + var entity = serv.entities[id]; + if (entity.deathTime && Date.now() - entity.bornTime >= entity.deathTime) { + entity.destroy(); + return; + } + if (!entity.velocity || !entity.size) return; + var oldPosAndOnGround = await entity.calculatePhysics(delta); + if (!oldPosAndOnGround.oldPos.equals(vec3(0,0,0))) + if (entity.type == 'mob') entity.sendPosition(oldPosAndOnGround); + }) + ).catch((err)=> setTimeout(() => {throw err;},0)); + }); + + serv.destroyEntity = entity => { + serv._writeNearby('entity_destroy', { + entityIds: [entity.id] + }, { + position: entity.position, + world: entity.world + }); + delete serv.entities[entity.id]; + } +} + +function getMoveAmount(dir, block, entity, delta, sizeSigned) { + if (block) { + entity.velocity[dir] = 0; + return Math.floor(-1 * (entity.position[dir] + sizeSigned/2 - floorInDirection(entity.position[dir], -sizeSigned))); + } else { + return Math.floor(entity.velocity[dir] * delta); + } +} + +function getSign(vec) { + return vec3(Math.sign(vec.x), Math.sign(vec.y), Math.sign(vec.z)); +} + +function floorInDirection(a, b) { + return b < 0 ? Math.floor(a) : Math.ceil(a); +} + +function addGravity(entity, dir, delta) { + if (entity.velocity[dir] < entity.terminalvelocity[dir] && entity.velocity[dir] > -entity.terminalvelocity[dir]) { + entity.velocity[dir] = clamp(-entity.terminalvelocity[dir], entity.velocity[dir] + entity.gravity[dir] * delta, entity.terminalvelocity[dir]); + } +} + +function getFriction(vel, fric, delta) { + return vel > 0 ? Math.max(0, vel - fric*delta) : Math.min(0, vel + fric*delta); +} + +function clamp(a, b, c) { + return Math.max(a, Math.min(b, c)); +} \ No newline at end of file diff --git a/src/lib/serverPlugins/tick.js b/src/lib/serverPlugins/tick.js index 04e95ed..5ec0a6d 100644 --- a/src/lib/serverPlugins/tick.js +++ b/src/lib/serverPlugins/tick.js @@ -3,6 +3,7 @@ module.exports = inject; function inject(serv, settings) { serv.tickCount = 0; + serv.lastTickTime = 0; serv.setTickInterval = ticksPerSecond => { @@ -10,7 +11,10 @@ function inject(serv, settings) { serv.tickInterval = setInterval(() => { serv.tickCount++; - serv.emit('tick', serv.tickCount); + var time = (Date.now() - serv.lastTickTime) / 1000; + if (time > 100) time = 0; + serv.emit('tick', time, serv.tickCount); + serv.lastTickTime = Date.now(); }, 1000/ticksPerSecond); };