Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix drops #563

Merged
merged 36 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
70d5ca6
Update digging.js
darksunlight Feb 13, 2022
5bbe32d
Update spawn.js
darksunlight Feb 13, 2022
87df5b8
Use mcData instead
darksunlight Feb 13, 2022
58a1173
Comply with linter
darksunlight Feb 13, 2022
f7cb915
Update spawn.js
darksunlight Feb 13, 2022
8ec8599
Merge branch 'PrismarineJS:master' into patch-drops
darksunlight Feb 13, 2022
30d2fb2
item nbt
darksunlight Feb 13, 2022
d071eb1
Comply with linter
darksunlight Feb 13, 2022
14c9317
Better drops
darksunlight Feb 13, 2022
45ffb0b
even better drops
darksunlight Feb 13, 2022
387bcd4
fix drops pre-flattening
darksunlight Feb 14, 2022
fde046f
velocity fix for object entity spawn
darksunlight Feb 17, 2022
9fca6b9
Merge branch 'PrismarineJS:master' into patch-drops
darksunlight Feb 17, 2022
129d571
increase mocha timeout to 3000ms
darksunlight Feb 17, 2022
64c5c2b
Merge branch 'patch-drops' of https://github.com/darksunlight/flying-…
darksunlight Feb 17, 2022
be9574a
Merge branch 'master' into patch-drops
darksunlight Mar 3, 2022
49f0072
Merge branch 'PrismarineJS:master' into patch-drops
darksunlight Mar 8, 2022
fdb4d1e
use existing selector parser and serv.getPlayers
darksunlight Mar 9, 2022
0a814d1
improved checks
darksunlight Mar 9, 2022
ba0cf2b
comply with linter
darksunlight Mar 9, 2022
40b8e86
(ugly) object metadata version check
darksunlight Mar 9, 2022
24c3c7a
Bump minecraft-data version
darksunlight Mar 9, 2022
313acc2
fix gamemode command
darksunlight Mar 9, 2022
2571bd6
prevent conflicting enchants
darksunlight Mar 9, 2022
54bbdef
Bump minecraft-data version again
darksunlight Mar 9, 2022
9592959
bug fixes
darksunlight Mar 9, 2022
277f46a
remove irrelevant comment
darksunlight Mar 9, 2022
e70af9e
Prevent incorrect blocks from being placed unexpectedly
darksunlight Mar 10, 2022
cd288b7
Increase mocha timeout to 3500ms
darksunlight Mar 10, 2022
8d1e55c
Increase mocha timeout to 5000ms
darksunlight Mar 10, 2022
266d232
try fixing tests
darksunlight Mar 10, 2022
a61e3a9
Fix
darksunlight Mar 10, 2022
e413525
Improve /enchant error message
darksunlight Mar 10, 2022
9c3faff
Merge branch 'master' into patch-drops
darksunlight Mar 19, 2022
e32cffc
Update playerDat.js
rom1504 Apr 26, 2022
200b50b
Merge branch 'master' into patch-drops
rom1504 Apr 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions 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 All @@ -33,7 +33,7 @@
"event-promise": "^0.0.1",
"flatmap": "^0.0.3",
"long": "^5.1.0",
"minecraft-data": "^2.63.0",
"minecraft-data": "^2.118.0",
"minecraft-protocol": "^1.15.0",
"mkdirp": "^0.5.1",
"moment": "^2.10.6",
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.name = ''
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nbtItem.tag.name would be undefined otherwise and would cause an error on join.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's defined and not empty you're losing the value
but it sounds like the problem should be fixed wherever the error was happening instead of here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nbtItem.tag.name is intended to be empty. It's just somehow lost during the saving process, and I don't know prismarine-nbt enough to figure out what caused it to omit this empty string value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you do if (nbtItem.tag && nbtItem.tag == undefined)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did it

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be added in doc

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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add in doc

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 })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand what you mean.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would we return nothing after the flattening ?

Copy link
Contributor Author

@darksunlight darksunlight Mar 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placeItem is currently only used in the same file, handling the block_place packet from clients. It's destructured into id and data after being called, then the id is used to find the corresponding blocks. Blocks with the same ID as an item is not the corresponding block for the item when the item does not have a corresponding block post-flattening, so I return an empty object with ID undefined such that the wrong block won't be placed.

Copy link
Contributor Author

@darksunlight darksunlight Mar 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In pre-flattening times however this can be used as a fallback for items that somehow don't have an item place handler as the old assumption is indeed true.

}

/**
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
darksunlight marked this conversation as resolved.
Show resolved Hide resolved
},
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]) < 0)) throw new UserError('Level invalid')
darksunlight marked this conversation as resolved.
Show resolved Hide resolved
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