Skip to content

Commit

Permalink
Fix drops (#563)
Browse files Browse the repository at this point in the history
* Update digging.js

* Update spawn.js

* Use mcData instead

* Comply with linter

* Update spawn.js

* item nbt

* Comply with linter

* Better drops

* even better drops

* fix drops pre-flattening

* velocity fix for object entity spawn

* increase mocha timeout to 3000ms

* use existing selector parser and serv.getPlayers

* improved checks

* comply with linter

* (ugly) object metadata version check

* Bump minecraft-data version

* fix gamemode command

* prevent conflicting enchants

* Bump minecraft-data version again

* bug fixes

* remove irrelevant comment

* Prevent incorrect blocks from being placed unexpectedly

* Increase mocha timeout to 3500ms

* Increase mocha timeout to 5000ms

* try fixing tests

* Fix

* Improve /enchant error message

* Update playerDat.js

Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
  • Loading branch information
darksunlight and rom1504 committed Apr 26, 2022
1 parent f32ad37 commit 80928f7
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 61 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"prepublishOnly": "cp docs/README.md README.md",
"lint": "standard test/*.test.js src/**/*.js src/**/**/*.js src/*.js examples/*.js *.js",
"fix": "standard --fix test/*.test.js src/**/*.js src/**/**/*.js src/*.js examples/*.js *.js",
"mocha_test": "mocha --reporter spec --exit",
"mocha_test": "mocha --reporter spec --timeout 3000 --exit",
"test": "npm run mocha_test",
"pretest": "npm run lint"
},
Expand Down
28 changes: 20 additions & 8 deletions src/lib/playerDat.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ async function read (uuid, spawnPoint, worldFolder) {
pitch: playerData.Rotation.value.value[1],
onGround: Boolean(playerData.OnGround.value)
},
inventory: playerData.Inventory.value.value
inventory: playerData.Inventory.value.value.map(nbtItem => {
if (nbtItem.tag && nbtItem.tag === undefined) nbtItem.tag.name = ''
return nbtItem
})
}
} catch (e) {
return {
Expand All @@ -44,12 +47,12 @@ async function read (uuid, spawnPoint, worldFolder) {
}
}

async function save (player, worldFolder, snakeCase) {
async function save (player, worldFolder, snakeCase, theFlattening) {
function playerInventoryToNBT (playerInventory) {
const nbtInventory = []
playerInventory.slots.forEach(item => {
if (item) {
nbtInventory.push({
const nbtItem = {
Slot: {
type: 'byte',
value: convertInventorySlotId.toNBT(item.slot)
Expand All @@ -61,12 +64,21 @@ async function save (player, worldFolder, snakeCase) {
Count: {
type: 'byte',
value: item.count
},
Damage: {
type: 'short',
value: item.metadata
}
})
}
if (!theFlattening) {
Object.assign(nbtItem, {
Damage: {
type: 'short',
value: item.metadata
}
})
} else if (item.nbt) {
Object.assign(nbtItem, {
tag: item.nbt
})
}
nbtInventory.push(nbtItem)
}
})
return nbtInventory
Expand Down
12 changes: 7 additions & 5 deletions src/lib/plugins/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ module.exports.server = function (serv, { version }) {

const notudf = i => typeof i !== 'undefined'

serv.selector = (type, opt) => {
if (['all', 'random', 'near', 'entity'].indexOf(type) === -1) { throw new UserError('serv.selector(): type must be either [all, random, near, or entity]') }
serv.selector = (type, opt, selfEntityId) => {
if (['all', 'random', 'self', 'near', 'entity'].indexOf(type) === -1) { throw new UserError('serv.selector(): type must be either [all, random, self, near, or entity]') }

const count = opt.count !== undefined
? opt.count
Expand All @@ -215,6 +215,7 @@ module.exports.server = function (serv, { version }) {
const pos = opt.pos
let sample
if (type === 'all') sample = serv.players
else if (type === 'self') sample = serv.players.filter(p => p.id === selfEntityId)
else if (type === 'random' || type === 'near') sample = serv.players.filter(p => p.health !== 0)
else if (type === 'entity') sample = Object.keys(serv.entities).map(k => serv.entities[k])

Expand Down Expand Up @@ -287,17 +288,18 @@ module.exports.server = function (serv, { version }) {
else return sample.slice(count) // Negative, returns from end
}

serv.selectorString = (str, pos, world, allowUser = true) => {
serv.selectorString = (str, pos, world, allowUser = true, ctxEntityId) => {
if (pos) pos = pos.clone()
const player = serv.getPlayer(str)
if (!player && str[0] !== '@') return []
else if (player) return allowUser ? [player] : []
const match = str.match(/^@([arpe])(?:\[([^\]]+)\])?$/)
const match = str.match(/^@([arspe])(?:\[([^\]]+)\])?$/)
if (match[1] === 'r' && !pos) throw new UserError('Can\'t found nearest players')
if (match === null) throw new UserError('Invalid selector format')
const typeConversion = {
a: 'all',
r: 'random',
s: 'self',
p: 'near',
e: 'entity'
}
Expand Down Expand Up @@ -346,7 +348,7 @@ module.exports.server = function (serv, { version }) {
}
})

return serv.selector(type, data)
return serv.selector(type, data, ctxEntityId)
}

serv.posFromString = (str, pos) => {
Expand Down
39 changes: 31 additions & 8 deletions src/lib/plugins/digging.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Vec3 = require('vec3').Vec3

module.exports.player = function (player, serv, { version }) {
const mcData = require('minecraft-data')(version)
function cancelDig ({ position, block }) {
player.sendBlock(position, block.type)
}
Expand Down Expand Up @@ -144,24 +145,46 @@ module.exports.player = function (player, serv, { version }) {
}).stop
}
if (!stop) {
player.behavior('dug', {
position: location,
block: currentlyDugBlock,
dropBlock: true,
const drops = []
const dropBase = {
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
}
if (typeof mcData.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 = mcData.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: mcData.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) dropBlock(data)
if (data.dropBlock) {
drops.forEach(drop => dropBlock(drop))
}
if (serv.supportFeature('acknowledgePlayerDigging')) {
player._client.write('acknowledge_player_digging', {
location: location,
Expand All @@ -188,7 +211,7 @@ module.exports.player = function (player, serv, { version }) {
}

function dropBlock ({ blockDropPosition, blockDropWorld, blockDropVelocity, blockDropId, blockDropDamage, blockDropCount, blockDropPickup, blockDropDeath }) {
serv.spawnObject(2, blockDropWorld, blockDropPosition, {
serv.spawnObject(mcData.entitiesByName[mcData.version['<']('1.11') ? 'Item' : 'item'].id, blockDropWorld, blockDropPosition, {
velocity: blockDropVelocity,
itemId: blockDropId,
itemDamage: blockDropDamage,
Expand Down
7 changes: 6 additions & 1 deletion src/lib/plugins/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ module.exports.player = async function (player, serv, settings) {
} else {
theItem = mcData.blocksByName[itemName]
}
const newItem = new Item(theItem.id, item.Count.value, item.Damage.value)

let newItem
if (mcData.version['<']('1.13')) newItem = new Item(theItem.id, item.Count.value, item.Damage.value)
else if (item.tag) newItem = new Item(theItem.id, item.Count.value, item.tag)
else newItem = new Item(theItem.id, item.Count.value)

const slot = convertInventorySlotId.fromNBT(item.Slot.value)
player.inventory.updateSlot(slot, newItem)
})
Expand Down
2 changes: 1 addition & 1 deletion src/lib/plugins/logout.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ module.exports.player = function (player, serv, { worldFolder }) {
delete serv.uuidToPlayer[player.uuid]
}

playerDat.save(player, worldFolder, serv.supportFeature('attributeSnakeCase'))
playerDat.save(player, worldFolder, serv.supportFeature('attributeSnakeCase'), serv.supportFeature('theFlattening'))
})
}
2 changes: 1 addition & 1 deletion src/lib/plugins/placeBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports.server = (serv, { version }) => {
const itemPlaceHandlers = new Map()
serv.placeItem = (data) => {
const handler = itemPlaceHandlers.get(data.item.type)
return handler ? handler(data) : { id: data.item.type, data: data.item.metadata }
return handler ? handler(data) : (serv.supportFeature('theFlattening') ? {} : { id: data.item.type, data: data.item.metadata })
}

/**
Expand Down
103 changes: 76 additions & 27 deletions src/lib/plugins/players.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const UserError = require('flying-squid').UserError

module.exports.server = function (serv, { version }) {
const mcData = require('minecraft-data')(version)
const Item = require('prismarine-item')(version)
serv.entityMaxId = 0
serv.players = []
Expand All @@ -13,6 +14,10 @@ module.exports.server = function (serv, { version }) {
return null
}

serv.getPlayers = (selector, ctxPlayer) => {
return serv.selectorString(selector, ctxPlayer?.position, ctxPlayer?.world, true, ctxPlayer?.id).filter(entity => entity.type === 'player')
}

serv.commands.add({
base: 'gamemode',
aliases: ['gm'],
Expand All @@ -32,7 +37,7 @@ module.exports.server = function (serv, { version }) {
else throw new UserError('Console cannot set gamemode itself')
}

return str.match(/^(survival|creative|adventure|spectator|[0-3]) (\w+)$/) || false
return str.match(/^(survival|creative|adventure|spectator|[0-3])( @[arspe](?:\[([^\]]+)\])?| \w+)?$/) || false
// return params || false
},
action (str, ctx) {
Expand All @@ -42,25 +47,35 @@ module.exports.server = function (serv, { version }) {
adventure: 2,
spectator: 3
}
const target = str[2]?.trim()
const gamemodesReverse = Object.assign({}, ...Object.entries(gamemodes).map(([k, v]) => ({ [v]: k })))
const gamemode = parseInt(str[1], 10) || gamemodes[str[1]]
const mode = parseInt(str[1], 10) ? gamemodesReverse[parseInt(str[1], 10)] : str[1]
const plyr = serv.getPlayer(str[2])
const mode = !isNaN(parseInt(str[1], 10)) ? gamemodesReverse[parseInt(str[1], 10)] : str[1]
const plyrs = serv.getPlayers(target ?? '@s', ctx.player)
if (ctx.player) {
if (str[2]) {
if (plyr !== null) {
plyr.setGameMode(gamemode)
return `Set ${str[2]}'s game mode to ${mode} Mode`
if (plyrs.length > 0) {
plyrs.forEach(plyr => plyr.setGameMode(gamemode))
if (plyrs.length === 1) {
if (plyrs[0].username === ctx.player.username) {
return `Set own game mode to ${mode} Mode`
}
return `Set ${target}'s game mode to ${mode} Mode`
} else {
throw new UserError(`Player '${str[2]}' cannot be found`)
return `Set ${plyrs.length} players' game mode to ${mode} Mode`
}
} else ctx.player.setGameMode(gamemode)
} else {
throw new UserError(`Player '${target}' not found`)
}
} else {
if (plyr !== null) {
plyr.setGameMode(gamemode)
return `Set ${str[2]}'s game mode to ${mode} Mode`
if (plyrs.length > 0) {
plyrs.forEach(plyr => plyr.setGameMode(gamemode))
if (plyrs.length === 1) {
return `Set ${target}'s game mode to ${mode} Mode`
} else {
return `Set ${plyrs.length} players' game mode to ${mode} Mode`
}
} else {
throw new UserError(`Player '${str[2]}' cannot be found`)
throw new UserError(`Player '${target}' not found`)
}
}
}
Expand Down Expand Up @@ -88,36 +103,70 @@ module.exports.server = function (serv, { version }) {
usage: '/give <player> <item> [count]',
tab: ['player', 'number', 'number'],
op: true,
parse (args) {
parse (args, ctx) {
args = args.split(' ')
if (args[0] === '') return false
if (!serv.getPlayer(args[0])) throw new UserError('Player is not found')
const players = serv.getPlayers(args[0], ctx.player)
if (players.length < 1) throw new UserError('Player not found')
if (args[2] && !args[2].match(/\d/)) throw new UserError('Count must be numerical')
return {
player: serv.getPlayer(args[0]),
players,
item: args[1],
count: args[2] ? args[2] : 1
}
},
action ({ player, item, count }, ctx) {
action ({ players, item, count }) {
const newItem = new Item(item, count)

player.inventory.slots.forEach((e, i) => {
if (!e) return
if (e.type === parseInt(newItem.type)) {
e.count += parseInt(count)
player.inventory.updateSlot(e.slot, e)
return true
}
players.forEach(player => {
player.inventory.slots.forEach((e, i) => {
if (!e) return
if (e.type === parseInt(newItem.type)) {
e.count += parseInt(count)
player.inventory.updateSlot(e.slot, e)
return true
}

if (player.inventory.slots.length === i) {
player.inventory.updateSlot(player.inventory.firstEmptyInventorySlot(), newItem)
}
})

if (player.inventory.slots.length === i) {
if (player.inventory.items().length === 0) {
player.inventory.updateSlot(player.inventory.firstEmptyInventorySlot(), newItem)
}
})
}
})

if (player.inventory.items().length === 0) {
player.inventory.updateSlot(player.inventory.firstEmptyInventorySlot(), newItem)
serv.commands.add({
base: 'enchant',
info: 'Enchants items holded by targets with the specified enchantment and level',
usage: '/enchant <targets> <enchantment> [level]',
tab: ['selector', 'item_enchantment', 'number'],
op: true,
parse (args, ctx) {
args = args.split(' ')
if (args[0] === '') return false
const enchantment = mcData.enchantmentsByName[args[1]]
if (!enchantment) throw new UserError('No such enchantment')
if (args[2] && (parseInt(args[2]) > enchantment.maxLevel || parseInt(args[2]) < 1)) throw new UserError(`Level ${args[2]} is not supported by that enchantment`)
const players = serv.getPlayers(args[0], ctx.player)
if (!players.length) throw new UserError('No players found')
return {
players,
enchantment,
level: args[2] ? parseInt(args[2]) : 1
}
},
action ({ players, enchantment, level }) {
players.forEach(player => {
const heldItem = player.inventory.slots[36 + player.heldItemSlot]
if (!heldItem) return
if (heldItem.enchants?.some(e => e.name === enchantment.name || enchantment.exclude.includes(e.name))) return
heldItem.enchants = [...heldItem.enchants ?? [], { name: enchantment.name, lvl: level }]
player.inventory.updateSlot(heldItem.slot, heldItem)
})
}
})
}
Loading

0 comments on commit 80928f7

Please sign in to comment.