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

Add elytra flying support and rocket support #3163

Merged
merged 8 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 24 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
- [bot.foodSaturation](#botfoodsaturation)
- [bot.oxygenLevel](#botoxygenlevel)
- [bot.physics](#botphysics)
- [bot.fireworkRocketDuration](#botfireworkrocketduration)
- [bot.simpleClick.leftMouse (slot)](#botsimpleclickleftmouse-slot)
- [bot.simpleClick.rightMouse (slot)](#botsimpleclickrightmouse-slot)
- [bot.time.doDaylightCycle](#bottimedodaylightcycle)
Expand Down Expand Up @@ -197,6 +198,7 @@
- ["entityEquip" (entity)](#entityequip-entity)
- ["entitySleep" (entity)](#entitysleep-entity)
- ["entitySpawn" (entity)](#entityspawn-entity)
- ["entityElytraFlew" (entity)](#entityelytraflew-entity)
- ["itemDrop" (entity)](#itemdrop-entity)
- ["playerCollect" (collector, collected)](#playercollect-collector-collected)
- ["entityGone" (entity)](#entitygone-entity)
Expand All @@ -223,6 +225,7 @@
- ["blockBreakProgressEnd" (block, entity)](#blockbreakprogressend-block-entity)
- ["diggingCompleted" (block)](#diggingcompleted-block)
- ["diggingAborted" (block)](#diggingaborted-block)
- ["usedFirework"](#usedfirework)
- ["move"](#move)
- ["forcedMove"](#forcedmove)
- ["mount"](#mount)
Expand Down Expand Up @@ -293,6 +296,7 @@
- [bot.unequip(destination)](#botunequipdestination)
- [bot.tossStack(item)](#bottossstackitem)
- [bot.toss(itemType, metadata, count)](#bottossitemtype-metadata-count)
- [bot.elytraFly()](#botelytrafly)
- [bot.dig(block, [forceLook = true], [digFace])](#botdigblock-forcelook--true-digface)
- [bot.stopDigging()](#botstopdigging)
- [bot.digTime(block)](#botdigtimeblock)
Expand Down Expand Up @@ -1037,6 +1041,10 @@ Number in the range [0, 20] respresenting the number of water-icons known as oxy
Edit these numbers to tweak gravity, jump speed, terminal velocity, etc.
Do this at your own risk.

#### bot.fireworkRocketDuration

How many physics ticks worth of firework rocket boost are left.

#### bot.simpleClick.leftMouse (slot)

abstraction over `bot.clickWindow(slot, 0, 0)`
Expand Down Expand Up @@ -1301,6 +1309,10 @@ Fires when an attribute of an entity changes.
#### "entityEquip" (entity)
#### "entitySleep" (entity)
#### "entitySpawn" (entity)
#### "entityElytraFlew" (entity)
rom1504 marked this conversation as resolved.
Show resolved Hide resolved

An entity started elytra flying.

#### "itemDrop" (entity)
#### "playerCollect" (collector, collected)

Expand Down Expand Up @@ -1417,6 +1429,10 @@ This occurs whether the process was completed or aborted.

* `block` - the block that still exists

#### "usedfirework"

Fires when the bot uses a firework while elytra flying.

#### "move"

Fires when the bot moves. If you want the current position, use
Expand Down Expand Up @@ -1851,6 +1867,11 @@ This function returns a `Promise`, with `void` as its argument once tossing is c
to match any metadata
* `count` - how many you want to toss. `null` is an alias for `1`.

#### bot.elytraFly()

This function returns a `Promise`, with `void` as its argument once activating
elytra flight is complete. It will throw an Error if it fails.

#### bot.dig(block, [forceLook = true], [digFace])

This function returns a `Promise`, with `void` as its argument when the block is broken or you are interrupted.
Expand Down Expand Up @@ -1946,7 +1967,9 @@ Use fishing rod

#### bot.activateItem(offHand=false)

Activates the currently held item. This is how you eat, shoot bows, throw an egg, etc.
Activates the currently held item. This is how you eat, shoot bows, throw an
egg, activate firework rockets, etc.

Optional parameter is `false` for main hand and `true` for off hand.

#### bot.deactivateItem()
Expand Down
66 changes: 66 additions & 0 deletions examples/elytra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This example will shoot the player that said "fire" in chat, when it is said in chat.
const mineflayer = require('mineflayer')

if (process.argv.length < 4 || process.argv.length > 6) {
console.log('Usage : node elytra.js <host> <port> [<name>] [<password>]')
process.exit(1)
}

const bot = mineflayer.createBot({
host: process.argv[2],
port: parseInt(process.argv[3]),
username: process.argv[4] ? process.argv[4] : 'elytraer',
password: process.argv[5]
})

bot.on('error', err => {
console.log(err)
})

bot.on('kicked', err => {
console.log(err)
})

bot.on('spawn', async function () {
bot.chat(`/give ${bot.username} minecraft:elytra`)
bot.chat(`/give ${bot.username} minecraft:firework_rocket 64`)

await sleep(1000)
const elytraItem = bot.inventory.slots.find(item => item?.name === 'elytra')
if (elytraItem == null) {
console.log('no elytra')
return
}
await bot.equip(elytraItem, 'torso')
const fireworkItem = bot.inventory.slots.find(item => item?.name === 'firework_rocket')
if (fireworkItem == null) {
console.log('no fireworks')
return
}
await bot.equip(fireworkItem, 'hand')
})

bot.on('chat', async (username, message) => {
if (message === 'fly') {
await bot.look(bot.entity.yaw, 50 * Math.PI / 180)
bot.setControlState('jump', true)
bot.setControlState('jump', false)
await sleep(50)

// try to fly
try {
await bot.elytraFly()
} catch (err) {
bot.chat(`Failed to fly: ${err}`)
return
}
await sleep(50)

// use rocket
bot.activateItem()
}
})

function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
5 changes: 5 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export interface BotEvents {
entityEquip: (entity: Entity) => Promise<void> | void
entitySleep: (entity: Entity) => Promise<void> | void
entitySpawn: (entity: Entity) => Promise<void> | void
entityElytraFlew: (entity: Entity) => Promise<void> | void
usedFirework: () => Promise<void> | void
itemDrop: (entity: Entity) => Promise<void> | void
playerCollect: (collector: Entity, collected: Entity) => Promise<void> | void
entityAttributes: (entity: Entity) => Promise<void> | void
Expand Down Expand Up @@ -164,6 +166,7 @@ export interface Bot extends TypedEmitter<BotEvents> {
version: string
lkwilson marked this conversation as resolved.
Show resolved Hide resolved
entity: Entity
entities: { [id: string]: Entity }
fireworkRocketDuration: number
spawnPoint: Vec3
game: GameState
player: Player
Expand Down Expand Up @@ -260,6 +263,8 @@ export interface Bot extends TypedEmitter<BotEvents> {

wake: () => Promise<void>

elytraFly: () => Promise<void>

setControlState: (control: ControlState, state: boolean) => void

getControlState: (control: ControlState) => boolean
Expand Down
122 changes: 113 additions & 9 deletions lib/plugins/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,70 @@ function inject (bot) {
}
})

bot.fireworkRocketDuration = 0
function setElytraFlyingState (entity, elytraFlying) {
let startedFlying = false
if (elytraFlying) {
startedFlying = !entity.elytraFlying
entity.elytraFlying = true
} else if (entity.elytraFlying) {
entity.elytraFlying = false
}
if (bot.fireworkRocketDuration !== 0 && entity.id === bot.entity?.id && !elytraFlying) {
bot.fireworkRocketDuration = 0
knownFireworks.splice(0, knownFireworks.length)
}

if (startedFlying) {
bot.emit('entityElytraFlew', entity)
}
}

const knownFireworks = []
function handleBotUsedFireworkRocket (fireworkEntityId, fireworkInfo) {
if (knownFireworks.includes(fireworkEntityId)) return
knownFireworks.push(fireworkEntityId)
let flightDur = 1
if (fireworkInfo?.nbtData != null) {
let nbt = fireworkInfo.nbtData
if (nbt.type === 'compound' && nbt.value.Fireworks != null) {
nbt = nbt.value.Fireworks
if (nbt.type === 'compound' && nbt.value.Flight != null) {
nbt = nbt.value.Flight
if (nbt.type === 'int') {
flightDur += nbt.value
}
}
}
}
const baseDuration = 10 * flightDur
const randomDuration = Math.floor(Math.random() * 6) + Math.floor(Math.random() * 7)
bot.fireworkRocketDuration = baseDuration + randomDuration

bot.emit('usedFirework')
}

let fireworkEntityName
if (bot.supportFeature('fireworkNamePlural')) {
fireworkEntityName = 'fireworks_rocket'
} else if (bot.supportFeature('fireworkNameSingular')) {
fireworkEntityName = 'firework_rocket'
}

let fireworkMetadataIdx
let fireworkMetadataIsOpt
if (bot.supportFeature('fireworkMetadataVarInt7')) {
fireworkMetadataIdx = 7
fireworkMetadataIsOpt = false
} else if (bot.supportFeature('fireworkMetadataOptVarInt8')) {
fireworkMetadataIdx = 8
fireworkMetadataIsOpt = true
} else if (bot.supportFeature('fireworkMetadataOptVarInt9')) {
fireworkMetadataIdx = 9
fireworkMetadataIsOpt = true
}
const hasFireworkSupport = fireworkEntityName !== undefined && fireworkMetadataIdx !== undefined && fireworkMetadataIsOpt !== undefined

bot._client.on('entity_metadata', (packet) => {
// entity metadata
const entity = fetchEntity(packet.entityId)
Expand All @@ -374,7 +438,25 @@ function inject (bot) {
if (metas.sleeping_pos || metas.pose === 2) {
bot.emit('entitySleep', entity)
}

if (hasFireworkSupport && fireworkEntityName === entity.name && metas.attached_to_target !== undefined) {
// fireworkMetadataOptVarInt9 and later is implied by
// mcDataHasEntityMetadata, so no need to check metadata index and type
// (eg fireworkMetadataOptVarInt8)
if (metas.attached_to_target !== 0) {
const entityId = metas.attached_to_target - 1
if (entityId === bot.entity?.id) {
handleBotUsedFireworkRocket(entity.id, metas.fireworks_item)
}
}
}

if (metas.shared_flags != null) {
if (bot.supportFeature('hasElytraFlying')) {
const elytraFlying = metas.shared_flags & 0x80
setElytraFlyingState(entity, Boolean(elytraFlying))
}

if (metas.shared_flags & 2) {
entity.crouching = true
bot.emit('entityCrouch', entity)
Expand All @@ -396,16 +478,38 @@ function inject (bot) {
bot.emit('entitySleep', entity)
}

const bitField = packet.metadata.find(p => p.key === 0)
if (bitField === undefined) {
return
if (hasFireworkSupport && fireworkEntityName === entity.name) {
const attachedToTarget = packet.metadata.find(e => e.key === fireworkMetadataIdx)
if (attachedToTarget !== undefined) {
let entityId
if (fireworkMetadataIsOpt) {
if (attachedToTarget.value !== 0) {
entityId = attachedToTarget.value - 1
} // else, not attached to an entity
} else {
entityId = attachedToTarget.value
}
if (entityId !== undefined && entityId === bot.entity?.id) {
const fireworksItem = packet.metadata.find(e => e.key === (fireworkMetadataIdx - 1))
handleBotUsedFireworkRocket(entity.id, fireworksItem?.value)
}
}
}
if ((bitField.value & 2) !== 0) {
entity.crouching = true
bot.emit('entityCrouch', entity)
} else if (entity.crouching) { // prevent the initial entity_metadata packet from firing off an uncrouch event
entity.crouching = false
bot.emit('entityUncrouch', entity)

const bitField = packet.metadata.find(p => p.key === 0)
if (bitField !== undefined) {
if (bot.supportFeature('hasElytraFlying')) {
const elytraFlying = bitField.value & 0x80
setElytraFlyingState(entity, Boolean(elytraFlying))
}

if ((bitField.value & 2) !== 0) {
entity.crouching = true
bot.emit('entityCrouch', entity)
} else if (entity.crouching) { // prevent the initial entity_metadata packet from firing off an uncrouch event
entity.crouching = false
bot.emit('entityUncrouch', entity)
}
}
}
})
Expand Down
38 changes: 38 additions & 0 deletions lib/plugins/physics.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,44 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {

bot.physics = physics

function getEffectLevel (mcData, effectName, effects) {
const effectDescriptor = mcData.effectsByName[effectName]
if (!effectDescriptor) {
return 0
}
const effectInfo = effects[effectDescriptor.id]
if (!effectInfo) {
return 0
}
return effectInfo.amplifier + 1
}

bot.elytraFly = async () => {
if (bot.entity.elytraFlying) {
throw new Error('Already elytra flying')
} else if (bot.entity.onGround) {
throw new Error('Unable to fly from ground')
} else if (bot.entity.isInWater) {
throw new Error('Unable to elytra fly while in water')
}

const mcData = require('minecraft-data')(bot.version)
if (getEffectLevel(mcData, 'Levitation', bot.entity.effects) > 0) {
throw new Error('Unable to elytra fly with levitation effect')
}

const torsoSlot = bot.getEquipmentDestSlot('torso')
const item = bot.inventory.slots[torsoSlot]
if (item == null || item.name !== 'elytra') {
throw new Error('Elytra must be equip to start flying')
}
bot._client.write('entity_action', {
entityId: bot.entity.id,
actionId: 8,
jumpBoost: 0
})
}

bot.setControlState = (control, state) => {
assert.ok(control in controlState, `invalid control: ${control}`)
assert.ok(typeof state === 'boolean', `invalid state: ${state}`)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
},
"license": "MIT",
"dependencies": {
"minecraft-data": "^3.37.0",
"minecraft-data": "^3.44.0",
"minecraft-protocol": "^1.44.0",
"prismarine-biome": "^1.1.1",
"prismarine-block": "^1.17.0",
Expand All @@ -30,7 +30,7 @@
"prismarine-entity": "^2.3.0",
lkwilson marked this conversation as resolved.
Show resolved Hide resolved
"prismarine-item": "^1.14.0",
"prismarine-nbt": "^2.0.0",
"prismarine-physics": "^1.7.0",
"prismarine-physics": "^1.8.0",
"prismarine-recipe": "^1.3.0",
"prismarine-registry": "^1.5.0",
"prismarine-windows": "^2.8.0",
Expand Down
Loading
Loading