flying-squid
Version:
A minecraft server written in node.js
394 lines (358 loc) • 13.2 kB
JavaScript
const UserError = require('flying-squid').UserError
const UUID = require('uuid-1345')
const { skipMcPrefix } = require('../utils')
const Vec3 = require('vec3').Vec3
const plugins = require('./index')
module.exports.server = function (serv, options) {
const { registry } = serv
const Entity = require('prismarine-entity')(registry)
const mobsById = registry.mobs
const objectsById = registry.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)
for (const plugin of plugins.builtinPlugins) plugin.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, itemCount = 1, pickupTime = undefined, deathTime = undefined }) => {
const object = serv.initEntity('object', type, world, position)
object.uuid = UUID.v4()
// TODO: don't use objectsById, it doesn't exist
object.name = objectsById[type] === undefined ? 'unknown' : 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.itemCount = itemCount
object.metadata = [
{ key: 0, type: 0, value: 0 },
{ key: 1, type: 1, value: 300 },
{ key: 2, type: 5 },
{ key: 3, type: 7, value: false },
{ key: 4, type: 7, value: false }
]
let key = 5
if (registry.version['>=']('1.10')) {
object.metadata.push({ key, type: 7, value: false })
++key
if (registry.version['>=']('1.14')) {
object.metadata.push({ key, type: 18, value: 0 })
++key
}
}
object.metadata.push(
{
key,
type: 6,
value: {
present: true,
itemId,
itemCount
}
}
)
object.updateAndSpawn()
return object
}
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]
}
const entitiesByName = serv.registry.entitiesByName
serv.commands.add({
base: 'summon',
info: 'Summon an entity',
usage: '/summon <entity_name>',
onlyPlayer: true,
tab: ['entity'],
op: true,
action (name, ctx) {
if (Object.keys(serv.entities).length > options['max-entities']) { throw new UserError('Too many mobs !') }
const entity = entitiesByName[skipMcPrefix(name)]
if (!entity) {
return 'No entity named ' + name
}
if (entity.type === 'mob') {
serv.spawnMob(entity.id, ctx.player.world, ctx.player.position, {
velocity: new Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10)
})
} else if (entity.type === 'object') {
serv.spawnObject(entity.id, ctx.player.world, ctx.player.position, {
velocity: new Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10)
})
}
}
})
serv.commands.add({
base: 'summonMany',
info: 'Summon many entities',
usage: '/summonMany <number> <entity_name>',
tab: ['number', 'entity'],
onlyPlayer: true,
op: true,
parse (str) {
const args = str.split(' ')
if (args.length !== 2) { return false }
return { number: args[0], name: args[1] }
},
action ({ number, name }, ctx) {
if (Object.keys(serv.entities).length > options['max-entities'] - number) { throw new UserError('Too many mobs !') }
const entity = entitiesByName[skipMcPrefix(name)]
if (!entity) {
return 'No entity named ' + name
}
const s = Math.floor(Math.sqrt(number))
for (let i = 0; i < number; i++) {
if (entity.type === 'mob') {
serv.spawnMob(entity.id, ctx.player.world, ctx.player.position.offset(Math.floor(i / s * 10), 0, i % s * 10), {
velocity: new Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10)
})
} else if (entity.type === 'object') {
serv.spawnObject(entity.id, ctx.player.world, ctx.player.position.offset(Math.floor(i / s * 10), 0, i % s * 10), {
velocity: new Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10)
})
}
}
}
})
serv.commands.add({
base: 'pile',
info: 'make a pile of entities',
usage: '/pile <entities types>',
onlyPlayer: true,
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, ctx) {
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, ctx.player.world, ctx.player.position, {
velocity: new 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, ctx.player.world, ctx.player.position, {
velocity: new Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10)
})
} else {
return Promise.resolve()
}
})
.reduce((prec, entity) => {
if (prec !== null) { prec.attach(entity) }
return entity
}, null)
}
})
serv.commands.add({
base: 'attach',
info: 'attach an entity on an other entity',
usage: '/attach <carrier> <attached>',
op: true,
parse (str, ctx) {
const args = str.split(' ')
if (args.length !== 2) { return false }
const carrier = ctx.player ? ctx.player.selectorString(args[0]) : serv.selectorString(args[0])
if (carrier.length === 0) throw new UserError('one carrier')
const attached = ctx.player ? ctx.player.selectorString(args[1]) : serv.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)
}
})
}
module.exports.player = function (player, serv, { version }) {
const { registry } = serv
const Item = require('prismarine-item')(registry)
player.spawnEntity = entity => {
player._client.write(entity.spawnPacketName, entity.getSpawnPacket())
if (serv.supportFeature('entityMetadataSentSeparately')) {
entity.sendMetadata(entity.metadata)
}
if (typeof entity.itemId !== 'undefined') {
if (serv.supportFeature('theFlattening')) {
entity.sendMetadata([{
key: 6,
type: 6,
value: {
present: true,
itemId: entity.itemId,
itemCount: entity.itemCount
}
}])
} else {
entity.sendMetadata([{
key: 10,
type: 5,
value: {
blockId: entity.itemId,
itemDamage: entity.itemDamage,
itemCount: entity.itemCount
}
}])
}
}
if (serv.supportFeature('allEntityEquipmentInOne')) {
const equipments = []
entity.equipment.forEach((equipment, slot) => {
if (equipment !== undefined) {
equipments.push({
slot,
item: Item.toNotch(equipment)
})
}
})
if (equipments.length > 0) {
player._client.write('entity_equipment', {
entityId: entity.id,
equipments
})
}
} else {
entity.equipment.forEach((equipment, slot) => {
if (equipment !== undefined) {
player._client.write('entity_equipment', {
entityId: entity.id,
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 (serv.supportFeature('unifiedPlayerAndEntitySpawnPacket')) entity.spawnPacketName = 'spawn_entity'
else if (entity.type === 'player') entity.spawnPacketName = 'named_entity_spawn'
else if (entity.type === 'object') entity.spawnPacketName = 'spawn_entity'
else if (entity.type === 'mob') {
if (serv.supportFeature('consolidatedEntitySpawnPacket')) entity.spawnPacketName = 'spawn_entity'
else 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
}
return {
entityId: entity.id,
playerUUID: entity.uuid,
entityUUID: entity.uuid,
objectUUID: entity.uuid,
type: entity.entityType,
x: entityPosition.x,
y: entityPosition.y,
z: entityPosition.z,
yaw: entity.yaw,
pitch: entity.pitch,
headPitch: entity.headPitch,
currentItem: 0,
objectData: entity.data,
velocity: scaledVelocity,
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
}
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)
}
}
}