Skip to main content

Client

Struct Client 

Source
pub struct Client {
    pub entity: Entity,
    pub ecs: Arc<RwLock<World>>,
}
Expand description

A Minecraft client instance that can interact with the world.

To make a new client, use either azalea::ClientBuilder or Client::join.

Note that Client is inaccessible from systems (i.e. plugins), but you can achieve everything that client can do with ECS events.

Fields§

§entity: Entity

The entity for this client in the ECS.

§ecs: Arc<RwLock<World>>

A mutually exclusive reference to the entity component system (ECS).

You probably don’t need to access this directly. Note that if you’re using a shared world (i.e. a swarm), the ECS will also contain all entities in all worlds.

You can nearly always use Self::component, Self::query_self, Self::query_entity, or another one of those related functions to access the ECS instead.

Implementations§

Source§

impl Client

Source

pub fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> BestToolResult

Source

pub async fn mine_with_auto_tool(&self, block_pos: BlockPos)

Source§

impl Client

Source

pub fn jump(&self)

Queue a jump for the next tick.

Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 179)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source

pub fn look_at(&self, position: Vec3)

Turn the bot’s head to look at the coordinate in the world.

To look at the center of a block, you should call BlockPos::center.

Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 97)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source

pub async fn wait_ticks(&self, n: usize)

Wait for the specified number of ticks using Self::get_tick_broadcaster.

If you’re going to run this in a loop, you may want to use that function instead and use the Receiver from it to avoid accidentally skipping ticks and having to wait longer.

Source

pub async fn wait_updates(&self, n: usize)

Waits for the specified number of ECS Updates using Self::get_update_broadcaster.

These are basically equivalent to frames because even though we have no rendering, some game mechanics depend on frames.

If you’re going to run this in a loop, you may want to use that function instead and use the Receiver from it to avoid accidentally skipping ticks and having to wait longer.

Source

pub async fn mine(&self, position: BlockPos)

Mine a block.

This won’t turn the bot’s head towards the block, so if that’s necessary you’ll have to do that yourself with look_at.

Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 81)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source§

impl Client

Source

pub fn attack(&self, entity: Entity)

Attack an entity in the world.

This doesn’t automatically look at the entity or perform any range/visibility checks, so it might trigger anticheats.

Source

pub fn has_attack_cooldown(&self) -> bool

Whether the player has an attack cooldown.

Also see Client::attack_cooldown_remaining_ticks.

Examples found in repository?
azalea/examples/testbot/killaura.rs (line 13)
9pub fn tick(bot: Client, state: State) -> eyre::Result<()> {
10    if !state.killaura {
11        return Ok(());
12    }
13    if bot.has_attack_cooldown() {
14        return Ok(());
15    }
16    let bot_position = bot.eye_position();
17
18    let nearest_entity = bot.nearest_entity_by::<&Position, (
19        With<AbstractMonster>,
20        Without<LocalEntity>,
21        Without<Dead>,
22    )>(|position: &Position| {
23        let distance = bot_position.distance_to(**position);
24        distance < 4.
25    });
26
27    if let Some(nearest_entity) = nearest_entity {
28        println!("attacking {nearest_entity:?}");
29        nearest_entity.attack();
30    }
31
32    Ok(())
33}
Source

pub fn attack_cooldown_remaining_ticks(&self) -> usize

Returns the number of ticks until we can attack at full strength again.

Also see Client::has_attack_cooldown.

Source§

impl Client

Source

pub fn write_chat_packet(&self, message: &str)

Send a chat message to the server.

This only sends the chat packet and not the command packet, which means on some servers you can use this to send chat messages that start with a /. The Client::chat function handles checking whether the message is a command and using the proper packet for you, so you should use that instead.

Source

pub fn write_command_packet(&self, command: &str)

Send a command packet to the server. The command argument should not include the slash at the front.

You can also just use Client::chat and start your message with a / to send a command.

Source

pub fn chat(&self, content: impl Into<String>)

Send a message in chat.

If the content starts with a /, a command is sent.

bot.chat("Hello, world!");
Examples found in repository?
azalea/examples/echo.rs (line 27)
19async fn handle(bot: Client, event: Event, _state: State) -> eyre::Result<()> {
20    if let Event::Chat(m) = event
21        && let (Some(sender), content) = m.split_sender_and_content()
22    {
23        if sender == bot.username() {
24            // ignore our own messages
25            return Ok(());
26        }
27        bot.chat(content);
28    }
29
30    Ok(())
31}
More examples
Hide additional examples
azalea/examples/testbot/commands.rs (line 28)
23    pub fn reply(&self, message: impl Into<String>) {
24        let message = message.into();
25        if self.chat.is_whisper() {
26            // /msg instead of /w for compat with custom servers
27            self.bot
28                .chat(format!("/msg {} {message}", self.chat.sender().unwrap()));
29        } else {
30            self.bot.chat(message);
31        }
32    }
azalea/examples/steal.rs (line 46)
42async fn steal(bot: Client, state: State) -> eyre::Result<()> {
43    {
44        let mut is_stealing = state.is_stealing.lock();
45        if *is_stealing {
46            bot.chat("Already stealing");
47            return Ok(());
48        }
49        *is_stealing = true;
50    }
51
52    state.checked_chests.lock().clear();
53
54    loop {
55        let chest_block = bot
56            .world()
57            .read()
58            .find_blocks(bot.position(), &BlockKind::Chest.into())
59            .find(
60                // find the closest chest that hasn't been checked
61                |block_pos| !state.checked_chests.lock().contains(block_pos),
62            );
63        let Some(chest_block) = chest_block else {
64            break;
65        };
66
67        state.checked_chests.lock().push(chest_block);
68
69        bot.goto(RadiusGoal::new(chest_block.center(), 3.)).await;
70
71        let Some(chest) = bot.open_container_at(chest_block).await else {
72            println!("Couldn't open chest at {chest_block:?}");
73            continue;
74        };
75
76        println!("Getting contents of chest at {chest_block:?}");
77        for (index, slot) in chest.contents().unwrap_or_default().iter().enumerate() {
78            println!("Checking slot {index}: {slot:?}");
79            let ItemStack::Present(item) = slot else {
80                continue;
81            };
82            if item.kind == ItemKind::Diamond {
83                println!("clicking slot ^");
84                chest.left_click(index);
85            }
86        }
87    }
88
89    bot.chat("Done");
90
91    *state.is_stealing.lock() = false;
92
93    Ok(())
94}
azalea/examples/testbot/commands/debug.rs (line 36)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn set_client_information(&self, client_information: ClientInformation)

Tell the server we changed our game options (i.e. render distance, main hand).

If this is not set before the login packet, the default will be sent.

bot.set_client_information(ClientInformation {
    view_distance: 2,
    ..Default::default()
});
Examples found in repository?
azalea/examples/testbot/main.rs (lines 140-143)
135async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result<()> {
136    let swarm = bot.resource::<SwarmState>();
137
138    match event {
139        azalea::Event::Init => {
140            bot.set_client_information(ClientInformation {
141                view_distance: 32,
142                ..Default::default()
143            });
144            if swarm.args.pathfinder_debug_particles {
145                bot.ecs
146                    .write()
147                    .entity_mut(bot.entity)
148                    .insert(PathfinderDebugParticles);
149            }
150        }
151        azalea::Event::Chat(chat) => {
152            let (Some(username), content) = chat.split_sender_and_content() else {
153                return Ok(());
154            };
155            if username != swarm.args.owner_username {
156                return Ok(());
157            }
158
159            println!("{:?}", chat.message());
160
161            let command = if chat.is_whisper() {
162                Some(content)
163            } else {
164                content.strip_prefix('!').map(|s| s.to_owned())
165            };
166            if let Some(command) = command {
167                match swarm.commands.execute(
168                    command,
169                    Mutex::new(CommandSource {
170                        bot: bot.clone(),
171                        chat: chat.clone(),
172                        state: state.clone(),
173                    }),
174                ) {
175                    Ok(_) => {}
176                    Err(err) => {
177                        eprintln!("{err:?}");
178                        let command_source = CommandSource {
179                            bot,
180                            chat: chat.clone(),
181                            state: state.clone(),
182                        };
183                        command_source.reply(format!("{err:?}"));
184                    }
185                }
186            }
187        }
188        azalea::Event::Tick => {
189            killaura::tick(bot.clone(), state.clone())?;
190
191            let task = *state.task.lock();
192            match task {
193                BotTask::None => {}
194            }
195        }
196        azalea::Event::Login => {
197            println!("Got login event")
198        }
199        _ => {}
200    }
201
202    Ok(())
203}
Source§

