const Entity = require('prismarine-entity') const path = require('path') const requireIndex = require('../requireindex') const plugins = requireIndex(path.join(__dirname, '..', 'plugins')) const UserError = require('flying-squid').UserError const UUID = require('uuid-1345') const Vec3 = require('vec3').Vec3 module.exports.server = function (serv, options) { const version = options.version const mobsById = require('minecraft-data')(version).mobs const objectsById = require('minecraft-data')(version).objects serv.initEntity = (type, entityType, world, position) => { if (Object.keys(serv.entities).length > options['max-entities']) { throw new Error('Too many mobs !') } serv.entityMaxId++ const entity = new Entity(serv.entityMaxId) Object.keys(plugins) .filter(pluginName => plugins[pluginName].entity !== undefined) .forEach(pluginName => plugins[pluginName].entity(entity, serv, options)) entity.initEntity(type, entityType, world, position) serv.emit('newEntity', entity) return entity } serv.spawnObject = (type, world, position, {pitch = 0, yaw = 0, velocity = new Vec3(0, 0, 0), data = 1, itemId, itemDamage = 0, pickupTime = undefined, deathTime = undefined}) => { const object = serv.initEntity('object', type, world, position) object.uuid = UUID.v4() object.name = objectsById[type].name object.data = data object.velocity = velocity object.pitch = pitch object.yaw = yaw object.gravity = new Vec3(0, -20, 0) object.terminalvelocity = new Vec3(27, 27, 27) object.friction = new Vec3(15, 0, 15) object.size = new Vec3(0.25, 0.25, 0.25) // Hardcoded, will be dependent on type! object.deathTime = deathTime object.pickupTime = pickupTime object.itemId = itemId object.itemDamage = itemDamage object.updateAndSpawn() } serv.spawnMob = (type, world, position, {pitch = 0, yaw = 0, headPitch = 0, velocity = new Vec3(0, 0, 0), metadata = []} = {}) => { const mob = serv.initEntity('mob', type, world, position) mob.uuid = UUID.v4() mob.name = mobsById[type].name mob.velocity = velocity mob.pitch = pitch mob.headPitch = headPitch mob.yaw = yaw mob.gravity = new Vec3(0, -20, 0) mob.terminalvelocity = new Vec3(27, 27, 27) mob.friction = new Vec3(15, 0, 15) mob.size = new Vec3(0.75, 1.75, 0.75) mob.health = 20 mob.metadata = metadata mob.updateAndSpawn() return mob } serv.destroyEntity = entity => { entity._writeOthersNearby('entity_destroy', { entityIds: [entity.id] }) delete serv.entities[entity.id] } } module.exports.player = function (player, serv, options) { const version = options.version const entitiesByName = require('minecraft-data')(version).entitiesByName const Item = require('prismarine-item')(version) player.commands.add({ base: 'summon', info: 'Summon an entity', usage: '/summon ', op: true, action (name) { if (Object.keys(serv.entities).length > options['max-entities']) { throw new UserError('Too many mobs !') } const entity = entitiesByName[name] if (!entity) { player.chat('No entity named ' + name) return } if (entity.type === 'mob') { serv.spawnMob(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } else if (entity.type === 'object') { serv.spawnObject(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } } }) player.commands.add({ base: 'summonMany', info: 'Summon many entities', usage: '/summonMany ', op: true, parse (str) { const args = str.split(' ') if (args.length !== 2) { return false } return {number: args[0], name: args[1]} }, action ({number, name}) { if (Object.keys(serv.entities).length > options['max-entities'] - number) { throw new UserError('Too many mobs !') } const entity = entitiesByName[name] if (!entity) { player.chat('No entity named ' + name) return } let s = Math.floor(Math.sqrt(number)) for (let i = 0; i < number; i++) { if (entity.type === 'mob') { serv.spawnMob(entity.id, player.world, player.position.offset(Math.floor(i / s * 10), 0, i % s * 10), { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } else if (entity.type === 'object') { serv.spawnObject(entity.id, player.world, player.position.offset(Math.floor(i / s * 10), 0, i % s * 10), { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } } } }) player.commands.add({ base: 'pile', info: 'make a pile of entities', usage: '/pile ', op: true, parse (str) { const args = str.split(' ') if (args.length === 0) { return false } return args .map(name => entitiesByName[name]) .filter(entity => !!entity) }, action (entityTypes) { if (Object.keys(serv.entities).length > options['max-entities'] - entityTypes.length) { throw new UserError('Too many mobs !') } entityTypes.map(entity => { if (entity.type === 'mob') { return serv.spawnMob(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } else if (entity.type === 'object') { return serv.spawnObject(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } }) .reduce((prec, entity) => { if (prec !== null) { prec.attach(entity) } return entity }, null) } }) player.commands.add({ base: 'attach', info: 'attach an entity on an other entity', usage: '/attach ', op: true, parse (str) { const args = str.split(' ') if (args.length !== 2) { return false } let carrier = player.selectorString(args[0]) if (carrier.length === 0) throw new UserError('one carrier') let attached = player.selectorString(args[1]) if (attached.length === 0) throw new UserError('one attached') return {carrier: carrier[0], attached: attached[0]} }, action ({carrier, attached}) { carrier.attach(attached) } }) player.spawnEntity = entity => { player._client.write(entity.spawnPacketName, entity.getSpawnPacket()) if (typeof entity.itemId !== 'undefined') { entity.sendMetadata([{ 'key': 10, 'type': 5, 'value': { blockId: entity.itemId, itemDamage: entity.itemDamage, itemCount: 1 } }]) } entity.equipment.forEach((equipment, slot) => { if (equipment !== undefined) { player._client.write('entity_equipment', { entityId: entity.id, slot: slot, item: Item.toNotch(equipment) }) } } ) } } module.exports.entity = function (entity, serv) { entity.initEntity = (type, entityType, world, position) => { 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.score = {} 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.getSpawnPacket = () => { let scaledVelocity = entity.velocity.scaled(8000 / 20) // from fixed-position/second to unit => 1/8000 blocks per tick if (serv.supportFeature('fixedPointPosition')) { scaledVelocity = scaledVelocity.scaled(1 / 32) } scaledVelocity = scaledVelocity.floored() let entityPosition if (serv.supportFeature('fixedPointPosition')) { entityPosition = entity.position.scaled(32).floored() } else if (serv.supportFeature('doublePosition')) { entityPosition = entity.position } if (entity.type === 'player') { return { entityId: entity.id, playerUUID: entity.uuid, x: entityPosition.x, y: entityPosition.y, z: entityPosition.z, yaw: entity.yaw, pitch: entity.pitch, currentItem: 0, metadata: entity.metadata } } else if (entity.type === 'object') { return { entityId: entity.id, objectUUID: entity.uuid, type: entity.entityType, x: entityPosition.x, y: entityPosition.y, z: entityPosition.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, entityUUID: entity.uuid, type: entity.entityType, x: entityPosition.x, y: entityPosition.y, z: entityPosition.z, yaw: entity.yaw, pitch: entity.pitch, headPitch: entity.headPitch, velocityX: scaledVelocity.x, velocityY: scaledVelocity.y, velocityZ: scaledVelocity.z, metadata: entity.metadata } } } entity.updateAndSpawn = () => { const updatedEntities = entity.getNearby() const entitiesToAdd = updatedEntities.filter(e => entity.nearbyEntities.indexOf(e) === -1) const entitiesToRemove = entity.nearbyEntities.filter(e => updatedEntities.indexOf(e) === -1) if (entity.type === 'player') { entity.despawnEntities(entitiesToRemove) entitiesToAdd.forEach(entity.spawnEntity) } entity.lastPositionPlayersUpdated = entity.position.clone() const playersToAdd = entitiesToAdd.filter(e => e.type === 'player') const playersToRemove = entitiesToRemove.filter(e => e.type === 'player') playersToRemove.forEach(p => p.despawnEntities([entity])) playersToRemove.forEach(p => { p.nearbyEntities = p.getNearby() }) playersToAdd.forEach(p => p.spawnEntity(entity)) playersToAdd.forEach(p => { p.nearbyEntities = p.getNearby() }) entity.nearbyEntities = updatedEntities } entity.on('move', () => { if (entity.position.distanceTo(entity.lastPositionPlayersUpdated) > 2) { entity.updateAndSpawn() } }) entity.destroy = () => { serv.destroyEntity(entity) } entity.attach = (attachedEntity, leash = false) => { if (serv.supportFeature('attachStackEntity') || (serv.supportFeature('setPassengerStackEntity') && leash)) { const p = { entityId: attachedEntity.id, vehicleId: entity.id, leash: leash } if (entity.type === 'player') { entity._client.write('attach_entity', p) } entity._writeOthersNearby('attach_entity', p) } if (serv.supportFeature('setPassengerStackEntity')) { const p = { entityId: entity.id, passengers: [ attachedEntity.id ] } if (entity.type === 'player') { entity._client.write('set_passengers', p) } entity._writeOthersNearby('set_passengers', p) } } }