flying-squid
Version:
A minecraft server written in node.js
252 lines (235 loc) • 8.71 kB
JavaScript
const Vec3 = require('vec3').Vec3
module.exports.player = function (player, serv, { version }) {
const { registry } = serv
function cancelDig ({ position, block }) {
player.sendBlock(position, block.type)
}
player._client.on('block_dig', async ({ location, status, face, sequence }) => {
if (status === 3 || status === 4) {
const heldItem = player.inventory.slots[36 + player.heldItemSlot]
if (!heldItem || heldItem.type === -1) return
const count = (status === 4) ? 1 : heldItem.count
heldItem.count -= count
if (heldItem.count === 0) player.inventory.slots[36 + player.heldItemSlot] = null
// TODO: correct position & velocity + physic simulation
dropBlock({
blockDropPosition: player.position,
blockDropWorld: player.world,
blockDropVelocity: new Vec3(0, 0, 0),
blockDropId: heldItem.type,
blockDropDamage: heldItem.metadata,
blockDropCount: count,
blockDropPickup: 500,
blockDropDeath: 60 * 5 * 1000
})
} else if (status === 5) {
// TODO: Shoot arrow / finish eating
} else if (status === 6) {
const currentSlot = player.inventory.slots[36 + player.heldItemSlot]
const offhand = player.inventory.slots[45]
player.inventory.updateSlot(36 + player.heldItemSlot, offhand)
player.inventory.updateSlot(45, currentSlot)
} else {
let pos = new Vec3(location.x, location.y, location.z)
const directionVector = directionToVector[face]
const facedPos = pos.plus(directionVector)
const facedBlock = await player.world.getBlock(facedPos)
let block
if (facedBlock.name === 'fire') {
block = facedBlock
pos = facedPos
} else {
block = await player.world.getBlock(pos)
}
currentlyDugBlock = block
if (currentlyDugBlock.type === 0) return
if (status === 0) {
if (player.gameMode === 1) {
creativeDigging(pos)
} else {
startDigging(pos, sequence)
}
} else if (status === 1 || player.gameMode >= 2) {
cancelDigging(pos, sequence)
} else if (status === 2) {
completeDigging(pos, sequence)
}
}
})
function diggingTime () {
// assume holding nothing and usual conditions
return currentlyDugBlock.digTime(null, false, false, false)
}
let currentlyDugBlock
let startDiggingTime
let animationInterval
let expectedDiggingTime
let lastDestroyState
let currentAnimationId
function startDigging (location, sequenceId) {
serv.entityMaxId++
currentAnimationId = serv.entityMaxId
expectedDiggingTime = diggingTime(location)
lastDestroyState = 0
startDiggingTime = new Date()
updateAnimation()
animationInterval = setInterval(updateAnimation, 100)
function updateAnimation () {
const currentDiggingTime = new Date() - startDiggingTime
let newDestroyState = Math.floor(9 * currentDiggingTime / expectedDiggingTime)
newDestroyState = newDestroyState > 9 ? 9 : newDestroyState
if (newDestroyState !== lastDestroyState) {
player.behavior('breakAnimation', {
lastState: lastDestroyState,
state: newDestroyState,
start: startDigging,
timePassed: currentDiggingTime,
position: location
}, ({ state }) => {
lastDestroyState = state
player._writeOthersNearby('block_break_animation', {
entityId: currentAnimationId,
location,
destroyStage: state
})
})
}
}
if (serv.supportFeature('acknowledgePlayerDigging')) {
player._client.write('acknowledge_player_digging', {
sequenceId, // 1.19
location,
block: currentlyDugBlock.stateId,
status: 0,
successful: true
})
}
}
function cancelDigging (location, sequenceId) {
clearInterval(animationInterval)
player._writeOthersNearby('block_break_animation', {
entityId: currentAnimationId,
location,
destroyStage: -1
})
if (serv.supportFeature('acknowledgePlayerDigging')) {
player._client.write('acknowledge_player_digging', {
sequenceId, // 1.19
location,
block: currentlyDugBlock.stateId,
status: 1,
successful: true
})
}
}
async function completeDigging (location, sequenceId) {
clearInterval(animationInterval)
const diggingTime = new Date() - startDiggingTime
let stop = false
if (expectedDiggingTime - diggingTime < 100) {
stop = player.behavior('forceCancelDig', {
stop: true,
start: startDiggingTime,
time: diggingTime
}).stop
}
if (!stop) {
const drops = []
const dropBase = {
blockDropPosition: location.offset(0.5, 0.5, 0.5),
blockDropWorld: player.world,
blockDropDamage: currentlyDugBlock.metadata,
blockDropPickup: 500,
blockDropDeath: 60 * 5 * 1000
}
if (typeof registry.blockLoot === 'undefined') {
drops.push({
...dropBase,
blockDropVelocity: new Vec3(Math.random() * 4 - 2, Math.random() * 2 + 2, Math.random() * 4 - 2),
blockDropId: serv.supportFeature('theFlattening') ? currentlyDugBlock.drops[0] : currentlyDugBlock.type
})
} else {
const heldItem = player.inventory.slots[36 + player.heldItemSlot]
const silkTouch = heldItem?.enchants.map(enchant => enchant.name).includes('silk_touch')
const blockDrops = registry.blockLoot[currentlyDugBlock.name].drops.filter(drop => !(drop[`${silkTouch ? 'noS' : 's'}ilkTouch`] ?? false))
for (const drop of blockDrops) {
drops.push({
...dropBase,
blockDropVelocity: new Vec3(Math.random() * 4 - 2, Math.random() * 2 + 2, Math.random() * 4 - 2),
blockDropId: registry.itemsByName[drop.item].id
})
}
}
player.behavior('dug', {
position: location,
block: currentlyDugBlock,
dropBlock: true,
drops
}, async (data) => {
player.changeBlock(data.position, 0, 0)
const aboveBlock = await player.world.getBlock(data.position.offset(0, 1, 0))
if (aboveBlock.material === 'plant') {
await player.setBlock(data.position.offset(0, 1, 0), 0, 0)
}
if (data.dropBlock) {
drops.forEach(drop => dropBlock(drop))
}
if (serv.supportFeature('acknowledgePlayerDigging')) {
player._client.write('acknowledge_player_digging', {
sequenceId, // 1.19
location,
block: 0,
status: 2,
successful: true
})
}
}, cancelDig)
} else {
player._client.write('block_change', {
location,
type: currentlyDugBlock.type << 4
})
if (serv.supportFeature('acknowledgePlayerDigging')) {
player._client.write('acknowledge_player_digging', {
sequenceId, // 1.19
location,
block: currentlyDugBlock.stateId,
status: 2,
successful: false
})
}
}
}
function dropBlock ({ blockDropPosition, blockDropWorld, blockDropVelocity, blockDropId, blockDropDamage, blockDropCount, blockDropPickup, blockDropDeath }) {
serv.spawnObject(registry.entitiesByName[registry.version['<']('1.11') ? 'Item' : 'item'].id, blockDropWorld, blockDropPosition, {
velocity: blockDropVelocity,
itemId: blockDropId,
itemDamage: blockDropDamage,
itemCount: blockDropCount,
pickupTime: blockDropPickup,
deathTime: blockDropDeath
})
}
function creativeDigging (location) {
player.behavior('dug', {
position: location,
block: currentlyDugBlock,
dropBlock: false,
blockDropPosition: location.offset(0.5, 0.5, 0.5),
blockDropWorld: player.world,
blockDropVelocity: new Vec3(Math.random() * 4 - 2, Math.random() * 2 + 2, Math.random() * 4 - 2),
blockDropId: currentlyDugBlock.type,
blockDropDamage: currentlyDugBlock.metadata,
blockDropPickup: 500,
blockDropDeath: 60 * 5 * 1000
}, async (data) => {
player.changeBlock(data.position, 0, 0)
const aboveBlock = await player.world.getBlock(data.position.offset(0, 1, 0))
if (aboveBlock.material === 'plant') {
await player.setBlock(data.position.offset(0, 1, 0), 0, 0)
}
if (data.dropBlock) dropBlock(data)
}, cancelDig)
}
}
const directionToVector = [new Vec3(0, -1, 0), new Vec3(0, 1, 0), new Vec3(0, 0, -1), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(1, 0, 0)]