impl Client

Source

pub fn component<T: Component>(&self) -> MappedRwLockReadGuard<'_, T>

Get a component from the client.

This allows you to access certain data stored about the client entity that isn’t accessible in a simpler way.

This returns a reference to the component wrapped by a read guard. This makes the component cheap to access, but means that the ECS cannot be mutated while it’s in scope (it will cause a deadlock). In some cases, it may be simpler for you to immediately clone the component after accessing it.

If the component isn’t guaranteed to be present, consider using Self::get_component instead.

To do more complex queries or to mutate data, see Self::query_self.

To access data about other entities, you can use Self::entity_component (and its other related functions).

You may also use Self::ecs directly if you need more control over when the ECS is locked.

§Panics

This will panic if the component doesn’t exist on the client. Use Self::get_component to avoid this.

§Examples
let world_name = client.component::<WorldName>();
Source

pub fn get_component<T: Component>( &self, ) -> Option<MappedRwLockReadGuard<'_, T>>

Get a component on this client, or None if it doesn’t exist.

If the component is guaranteed to be present, consider using Self::component. Also see that function for more details.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 189)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn query_self<D: QueryData, R>( &self, f: impl FnOnce(QueryItem<'_, '_, D>) -> R, ) -> R

Query the ECS for data from our client entity.

To query another entity, you can use Self::query_entity.

You can use this to mutate data on the client.

§Examples
// teleport one block up
client.query_self::<&mut Position, _>(|mut pos| pos.y += 1.0);
§Panics

This will panic if the client is missing a component required by the query. Consider using Self::try_query_self to avoid this.

Examples found in repository?
azalea/examples/testbot/commands/combat.rs (line 13)
7pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
8    commands.register(
9        literal("killaura").then(argument("enabled", bool()).executes(|ctx: &Ctx| {
10            let enabled = get_bool(ctx, "enabled").unwrap();
11            let source = ctx.source.lock();
12            let bot = source.bot.clone();
13            bot.query_self::<&mut State, _>(|mut state| state.killaura = enabled);
14            source.reply(if enabled {
15                "Enabled killaura"
16            } else {
17                "Disabled killaura"
18            });
19            1
20        })),
21    );
22}
Source

pub fn try_query_self<D: QueryData, R>( &self, f: impl FnOnce(QueryItem<'_, '_, D>) -> R, ) -> Result<R, QueryEntityError>

Query the ECS for data from our client entity, or return None if the query failed.

Also see Self::query_self.

Source

pub fn query_entity<D: QueryData, R>( &self, entity: Entity, f: impl FnOnce(QueryItem<'_, '_, D>) -> R, ) -> R

Query the ECS for data from an entity.

Note that it is often simpler to use Self::entity_component.

To query the client, you should use Self::query_self.

You can also use this to mutate data on an entity.

§Panics

This will panic if the entity doesn’t exist or if the query isn’t valid for the entity. For a non-panicking version, you may use Self::try_query_entity.

Source

pub fn try_query_entity<D: QueryData, R>( &self, entity: Entity, f: impl FnOnce(QueryItem<'_, '_, D>) -> R, ) -> Result<R, QueryEntityError>

A convenience function for getting components from any entity, or None if the query fails.

If you’re sure that the entity exists and that the query will succeed, you can use Self::query_entity.

Source

pub fn any_entity_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Option<EntityRef>

Quickly returns an EntityRef for an arbitrary entity that matches the given predicate function that is in the same World as the client.

Examples found in repository?
azalea/examples/testbot/commands.rs (lines 37-39)
34    pub fn entity(&self) -> Option<azalea::EntityRef> {
35        let username = self.chat.sender()?;
36        self.bot
37            .any_entity_by::<&GameProfileComponent, With<Player>>(
38                |profile: &GameProfileComponent| profile.name == username,
39            )
40    }
Source

pub fn any_entity_id_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Option<Entity>

Quickly returns a lightweight [Entity] for an arbitrary entity that matches the given predicate function that is in the same World as the client.

To get an EntityRef, consider using Self::any_entity_by instead.

If you want to find the nearest entity, consider using Self::nearest_entity_id_by instead. If you want to find all entities that match the predicate, use Self::nearest_entity_ids_by.

§Example
use azalea::{entity::metadata::Player, player::GameProfileComponent};
use bevy_ecs::query::With;

let entity = bot.any_entity_id_by::<&GameProfileComponent, With<Player>>(
    |profile: &GameProfileComponent| profile.name == sender_name,
);
Source

pub fn nearest_entity_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Option<EntityRef>

Return an EntityRef for the nearest entity that matches the given predicate function.

If you don’t need the entity to be the nearest one, it may be more efficient to use Self::any_entity_by instead. You can also use Self::nearest_entities_by to get all nearby entities.

Also see Self::nearest_entity_id_by if you only need the lightweight [Entity] identifier.

Examples found in repository?
azalea/examples/testbot/killaura.rs (lines 18-25)
9pub fn tick(bot: Client, state: State) -> eyre::Result<()> {
10    if !state.killaura {
11        return Ok(());
12    }
13    if bot.has_attack_cooldown() {
14        return Ok(());
15    }
16    let bot_position = bot.eye_position();
17
18    let nearest_entity = bot.nearest_entity_by::<&Position, (
19        With<AbstractMonster>,
20        Without<LocalEntity>,
21        Without<Dead>,
22    )>(|position: &Position| {
23        let distance = bot_position.distance_to(**position);
24        distance < 4.
25    });
26
27    if let Some(nearest_entity) = nearest_entity {
28        println!("attacking {nearest_entity:?}");
29        nearest_entity.attack();
30    }
31
32    Ok(())
33}
Source

pub fn nearest_entity_id_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Option<Entity>

Return a lightweight [Entity] for the nearest entity that matches the given predicate function.

To get an EntityRef, consider using Self::nearest_entity_by instead.

If you don’t need the entity to be the nearest one, it may be more efficient to use Self::any_entity_id_by instead. You can also use Self::nearest_entity_ids_by to get all nearby entities.

Source

pub fn nearest_entities_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Box<[EntityRef]>

Returns an array of all EntityRefs in the world that match the predicate, sorted by nearest first.

To only get the nearest entity, consider using Self::nearest_entity_by. If you only need the [Entity] identifiers, you can use Self::nearest_entity_ids_by instead.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 281)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn nearest_entity_ids_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Box<[Entity]>

Returns an array of all [Entity]s in the world that match the predicate, sorted by nearest first.

To only get the nearest entity, consider using Self::nearest_entity_id_by. To get the EntityRefs instead, you can use Self::nearest_entities_by.

let nearby_players =
    bot.nearest_entities_by::<(), (With<Player>, Without<LocalEntity>)>(|_: ()| true);
Source

pub fn entity_component<T: Component>( &self, entity: Entity, ) -> MappedRwLockReadGuard<'_, T>

Get a component from an entity.

This allows you to access data stored about entities that isn’t accessible in a simpler way.

This returns a reference to the component wrapped by a read guard. This makes the component cheap to access, but means that the ECS cannot be mutated while it’s in scope. In some cases, it may be simpler for you to immediately clone the component after accessing it.

If you’re trying to get a component for this client, you should use Self::component instead.

To do more complex queries or to mutate data, see Self::query_entity.

§Panics

This will panic if the component doesn’t exist on the entity. Use Self::get_entity_component to avoid this.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 133)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn get_entity_component<T: Component>( &self, entity: Entity, ) -> Option<MappedRwLockReadGuard<'_, T>>

Get a component from an entity, if it exists.

This is similar to Self::entity_component but returns an Option instead of panicking if the component isn’t present.

Source§

impl Client

Source

pub fn hit_result(&self) -> HitResult

Returns the current HitResult, which is the block or entity in the client’s crosshair.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 120)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn block_interact(&self, position: BlockPos)

Right-click a block.

The behavior of this depends on the target block, and it’ll either place the block you’re holding in your hand or use the block you clicked (like toggling a lever).

Note that this may trigger anticheats as it doesn’t take into account whether you’re actually looking at the block.

Source

pub fn entity_interact(&self, entity: Entity)

Right-click an entity.

This can click through walls, which may trigger anticheats. If that behavior isn’t desired, consider using Client::start_use_item instead.

Source

pub fn start_use_item(&self)

Right-click the currently held item.

If the item is consumable, then it’ll act as if right-click was held until the item finishes being consumed. You can use this to eat food.

If we’re looking at a block or entity, then it will be clicked. Also see Client::block_interact and Client::entity_interact.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 255)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn menu(&self) -> Menu

Return the menu that is currently open, or the player’s inventory if no menu is open.

If you need to interact with the menu, consider using Self::open_inventory instead.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 173)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn selected_hotbar_slot(&self) -> u8

Returns the index of the hotbar slot that’s currently selected.

If you want to access the actual held item, you can get the current menu with Client::menu and then get the slot index by offsetting from the start of azalea_inventory::Menu::hotbar_slots_range.

You can use Self::set_selected_hotbar_slot to change it.

Source

pub fn set_selected_hotbar_slot(&self, new_hotbar_slot_index: u8)

Update the selected hotbar slot index.

This will run next Update, so you might want to call bot.wait_updates(1) after calling this if you’re using azalea.

§Panics

This will panic if new_hotbar_slot_index is not in the range 0..=8.

Source§

impl Client

Source

pub fn start_mining(&self, position: BlockPos)

Source

pub fn is_mining(&self) -> bool

Returns true if the client is currently trying to mine a block.

Source

pub fn left_click_mine(&self, enabled: bool)

When enabled, the bot will mine any block that it is looking at if it is reachable.

Source§

impl Client

Source

pub fn set_jumping(&self, jumping: bool)

Set whether we’re jumping. This acts as if you held space in vanilla.

If you want to jump once, use the jump function in azalea.

If you’re making a realistic client, calling this function every tick is recommended.

Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 186)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source

pub fn jumping(&self) -> bool

Returns whether the player will try to jump next tick.

Source

pub fn set_crouching(&self, crouching: bool)

Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 193)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source

pub fn crouching(&self) -> bool

Whether the client is currently trying to sneak.

You may want to check the Pose instead.

Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 193)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source

pub fn set_direction(&self, y_rot: f32, x_rot: f32)

Sets the direction the client is looking.

y_rot is yaw (looking to the side, between -180 to 180), and x_rot is pitch (looking up and down, between -90 to 90).

You can get these numbers from the vanilla f3 screen.

Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 153)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source

pub fn direction(&self) -> LookDirection

Returns the direction the client is looking.

See Self::set_direction for more details.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 100)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn walk(&self, direction: WalkDirection)

Start walking in the given direction.

To sprint, use Client::sprint. To stop walking, call walk with WalkDirection::None.

§Example
// walk for one second
bot.walk(WalkDirection::Forward);
tokio::time::sleep(Duration::from_secs(1)).await;
bot.walk(WalkDirection::None);
Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 122)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source

pub fn sprint(&self, direction: SprintDirection)

Start sprinting in the given direction.

To stop moving, call bot.walk(WalkDirection::None).

§Example
// sprint for one second
bot.sprint(SprintDirection::Forward);
tokio::time::sleep(Duration::from_secs(1)).await;
bot.walk(WalkDirection::None);
Examples found in repository?
azalea/examples/testbot/commands/movement.rs (line 141)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
Source§

impl Client

Source

pub fn new(entity: Entity, ecs: Arc<RwLock<World>>) -> Self

Create a new client from the given GameProfile, ECS Entity, ECS World, and schedule runner function. You should only use this if you want to change these fields from the defaults, otherwise use Client::join.

Source

pub async fn join( account: Account, address: impl ResolvableAddr, ) -> Result<(Self, UnboundedReceiver<Event>), ResolveError>

Connect to a Minecraft server.

To change the render distance and other settings, use Client::set_client_information. To watch for events like packets sent by the server, use the rx variable this function returns.

§Examples
use azalea::{Account, Client};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let account = Account::offline("bot");
    let (client, rx) = Client::join(account, "localhost").await?;
    client.chat("Hello, world!");
    client.disconnect();
    Ok(())
}
Source

pub async fn join_with_proxy( account: Account, address: impl ResolvableAddr, proxy: Proxy, ) -> Result<(Self, UnboundedReceiver<Event>), ResolveError>

Source

pub async fn start_client(__arg0: StartClientOpts) -> Self

Create a Client when you already have the ECS made with [start_ecs_runner]. You’d usually want to use Self::join instead.

Source

pub fn write_packet(&self, packet: impl Packet<ServerboundGamePacket>)

Write a packet directly to the server.

Source

pub fn disconnect(&self)

Disconnect this client from the server by ending all tasks.

The OwnedReadHalf for the TCP connection is in one of the tasks, so it automatically closes the connection when that’s dropped.

Note that this will not return from your client builder. If you need that, consider using Self::exit instead.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 43)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn exit(&self)

End the entire client or swarm, and return from ClientBuilder::start or SwarmBuilder::start.

You should typically avoid calling this if you intend on creating the client again, because creating an entirely new swarm can be a relatively expensive process.

If you only want to change the server that the bots are connecting to, it may be better to access the client’s internal Swarm and call Swarm::add_with_opts with a different server address.

For convenience, this is also duplicated on Swarm as Swarm::exit.

// a bot that joins a server and prints "done!" when it's disconnected or if it fails to connect.
use azalea::{NoState, prelude::*};
#[tokio::main]
async fn main() {
    let account = Account::offline("bot");
    ClientBuilder::new()
        .set_handler(handle)
        .start(account, "localhost")
        .await;
    println!("done!");
}
async fn handle(bot: Client, event: Event, _state: NoState) -> eyre::Result<()> {
    match event {
        Event::Disconnect(_) | Event::ConnectionFailed(_) => {
            bot.exit();
        }
        _ => {}
    }
    Ok(())
}
Source

pub fn with_raw_connection<R>(&self, f: impl FnOnce(&RawConnection) -> R) -> R

Source

pub fn with_raw_connection_mut<R>( &self, f: impl FnOnce(Mut<'_, RawConnection>) -> R, ) -> R

Source

pub fn resource<T: Resource + Clone>(&self) -> T

Get a resource from the ECS. This will clone the resource and return it.

Examples found in repository?
azalea/examples/testbot/main.rs (line 136)
135async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result<()> {
136    let swarm = bot.resource::<SwarmState>();
137
138    match event {
139        azalea::Event::Init => {
140            bot.set_client_information(ClientInformation {
141                view_distance: 32,
142                ..Default::default()
143            });
144            if swarm.args.pathfinder_debug_particles {
145                bot.ecs
146                    .write()
147                    .entity_mut(bot.entity)
148                    .insert(PathfinderDebugParticles);
149            }
150        }
151        azalea::Event::Chat(chat) => {
152            let (Some(username), content) = chat.split_sender_and_content() else {
153                return Ok(());
154            };
155            if username != swarm.args.owner_username {
156                return Ok(());
157            }
158
159            println!("{:?}", chat.message());
160
161            let command = if chat.is_whisper() {
162                Some(content)
163            } else {
164                content.strip_prefix('!').map(|s| s.to_owned())
165            };
166            if let Some(command) = command {
167                match swarm.commands.execute(
168                    command,
169                    Mutex::new(CommandSource {
170                        bot: bot.clone(),
171                        chat: chat.clone(),
172                        state: state.clone(),
173                    }),
174                ) {
175                    Ok(_) => {}
176                    Err(err) => {
177                        eprintln!("{err:?}");
178                        let command_source = CommandSource {
179                            bot,
180                            chat: chat.clone(),
181                            state: state.clone(),
182                        };
183                        command_source.reply(format!("{err:?}"));
184                    }
185                }
186            }
187        }
188        azalea::Event::Tick => {
189            killaura::tick(bot.clone(), state.clone())?;
190
191            let task = *state.task.lock();
192            match task {
193                BotTask::None => {}
194            }
195        }
196        azalea::Event::Login => {
197            println!("Got login event")
198        }
199        _ => {}
200    }
201
202    Ok(())
203}
Source

pub fn map_resource<T: Resource, R>(&self, f: impl FnOnce(&T) -> R) -> R

Get a required ECS resource and call the given function with it.

Source

pub fn map_get_resource<T: Resource, R>( &self, f: impl FnOnce(Option<&T>) -> R, ) -> R

Get an optional ECS resource and call the given function with it.

Source

pub fn world(&self) -> Arc<RwLock<World>>

Get an RwLock with a reference to our (potentially shared) Minecraft world.

This gets the World from the client’s WorldHolder component. If it’s a normal client, then it’ll be the same as the world the client has loaded. If the client is using a shared world, then the shared world will be a superset of the client’s world.

Examples found in repository?
azalea/examples/steal.rs (line 56)
42async fn steal(bot: Client, state: State) -> eyre::Result<()> {
43    {
44        let mut is_stealing = state.is_stealing.lock();
45        if *is_stealing {
46            bot.chat("Already stealing");
47            return Ok(());
48        }
49        *is_stealing = true;
50    }
51
52    state.checked_chests.lock().clear();
53
54    loop {
55        let chest_block = bot
56            .world()
57            .read()
58            .find_blocks(bot.position(), &BlockKind::Chest.into())
59            .find(
60                // find the closest chest that hasn't been checked
61                |block_pos| !state.checked_chests.lock().contains(block_pos),
62            );
63        let Some(chest_block) = chest_block else {
64            break;
65        };
66
67        state.checked_chests.lock().push(chest_block);
68
69        bot.goto(RadiusGoal::new(chest_block.center(), 3.)).await;
70
71        let Some(chest) = bot.open_container_at(chest_block).await else {
72            println!("Couldn't open chest at {chest_block:?}");
73            continue;
74        };
75
76        println!("Getting contents of chest at {chest_block:?}");
77        for (index, slot) in chest.contents().unwrap_or_default().iter().enumerate() {
78            println!("Checking slot {index}: {slot:?}");
79            let ItemStack::Present(item) = slot else {
80                continue;
81            };
82            if item.kind == ItemKind::Diamond {
83                println!("clicking slot ^");
84                chest.left_click(index);
85            }
86        }
87    }
88
89    bot.chat("Done");
90
91    *state.is_stealing.lock() = false;
92
93    Ok(())
94}
More examples
Hide additional examples
azalea/examples/testbot/commands/debug.rs (line 129)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn partial_world(&self) -> Arc<RwLock<PartialWorld>>

Get an RwLock with a reference to the world that this client has loaded.

let world = client.partial_world();
let is_0_0_loaded = world.read().chunks.limited_get(&ChunkPos::new(0, 0)).is_some();
Source

pub fn logged_in(&self) -> bool

Returns whether we have a received the login packet yet.

Source

pub fn entity(&self) -> EntityRef

Returns the client as an EntityRef, allowing you to treat it as any other entity.

Source

pub fn entity_ref_for(&self, entity: Entity) -> EntityRef

Create an EntityRef for the given ECS entity.

Source§

impl Client

Source

pub fn hunger(&self) -> Hunger

Get the hunger level of this client, which includes both food and saturation.

This is a shortcut for self.component::<Hunger>().to_owned().

Source

pub fn experience(&self) -> Experience

Get the experience of this client.

This is a shortcut for self.component::<Experience>().to_owned().

Source

pub fn username(&self) -> String

Get the username of this client.

This is a shortcut for bot.component::<GameProfileComponent>().name.to_owned().

Examples found in repository?
azalea/examples/steal.rs (line 29)
27async fn handle(bot: Client, event: Event, state: State) -> eyre::Result<()> {
28    if let Event::Chat(m) = event {
29        if m.sender() == Some(bot.username()) {
30            return Ok(());
31        };
32        if m.content() != "go" {
33            return Ok(());
34        }
35
36        steal(bot, state).await?;
37    }
38
39    Ok(())
40}
More examples
Hide additional examples
azalea/examples/echo.rs (line 23)
19async fn handle(bot: Client, event: Event, _state: State) -> eyre::Result<()> {
20    if let Event::Chat(m) = event
21        && let (Some(sender), content) = m.split_sender_and_content()
22    {
23        if sender == bot.username() {
24            // ignore our own messages
25            return Ok(());
26        }
27        bot.chat(content);
28    }
29
30    Ok(())
31}
azalea/examples/testbot/commands/debug.rs (line 91)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn tab_list(&self) -> HashMap<Uuid, PlayerInfo>

Get a map of player UUIDs to their information in the tab list.

This is a shortcut for *bot.component::<TabList>().

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 282)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn profile(&self) -> GameProfile

Returns the GameProfile for our client. This contains your username, UUID, and skin data.

These values are set by the server upon login, which means they might not match up with your actual game profile. Also, note that the username and skin that gets displayed in-game will actually be the ones from the tab list, which you can get from Self::tab_list.

This as also available from the ECS as GameProfileComponent.

Source

pub fn account(&self) -> Account

Returns the Account for our client.

Source

pub fn player_uuid_by_username(&self, username: &str) -> Option<Uuid>

A convenience function to get the Minecraft Uuid of a player by their username, if they’re present in the tab list.

You can chain this with Client::entity_by_uuid to get the ECS Entity for the player.

Source

pub fn entity_id_by_uuid(&self, uuid: Uuid) -> Option<Entity>

Get an [Entity] in the world by its Minecraft UUID, if it’s within render distance.

Also see Self::entity_by_uuid and Self::entity_id_by_minecraft_id.

Source

pub fn entity_by_uuid(&self, uuid: Uuid) -> Option<EntityRef>

Get an EntityRef in the world by its Minecraft UUID, if it’s within render distance.

Also see Self::entity_id_by_uuid.

Source

pub fn entity_id_by_minecraft_id(&self, id: MinecraftEntityId) -> Option<Entity>

Get an [Entity] in the world by its MinecraftEntityId.

Also see Self::entity_by_uuid and Self::entity_id_by_uuid.

Source

pub fn entity_by_minecraft_id(&self, id: MinecraftEntityId) -> Option<EntityRef>

Get an EntityRef in the world by its MinecraftEntityId.

Also see Self::entity_id_by_uuid.

Source

pub fn with_registry_holder<R>(&self, f: impl FnOnce(&RegistryHolder) -> R) -> R

Call the given function with the client’s RegistryHolder.

Note that the player’s world will be locked during this time, which may result in a deadlock if you try to access the world again while in the function.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (lines 297-300)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source

pub fn resolve_registry_name( &self, registry: &impl ResolvableDataRegistry, ) -> Option<Identifier>

👎Deprecated:

use bot.resolve_registry_key(registry).map(|r| r.into_ident()) instead.

Resolve the given registry to its name.

This is necessary for data-driven registries like Enchantment.

Source

pub fn resolve_registry_key<R: ResolvableDataRegistry>( &self, registry: &R, ) -> Option<R::Key>

Resolve the given registry entry to its key (aka name).

This is necessary for data-driven registries like Enchantment and Biome.

To get the key as an Identifier, you can map the return value like .map(|r| r.into_ident()).

Source

pub fn with_resolved_registry<R: ResolvableDataRegistry, Ret>( &self, registry: R, f: impl FnOnce(&Identifier, &R::DeserializesTo) -> Ret, ) -> Option<Ret>

Resolve the given registry to its name and data and call the given function with it.

This is necessary for data-driven registries like Enchantment.

If you just want the value name, use Self::resolve_registry_name instead.

Source

pub fn ticks_connected(&self) -> u64

Returns the number of ticks since the login packet was received, or 0 if the client isn’t in the world.

This is a shortcut for getting the TicksConnected component.

Source§

impl Client

Source

pub async fn open_container_at(&self, pos: BlockPos) -> Option<ContainerHandle>

Open a container in the world, like a chest.

Use Client::open_inventory to open your own inventory.

This function times out after 5 seconds (100 ticks). Use Self::open_container_at_with_timeout_ticks if you would like to configure this.

let target_pos = bot
    .world()
    .read()
    .find_block(bot.position(), &BlockKind::Chest.into());
let Some(target_pos) = target_pos else {
    bot.chat("no chest found");
    return;
};
let container = bot.open_container_at(target_pos).await;
Examples found in repository?
azalea/examples/steal.rs (line 71)
42async fn steal(bot: Client, state: State) -> eyre::Result<()> {
43    {
44        let mut is_stealing = state.is_stealing.lock();
45        if *is_stealing {
46            bot.chat("Already stealing");
47            return Ok(());
48        }
49        *is_stealing = true;
50    }
51
52    state.checked_chests.lock().clear();
53
54    loop {
55        let chest_block = bot
56            .world()
57            .read()
58            .find_blocks(bot.position(), &BlockKind::Chest.into())
59            .find(
60                // find the closest chest that hasn't been checked
61                |block_pos| !state.checked_chests.lock().contains(block_pos),
62            );
63        let Some(chest_block) = chest_block else {
64            break;
65        };
66
67        state.checked_chests.lock().push(chest_block);
68
69        bot.goto(RadiusGoal::new(chest_block.center(), 3.)).await;
70
71        let Some(chest) = bot.open_container_at(chest_block).await else {
72            println!("Couldn't open chest at {chest_block:?}");
73            continue;
74        };
75
76        println!("Getting contents of chest at {chest_block:?}");
77        for (index, slot) in chest.contents().unwrap_or_default().iter().enumerate() {
78            println!("Checking slot {index}: {slot:?}");
79            let ItemStack::Present(item) = slot else {
80                continue;
81            };
82            if item.kind == ItemKind::Diamond {
83                println!("clicking slot ^");
84                chest.left_click(index);
85            }
86        }
87    }
88
89    bot.chat("Done");
90
91    *state.is_stealing.lock() = false;
92
93    Ok(())
94}
Source

pub async fn open_container_at_with_timeout_ticks( &self, pos: BlockPos, timeout_ticks: Option<usize>, ) -> Option<ContainerHandle>

Open a container in the world, or time out after a specified amount of ticks.

See Self::open_container_at for more information. That function defaults to a timeout of 5 seconds (100 ticks), which is usually good enough. However to detect failures faster or to account for server lag, you may find it useful to adjust the timeout to a different value.

The timeout is measured in game ticks (on the client, not the server), i.e. 1/20th of a second.

Source

pub async fn wait_for_container_open( &self, timeout_ticks: Option<usize>, ) -> Option<ContainerHandle>

Wait until a container is open, up to the specified number of ticks.

Returns None if the container was immediately opened and closed, or if the timeout expired.

If timeout_ticks is None, there will be no timeout.

Source

pub fn open_inventory(&self) -> Option<ContainerHandle>

Open the player’s inventory.

This will return None if another container is open.

Note that this will send a packet to the server once it’s dropped. Also, due to how it’s implemented, you could call this function multiple times while another inventory handle already exists (but you shouldn’t).

If you just want to get the items in the player’s inventory without sending any packets, use Client::menu, Menu::player_slots_range, and Menu::slots.

Source

pub fn get_inventory(&self) -> ContainerHandleRef

Returns a ContainerHandleRef to the client’s currently open container, or their inventory.

This will not send a packet to close the container when it’s dropped, which may cause anticheat compatibility issues if you modify your inventory without closing it afterwards.

To simulate opening your own inventory (like pressing ‘e’) in a way that won’t trigger anticheats, use Client::open_inventory.

To open a container in the world, use Client::open_container_at.

Source

pub fn get_held_item(&self) -> ItemStack

Get the item in the bot’s hotbar that is currently being held in its main hand.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 263)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn position(&self) -> Vec3

Get the client’s position in the world, which is the same as its feet position.

This is a shortcut for **bot.component::<Position>().

To get the client’s eye position, use Self::eye_position.

Note that this value is given a default of Vec3::ZERO when it receives the login packet, its true position may be set ticks later.

Examples found in repository?
azalea/examples/steal.rs (line 58)
42async fn steal(bot: Client, state: State) -> eyre::Result<()> {
43    {
44        let mut is_stealing = state.is_stealing.lock();
45        if *is_stealing {
46            bot.chat("Already stealing");
47            return Ok(());
48        }
49        *is_stealing = true;
50    }
51
52    state.checked_chests.lock().clear();
53
54    loop {
55        let chest_block = bot
56            .world()
57            .read()
58            .find_blocks(bot.position(), &BlockKind::Chest.into())
59            .find(
60                // find the closest chest that hasn't been checked
61                |block_pos| !state.checked_chests.lock().contains(block_pos),
62            );
63        let Some(chest_block) = chest_block else {
64            break;
65        };
66
67        state.checked_chests.lock().push(chest_block);
68
69        bot.goto(RadiusGoal::new(chest_block.center(), 3.)).await;
70
71        let Some(chest) = bot.open_container_at(chest_block).await else {
72            println!("Couldn't open chest at {chest_block:?}");
73            continue;
74        };
75
76        println!("Getting contents of chest at {chest_block:?}");
77        for (index, slot) in chest.contents().unwrap_or_default().iter().enumerate() {
78            println!("Checking slot {index}: {slot:?}");
79            let ItemStack::Present(item) = slot else {
80                continue;
81            };
82            if item.kind == ItemKind::Diamond {
83                println!("clicking slot ^");
84                chest.left_click(index);
85            }
86        }
87    }
88
89    bot.chat("Done");
90
91    *state.is_stealing.lock() = false;
92
93    Ok(())
94}
More examples
Hide additional examples
azalea/examples/testbot/commands/movement.rs (line 79)
14pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
15    commands.register(
16        literal("goto")
17            .executes(|ctx: &Ctx| {
18                let source = ctx.source.lock();
19                println!("got goto");
20                // look for the sender
21                let Some(entity) = source.entity() else {
22                    source.reply("I can't see you!");
23                    return 0;
24                };
25                let position = entity.position();
26                source.reply("ok");
27                source
28                    .bot
29                    .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5))));
30                1
31            })
32            .then(literal("xz").then(argument("x", integer()).then(
33                argument("z", integer()).executes(|ctx: &Ctx| {
34                    let source = ctx.source.lock();
35                    let x = get_integer(ctx, "x").unwrap();
36                    let z = get_integer(ctx, "z").unwrap();
37                    println!("goto xz {x} {z}");
38                    source.reply("ok");
39                    source.bot.start_goto(XZGoal { x, z });
40                    1
41                }),
42            )))
43            .then(literal("radius").then(argument("radius", float()).then(
44                argument("x", integer()).then(argument("y", integer()).then(
45                    argument("z", integer()).executes(|ctx: &Ctx| {
46                        let source = ctx.source.lock();
47                        let radius = get_float(ctx, "radius").unwrap();
48                        let x = get_integer(ctx, "x").unwrap();
49                        let y = get_integer(ctx, "y").unwrap();
50                        let z = get_integer(ctx, "z").unwrap();
51                        println!("goto radius {radius}, position: {x} {y} {z}");
52                        source.reply("ok");
53                        source.bot.start_goto(RadiusGoal {
54                            pos: BlockPos::new(x, y, z).center(),
55                            radius,
56                        });
57                        1
58                    }),
59                )),
60            )))
61            .then(argument("x", integer()).then(argument("y", integer()).then(
62                argument("z", integer()).executes(|ctx: &Ctx| {
63                    let source = ctx.source.lock();
64                    let x = get_integer(ctx, "x").unwrap();
65                    let y = get_integer(ctx, "y").unwrap();
66                    let z = get_integer(ctx, "z").unwrap();
67                    println!("goto xyz {x} {y} {z}");
68                    source.reply("ok");
69                    source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z)));
70                    1
71                }),
72            ))),
73    );
74
75    commands.register(literal("down").executes(|ctx: &Ctx| {
76        let source = ctx.source.clone();
77        tokio::spawn(async move {
78            let bot = source.lock().bot.clone();
79            let position = BlockPos::from(bot.position());
80            source.lock().reply("mining...");
81            bot.mine(position.down(1)).await;
82            source.lock().reply("done");
83        });
84        1
85    }));
86
87    commands.register(
88        literal("look")
89            .executes(|ctx: &Ctx| {
90                // look for the sender
91                let source = ctx.source.lock();
92                let Some(entity) = source.entity() else {
93                    source.reply("I can't see you!");
94                    return 0;
95                };
96                let eye_position = entity.eye_position();
97                source.bot.look_at(eye_position);
98                1
99            })
100            .then(argument("x", integer()).then(argument("y", integer()).then(
101                argument("z", integer()).executes(|ctx: &Ctx| {
102                    let pos = BlockPos::new(
103                        get_integer(ctx, "x").unwrap(),
104                        get_integer(ctx, "y").unwrap(),
105                        get_integer(ctx, "z").unwrap(),
106                    );
107                    println!("{pos:?}");
108                    let source = ctx.source.lock();
109                    source.bot.look_at(pos.center());
110                    1
111                }),
112            ))),
113    );
114
115    commands.register(
116        literal("walk").then(argument("seconds", float()).executes(|ctx: &Ctx| {
117            let mut seconds = get_float(ctx, "seconds").unwrap();
118            let source = ctx.source.lock();
119            let bot = source.bot.clone();
120
121            if seconds < 0. {
122                bot.walk(WalkDirection::Backward);
123                seconds = -seconds;
124            } else {
125                bot.walk(WalkDirection::Forward);
126            }
127
128            tokio::spawn(async move {
129                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
130                bot.walk(WalkDirection::None);
131            });
132            source.reply(format!("ok, walking for {seconds} seconds"));
133            1
134        })),
135    );
136    commands.register(
137        literal("sprint").then(argument("seconds", float()).executes(|ctx: &Ctx| {
138            let seconds = get_float(ctx, "seconds").unwrap();
139            let source = ctx.source.lock();
140            let bot = source.bot.clone();
141            bot.sprint(SprintDirection::Forward);
142            tokio::spawn(async move {
143                tokio::time::sleep(Duration::from_secs_f32(seconds)).await;
144                bot.walk(WalkDirection::None);
145            });
146            source.reply(format!("ok, sprinting for {seconds} seconds"));
147            1
148        })),
149    );
150
151    commands.register(literal("north").executes(|ctx: &Ctx| {
152        let source = ctx.source.lock();
153        source.bot.set_direction(180., 0.);
154        source.reply("ok");
155        1
156    }));
157    commands.register(literal("south").executes(|ctx: &Ctx| {
158        let source = ctx.source.lock();
159        source.bot.set_direction(0., 0.);
160        source.reply("ok");
161        1
162    }));
163    commands.register(literal("east").executes(|ctx: &Ctx| {
164        let source = ctx.source.lock();
165        source.bot.set_direction(-90., 0.);
166        source.reply("ok");
167        1
168    }));
169    commands.register(literal("west").executes(|ctx: &Ctx| {
170        let source = ctx.source.lock();
171        source.bot.set_direction(90., 0.);
172        source.reply("ok");
173        1
174    }));
175    commands.register(
176        literal("jump")
177            .executes(|ctx: &Ctx| {
178                let source = ctx.source.lock();
179                source.bot.jump();
180                source.reply("ok");
181                1
182            })
183            .then(argument("enabled", bool()).executes(|ctx: &Ctx| {
184                let jumping = get_bool(ctx, "enabled").unwrap();
185                let source = ctx.source.lock();
186                source.bot.set_jumping(jumping);
187                1
188            })),
189    );
190
191    let sneak = |ctx: &Ctx| {
192        let source = ctx.source.lock();
193        source.bot.set_crouching(!source.bot.crouching());
194        source.reply("ok");
195        1
196    };
197    let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| {
198        let sneaking = get_bool(ctx, "enabled").unwrap();
199        let source = ctx.source.lock();
200        source.bot.set_crouching(sneaking);
201        1
202    });
203    commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone()));
204    commands.register(literal("crouch").executes(sneak).then(sneak_enabled));
205
206    commands.register(literal("stop").executes(|ctx: &Ctx| {
207        let source = ctx.source.lock();
208        source.bot.stop_pathfinding();
209        source.reply("ok");
210        *source.state.task.lock() = BotTask::None;
211        1
212    }));
213    commands.register(literal("forcestop").executes(|ctx: &Ctx| {
214        let source = ctx.source.lock();
215        source.bot.force_stop_pathfinding();
216        source.reply("ok");
217        *source.state.task.lock() = BotTask::None;
218        1
219    }));
220}
azalea/examples/testbot/commands/debug.rs (line 77)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn dimensions(&self) -> EntityDimensions

Get the bounding box dimensions for our client, which contains our width, height, and eye height.

This is a shortcut for self.component::<EntityDimensions>().

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 272)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn eye_position(&self) -> Vec3

Get the position of this client’s eyes.

Also see Self::position.

This is a shortcut for bot.position().up(bot.dimensions().eye_height).

Examples found in repository?
azalea/examples/testbot/killaura.rs (line 16)
9pub fn tick(bot: Client, state: State) -> eyre::Result<()> {
10    if !state.killaura {
11        return Ok(());
12    }
13    if bot.has_attack_cooldown() {
14        return Ok(());
15    }
16    let bot_position = bot.eye_position();
17
18    let nearest_entity = bot.nearest_entity_by::<&Position, (
19        With<AbstractMonster>,
20        Without<LocalEntity>,
21        Without<Dead>,
22    )>(|position: &Position| {
23        let distance = bot_position.distance_to(**position);
24        distance < 4.
25    });
26
27    if let Some(nearest_entity) = nearest_entity {
28        println!("attacking {nearest_entity:?}");
29        nearest_entity.attack();
30    }
31
32    Ok(())
33}
Source§

impl Client

Source

pub fn health(&self) -> f32

Get the health of this client, typically in the range 0..=20.

This is a shortcut for *bot.component::<Health>().

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 112)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn uuid(&self) -> Uuid

Get the Minecraft UUID of this client.

This is a shortcut for **self.component::<EntityUuid>().

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 92)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn minecraft_id(&self) -> MinecraftEntityId

Get the Minecraft ID of this client.

See MinecraftEntityId for more details. For persistent identifiers, consider using Self::uuid instead.

This is a shortcut for **self.component::<MinecraftEntityId>().

Source§

impl Client

Source

pub fn attributes(&self) -> Attributes

Returns the attribute values of our player, which can be used to determine things like our movement speed.

Examples found in repository?
azalea/examples/testbot/commands/debug.rs (line 306)
26pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
27    commands.register(literal("ping").executes(|ctx: &Ctx| {
28        let source = ctx.source.lock();
29        source.reply("pong!");
30        1
31    }));
32    commands.register(
33        literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| {
34            let source = ctx.source.lock();
35            let message = get_string(ctx, "message").unwrap();
36            source.bot.chat(message);
37            1
38        })),
39    );
40
41    commands.register(literal("disconnect").executes(|ctx: &Ctx| {
42        let source = ctx.source.lock();
43        source.bot.disconnect();
44        1
45    }));
46
47    commands.register(literal("whereami").executes(|ctx: &Ctx| {
48        let source = ctx.source.lock();
49        let Some(entity) = source.entity() else {
50            source.reply("You aren't in render distance!");
51            return 0;
52        };
53        let position = entity.position();
54        source.reply(format!(
55            "You are at {}, {}, {}",
56            position.x, position.y, position.z
57        ));
58        1
59    }));
60
61    commands.register(literal("entityid").executes(|ctx: &Ctx| {
62        let source = ctx.source.lock();
63        let Some(entity) = source.entity() else {
64            source.reply("You aren't in render distance!");
65            return 0;
66        };
67        let entity_id = entity.minecraft_id();
68        source.reply(format!(
69            "Your Minecraft ID is {} and your ECS ID is {entity:?}",
70            *entity_id
71        ));
72        1
73    }));
74
75    let whereareyou = |ctx: &Ctx| {
76        let source = ctx.source.lock();
77        let position = source.bot.position();
78        source.reply(format!(
79            "I'm at {}, {}, {}",
80            position.x, position.y, position.z
81        ));
82        1
83    };
84    commands.register(literal("whereareyou").executes(whereareyou));
85    commands.register(literal("pos").executes(whereareyou));
86
87    commands.register(literal("whoareyou").executes(|ctx: &Ctx| {
88        let source = ctx.source.lock();
89        source.reply(format!(
90            "I am {} ({}, {})",
91            source.bot.username(),
92            source.bot.uuid(),
93            source.bot.entity
94        ));
95        1
96    }));
97
98    commands.register(literal("getdirection").executes(|ctx: &Ctx| {
99        let source = ctx.source.lock();
100        let direction = source.bot.direction();
101        source.reply(format!(
102            "I'm looking at {}, {}",
103            direction.y_rot(),
104            direction.x_rot()
105        ));
106        1
107    }));
108
109    commands.register(literal("health").executes(|ctx: &Ctx| {
110        let source = ctx.source.lock();
111
112        let health = source.bot.health();
113        source.reply(format!("I have {health} health"));
114        1
115    }));
116
117    commands.register(literal("lookingat").executes(|ctx: &Ctx| {
118        let source = ctx.source.lock();
119
120        let hit_result = source.bot.hit_result();
121
122        match &hit_result {
123            HitResult::Block(r) => {
124                if r.miss {
125                    source.reply("I'm not looking at anything");
126                    return 0;
127                }
128                let block_pos = r.block_pos;
129                let block = source.bot.world().read().get_block_state(block_pos);
130                source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
131            }
132            HitResult::Entity(r) => {
133                let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
134                source.reply(format!(
135                    "I'm looking at {entity_kind} ({:?}) at {}",
136                    r.entity, r.location
137                ));
138            }
139        }
140
141        1
142    }));
143
144    commands.register(literal("getblock").then(argument("x", integer()).then(
145        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
146            let source = ctx.source.lock();
147            let x = get_integer(ctx, "x").unwrap();
148            let y = get_integer(ctx, "y").unwrap();
149            let z = get_integer(ctx, "z").unwrap();
150            println!("getblock xyz {x} {y} {z}");
151            let block_pos = BlockPos::new(x, y, z);
152            let block = source.bot.world().read().get_block_state(block_pos);
153            source.reply(format!("BlockKind at {block_pos} is {block:?}"));
154            1
155        })),
156    )));
157    commands.register(literal("getfluid").then(argument("x", integer()).then(
158        argument("y", integer()).then(argument("z", integer()).executes(|ctx: &Ctx| {
159            let source = ctx.source.lock();
160            let x = get_integer(ctx, "x").unwrap();
161            let y = get_integer(ctx, "y").unwrap();
162            let z = get_integer(ctx, "z").unwrap();
163            println!("getfluid xyz {x} {y} {z}");
164            let block_pos = BlockPos::new(x, y, z);
165            let block = source.bot.world().read().get_fluid_state(block_pos);
166            source.reply(format!("Fluid at {block_pos} is {block:?}"));
167            1
168        })),
169    )));
170
171    commands.register(literal("inventory").executes(|ctx: &Ctx| {
172        let source = ctx.source.lock();
173        for item in source.bot.menu().slots() {
174            if item.is_empty() {
175                continue;
176            }
177            println!("{item:?}");
178            for (kind, data) in item.component_patch().iter() {
179                if let Some(data) = data {
180                    println!("- {kind} {data:?}");
181                }
182            }
183        }
184        1
185    }));
186
187    commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| {
188        let source = ctx.source.lock();
189        let pathfinder = source.bot.get_component::<Pathfinder>();
190        let Some(pathfinder) = pathfinder else {
191            source.reply("I don't have the Pathfinder component");
192            return 1;
193        };
194        source.reply(format!(
195            "pathfinder.is_calculating: {}",
196            pathfinder.is_calculating
197        ));
198
199        let executing_path = source.bot.get_component::<ExecutingPath>();
200        let Some(executing_path) = executing_path else {
201            source.reply("I'm not executing a path");
202            return 1;
203        };
204        source.reply(format!(
205            "is_path_partial: {}, path.len: {}, queued_path.len: {}",
206            executing_path.is_path_partial,
207            executing_path.path.len(),
208            if let Some(queued) = &executing_path.queued_path {
209                queued.len().to_string()
210            } else {
211                "n/a".to_owned()
212            },
213        ));
214        1
215    }));
216    commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| {
217        let source = ctx.source.lock();
218
219        let Some(entity) = source.entity() else {
220            source.reply("You aren't in render distance!");
221            return 0;
222        };
223        let position = entity.position();
224        let position = BlockPos::from(position);
225
226        let mut edges = Vec::new();
227        let cached_world = CachedWorld::new(source.bot.world(), position);
228        let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default())));
229        let custom_state = CustomPathfinderStateRef::default();
230
231        azalea::pathfinder::moves::default_move(
232            &mut MovesCtx {
233                edges: &mut edges,
234                world: &cached_world,
235                mining_cache: &mining_cache,
236                custom_state: &custom_state,
237            },
238            RelBlockPos::from_origin(position, position),
239        );
240
241        if edges.is_empty() {
242            source.reply("No possible moves.");
243        } else {
244            source.reply("Moves:");
245            for (i, edge) in edges.iter().enumerate() {
246                source.reply(format!("{}) {edge:?}", i + 1));
247            }
248        }
249
250        1
251    }));
252
253    commands.register(literal("startuseitem").executes(|ctx: &Ctx| {
254        let source = ctx.source.lock();
255        source.bot.start_use_item();
256        source.reply("Ok!");
257        1
258    }));
259    commands.register(literal("maxstacksize").executes(|ctx: &Ctx| {
260        let source = ctx.source.lock();
261        let max_stack_size = source
262            .bot
263            .get_held_item()
264            .get_component::<MaxStackSize>()
265            .map_or(-1, |s| s.count);
266        source.reply(format!("{max_stack_size}"));
267        1
268    }));
269
270    commands.register(literal("dimensions").executes(|ctx: &Ctx| {
271        let source = ctx.source.lock();
272        let bot_dimensions = source.bot.dimensions();
273        source.reply(format!("{bot_dimensions:?}"));
274        1
275    }));
276
277    commands.register(literal("players").executes(|ctx: &Ctx| {
278        let source = ctx.source.lock();
279        let player_entities = source
280            .bot
281            .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| true);
282        let tab_list = source.bot.tab_list();
283        for player_entity in player_entities {
284            let uuid = player_entity.uuid();
285            source.reply(format!(
286                "{} - {} ({:?})",
287                player_entity.id(),
288                tab_list.get(&uuid).map_or("?", |p| p.profile.name.as_str()),
289                uuid
290            ));
291        }
292        1
293    }));
294
295    commands.register(literal("enchants").executes(|ctx: &Ctx| {
296        let source = ctx.source.lock();
297        source.bot.with_registry_holder(|r| {
298            let enchants = &r.enchantment;
299            println!("enchants: {enchants:?}");
300        });
301        1
302    }));
303
304    commands.register(literal("attributes").executes(|ctx: &Ctx| {
305        let source = ctx.source.lock();
306        let attributes = source.bot.attributes();
307        println!("attributes: {attributes:?}");
308        1
309    }));
310
311    commands.register(literal("debugecsleak").executes(|ctx: &Ctx| {
312        let source = ctx.source.lock();
313
314        source.reply("Ok!");
315
316
317
318        source.bot.disconnect();
319
320        let ecs = source.bot.ecs.clone();
321        thread::spawn(move || {
322            thread::sleep(Duration::from_secs(1));
323            // dump the ecs
324
325            let mut ecs = ecs.write();
326
327            let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
328            let mut report = File::create(&report_path).unwrap();
329
330            let mut query = ecs.query::<EntityRef>();
331            for entity in query.iter(& ecs) {
332                writeln!(report, "Entity: {}", entity.id()).unwrap();
333                let archetype = entity.archetype();
334                let component_count = archetype.component_count();
335
336                let component_names = archetype
337                    .components()
338                    .iter()
339                    .map(|c| ecs.components().get_info(*c).unwrap().name().to_string())
340                    .collect::<Vec<_>>();
341                writeln!(
342                    report,
343                    "- {component_count} components: {}",
344                    component_names.join(", ")
345                )
346                .unwrap();
347            }
348
349            writeln!(report).unwrap();
350
351
352            for (info, _) in ecs.iter_resources() {
353                let name = info.name().to_string();
354                writeln!(report, "Resource: {name}").unwrap();
355                // writeln!(report, "- Size: {} bytes",
356                // info.layout().size()).unwrap();
357
358                match name.as_ref() {
359                    "azalea_world::container::Worlds" => {
360                        let worlds = ecs.resource::<Worlds>();
361
362                        for (world_name, world) in &worlds.map {
363                            writeln!(report, "- Name: {world_name}").unwrap();
364                            writeln!(report, "- Reference count: {}", world.strong_count())
365                                .unwrap();
366                            if let Some(world) = world.upgrade() {
367                                let world = world.read();
368                                let chunks = &world.chunks;
369                                let chunks = (chunks as &dyn Any).downcast_ref::<WeakChunkStorage>();
370                                if let Some(chunks) = chunks {
371                                    let strong_chunks = chunks
372                                        .map
373                                        .iter()
374                                        .filter(|(_, v)| v.strong_count() > 0)
375                                        .count();
376                                    writeln!(
377                                        report,
378                                        "- Chunks: {} strongly referenced, {} in map",
379                                        strong_chunks,
380                                        chunks.map.len()
381                                    )
382                                    .unwrap();
383                                }
384                                writeln!(
385                                    report,
386                                    "- Entities: {}",
387                                    world.entities_by_chunk.len()
388                                )
389                                .unwrap();
390                            }
391                        }
392                    }
393                    "bevy_ecs::message::Messages<azalea_client::packet::game::ReceivePacketEvent>" => {
394                        let events = ecs.resource::<Messages<game::ReceiveGamePacketEvent>>();
395                        writeln!(report, "- Event count: {}", events.len()).unwrap();
396                    }
397                    "bevy_ecs::message::Messages<azalea_client::chunks::ReceiveChunkEvent>" => {
398                        let events = ecs.resource::<Messages<ReceiveChunkEvent>>();
399                        writeln!(report, "- Event count: {}", events.len()).unwrap();
400                    }
401
402                    _ => {}
403                }
404            }
405
406            println!("\x1b[1mWrote report to {}\x1b[m", report_path.display());
407        });
408
409        1
410    }));
411
412    commands.register(literal("exit").executes(|ctx: &Ctx| {
413        let source = ctx.source.lock();
414        source.reply("bye!");
415
416        source.bot.disconnect();
417
418        let source = ctx.source.clone();
419        thread::spawn(move || {
420            thread::sleep(Duration::from_secs(1));
421
422            source
423                .lock()
424                .bot
425                .ecs
426                .write()
427                .write_message(AppExit::Success);
428        });
429
430        1
431    }));
432}
Source§

impl Client

Source

pub fn instance_name(&self) -> WorldName

👎Deprecated:

renamed to world_name.

Source§

impl Client

Source

pub fn world_name(&self) -> WorldName

Get the name of the world that the bot is in.

This can be used to check if the client is in the same world as another entity.

Source§

impl Client

Source

pub fn is_alive(&self) -> bool

Returns whether the client is alive and in the world.

You should avoid using this if you have auto-respawn enabled (which is the default), instead consider watching for Event::Death instead.

Also see Self::exists.

Source§

impl Client

Source

pub fn exists(&self) -> bool

Returns whether the client is in the world (has been assigned an entity ID).

Like Self::is_alive, this will not panic.

Source§

impl Client

Source

pub fn physics(&self) -> Physics

Returns the complete [Physics] data for this client, including velocity, bounding box, collisions, etc.

Source§

impl Client

Source

pub fn get_tick_broadcaster(&self) -> Receiver<()>

Returns a Receiver that receives a message every game tick.

This is useful if you want to efficiently loop until a certain condition is met.

let mut ticks = bot.get_tick_broadcaster();
while ticks.recv().await.is_ok() {
    let ecs = bot.ecs.read();
    if ecs.get::<WaitingForInventoryOpen>(bot.entity).is_none() {
        break;
    }
}
Source

pub fn get_update_broadcaster(&self) -> Receiver<()>

Returns a Receiver that receives a message every ECS Update.

ECS Updates happen at least at the frequency of game ticks, usually faster.

This is useful if you’re sending an ECS event and want to make sure it’s been handled before continuing.

Trait Implementations§

Source§

impl Clone for Client

Source§

fn clone(&self) -> Client

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl PathfinderClientExt for Client

Source§

async fn goto(&self, goal: impl Goal + 'static)

Pathfind to the given goal and wait until either the target is reached or the pathfinding is canceled. Read more
Source§

async fn goto_with_opts(&self, goal: impl Goal + 'static, opts: PathfinderOpts)

Same as Self::goto, but allows you to set custom options for pathfinding, including disabling mining and setting custom moves. Read more
Source§

fn start_goto(&self, goal: impl Goal + 'static)

Start pathfinding to a given goal. Read more
Source§

fn start_goto_with_opts(&self, goal: impl Goal + 'static, opts: PathfinderOpts)

Same as Self::start_goto, but allows you to set custom options for pathfinding, including disabling mining and setting custom moves. Read more
Source§

fn stop_pathfinding(&self)

Stop calculating a path, and stop moving once the current movement is finished. Read more
Source§

fn force_stop_pathfinding(&self)

Stop calculating a path and stop executing the current movement immediately.
Source§

async fn wait_until_goto_target_reached(&self)

Waits forever until the bot no longer has a pathfinder goal.
Source§

fn is_goto_target_reached(&self) -> bool

Returns true if the pathfinder has no active goal and isn’t calculating a path.
Source§

fn is_executing_path(&self) -> bool

Whether the pathfinder is currently following a path. Read more
Source§

fn is_calculating_path(&self) -> bool

Whether the pathfinder is currently calculating a path. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> CompatExt for T

§

fn compat(self) -> Compat<T>

Applies the [Compat] adapter by value. Read more
§

fn compat_ref(&self) -> Compat<&T>

Applies the [Compat] adapter by shared reference. Read more
§

fn compat_mut(&mut self) -> Compat<&mut T>

Applies the [Compat] adapter by mutable reference. Read more
§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
§

impl<T> DowncastSend for T
where T: Any + Send,

§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> IntoResult<T> for T

§

fn into_result(self) -> Result<T, RunSystemError>

Converts this type into the system output type.
§

impl<A> Is for A
where A: Any,

§

fn is<T>() -> bool
where T: Any,

Checks if the current type “is” another type, using a TypeId equality comparison. This is most useful in the context of generic logic. Read more
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> TypeData for T
where T: 'static + Send + Sync + Clone,

§

fn clone_type_data(&self) -> Box<dyn TypeData>

Creates a type-erased clone of this value.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<T> ConditionalSend for T
where T: Send,