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: EntityThe 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
impl Client
pub fn best_tool_in_hotbar_for_block(&self, block: BlockState) -> BestToolResult
pub async fn mine_with_auto_tool(&self, block_pos: BlockPos)
Source§impl Client
impl Client
Sourcepub fn jump(&self)
pub fn jump(&self)
Queue a jump for the next tick.
Examples found in repository?
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}Sourcepub fn look_at(&self, position: Vec3)
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?
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}Sourcepub async fn wait_ticks(&self, n: usize)
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.
Sourcepub async fn wait_updates(&self, n: usize)
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.
Sourcepub async fn mine(&self, position: BlockPos)
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?
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
impl Client
Sourcepub fn attack(&self, entity: Entity)
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.
Sourcepub fn has_attack_cooldown(&self) -> bool
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?
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}Sourcepub fn attack_cooldown_remaining_ticks(&self) -> usize
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
impl Client
Sourcepub fn write_chat_packet(&self, message: &str)
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.
Sourcepub fn write_command_packet(&self, command: &str)
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.
Sourcepub fn chat(&self, content: impl Into<String>)
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?
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
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}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
impl Client
Sourcepub fn set_client_information(&self, client_information: ClientInformation)
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?
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
impl Client
Sourcepub fn component<T: Component>(&self) -> MappedRwLockReadGuard<'_, T>
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>();Sourcepub fn get_component<T: Component>(
&self,
) -> Option<MappedRwLockReadGuard<'_, T>>
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?
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}Sourcepub fn query_self<D: QueryData, R>(
&self,
f: impl FnOnce(QueryItem<'_, '_, D>) -> R,
) -> R
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?
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}Sourcepub fn try_query_self<D: QueryData, R>(
&self,
f: impl FnOnce(QueryItem<'_, '_, D>) -> R,
) -> Result<R, QueryEntityError>
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.
Sourcepub fn query_entity<D: QueryData, R>(
&self,
entity: Entity,
f: impl FnOnce(QueryItem<'_, '_, D>) -> R,
) -> R
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.
Sourcepub fn try_query_entity<D: QueryData, R>(
&self,
entity: Entity,
f: impl FnOnce(QueryItem<'_, '_, D>) -> R,
) -> Result<R, QueryEntityError>
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.
Sourcepub fn any_entity_by<Q: QueryData, F: QueryFilter>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Option<EntityRef>
pub fn any_entity_by<Q: QueryData, F: QueryFilter>( &self, predicate: impl EntityPredicate<Q, F>, ) -> Option<EntityRef>
Sourcepub fn any_entity_id_by<Q: QueryData, F: QueryFilter>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Option<Entity>
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,
);Sourcepub fn nearest_entity_by<Q: QueryData, F: QueryFilter>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Option<EntityRef>
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?
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}Sourcepub fn nearest_entity_id_by<Q: QueryData, F: QueryFilter>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Option<Entity>
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.
Sourcepub fn nearest_entities_by<Q: QueryData, F: QueryFilter>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Box<[EntityRef]>
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?
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}Sourcepub fn nearest_entity_ids_by<Q: QueryData, F: QueryFilter>(
&self,
predicate: impl EntityPredicate<Q, F>,
) -> Box<[Entity]>
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);Sourcepub fn entity_component<T: Component>(
&self,
entity: Entity,
) -> MappedRwLockReadGuard<'_, T>
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?
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}Sourcepub fn get_entity_component<T: Component>(
&self,
entity: Entity,
) -> Option<MappedRwLockReadGuard<'_, T>>
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
impl Client
Sourcepub fn hit_result(&self) -> HitResult
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?
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}Sourcepub fn block_interact(&self, position: BlockPos)
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.
Sourcepub fn entity_interact(&self, entity: Entity)
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.
Sourcepub fn start_use_item(&self)
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?
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
impl Client
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?
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}Sourcepub fn selected_hotbar_slot(&self) -> u8
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.
Sourcepub fn set_selected_hotbar_slot(&self, new_hotbar_slot_index: u8)
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
impl Client
pub fn start_mining(&self, position: BlockPos)
Sourcepub fn is_mining(&self) -> bool
pub fn is_mining(&self) -> bool
Returns true if the client is currently trying to mine a block.
Sourcepub fn left_click_mine(&self, enabled: bool)
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
impl Client
Sourcepub fn set_jumping(&self, jumping: bool)
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?
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}Sourcepub fn set_crouching(&self, crouching: bool)
pub fn set_crouching(&self, crouching: bool)
Examples found in repository?
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}Sourcepub fn crouching(&self) -> bool
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?
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}Sourcepub fn set_direction(&self, y_rot: f32, x_rot: f32)
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?
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}Sourcepub fn direction(&self) -> LookDirection
pub fn direction(&self) -> LookDirection
Returns the direction the client is looking.
See Self::set_direction for more details.
Examples found in repository?
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}Sourcepub fn walk(&self, direction: WalkDirection)
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?
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}Sourcepub fn sprint(&self, direction: SprintDirection)
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?
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
impl Client
Sourcepub fn new(entity: Entity, ecs: Arc<RwLock<World>>) -> Self
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.
Sourcepub async fn join(
account: Account,
address: impl ResolvableAddr,
) -> Result<(Self, UnboundedReceiver<Event>), ResolveError>
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(())
}pub async fn join_with_proxy( account: Account, address: impl ResolvableAddr, proxy: Proxy, ) -> Result<(Self, UnboundedReceiver<Event>), ResolveError>
Sourcepub async fn start_client(__arg0: StartClientOpts) -> Self
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.
Sourcepub fn write_packet(&self, packet: impl Packet<ServerboundGamePacket>)
pub fn write_packet(&self, packet: impl Packet<ServerboundGamePacket>)
Write a packet directly to the server.
Sourcepub fn disconnect(&self)
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?
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}Sourcepub fn exit(&self)
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(())
}pub fn with_raw_connection<R>(&self, f: impl FnOnce(&RawConnection) -> R) -> R
pub fn with_raw_connection_mut<R>( &self, f: impl FnOnce(Mut<'_, RawConnection>) -> R, ) -> R
Sourcepub fn resource<T: Resource + Clone>(&self) -> T
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?
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}Sourcepub fn map_resource<T: Resource, R>(&self, f: impl FnOnce(&T) -> R) -> R
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.
Sourcepub fn map_get_resource<T: Resource, R>(
&self,
f: impl FnOnce(Option<&T>) -> R,
) -> R
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.
Sourcepub fn world(&self) -> Arc<RwLock<World>>
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?
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
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}Sourcepub fn partial_world(&self) -> Arc<RwLock<PartialWorld>>
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();Sourcepub fn entity(&self) -> EntityRef
pub fn entity(&self) -> EntityRef
Returns the client as an EntityRef, allowing you to treat it as any
other entity.
Sourcepub fn entity_ref_for(&self, entity: Entity) -> EntityRef
pub fn entity_ref_for(&self, entity: Entity) -> EntityRef
Create an EntityRef for the given ECS entity.
Source§impl Client
impl Client
Sourcepub fn hunger(&self) -> Hunger
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().
Sourcepub fn experience(&self) -> Experience
pub fn experience(&self) -> Experience
Get the experience of this client.
This is a shortcut for self.component::<Experience>().to_owned().
Sourcepub fn username(&self) -> String
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?
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
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}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}Sourcepub fn tab_list(&self) -> HashMap<Uuid, PlayerInfo>
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?
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}Sourcepub fn profile(&self) -> GameProfile
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.
Sourcepub fn player_uuid_by_username(&self, username: &str) -> Option<Uuid>
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.
Sourcepub fn entity_id_by_uuid(&self, uuid: Uuid) -> Option<Entity>
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.
Sourcepub fn entity_by_uuid(&self, uuid: Uuid) -> Option<EntityRef>
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.
Sourcepub fn entity_id_by_minecraft_id(&self, id: MinecraftEntityId) -> Option<Entity>
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.
Sourcepub fn entity_by_minecraft_id(&self, id: MinecraftEntityId) -> Option<EntityRef>
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.
Sourcepub fn with_registry_holder<R>(&self, f: impl FnOnce(&RegistryHolder) -> R) -> R
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?
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}Sourcepub fn resolve_registry_name(
&self,
registry: &impl ResolvableDataRegistry,
) -> Option<Identifier>
👎Deprecated: use bot.resolve_registry_key(registry).map(|r| r.into_ident()) instead.
pub fn resolve_registry_name( &self, registry: &impl ResolvableDataRegistry, ) -> Option<Identifier>
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.
Sourcepub fn resolve_registry_key<R: ResolvableDataRegistry>(
&self,
registry: &R,
) -> Option<R::Key>
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()).
Sourcepub fn with_resolved_registry<R: ResolvableDataRegistry, Ret>(
&self,
registry: R,
f: impl FnOnce(&Identifier, &R::DeserializesTo) -> Ret,
) -> Option<Ret>
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.
Sourcepub fn ticks_connected(&self) -> u64
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
impl Client
Sourcepub async fn open_container_at(&self, pos: BlockPos) -> Option<ContainerHandle>
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?
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}Sourcepub async fn open_container_at_with_timeout_ticks(
&self,
pos: BlockPos,
timeout_ticks: Option<usize>,
) -> Option<ContainerHandle>
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.
Sourcepub async fn wait_for_container_open(
&self,
timeout_ticks: Option<usize>,
) -> Option<ContainerHandle>
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.
Sourcepub fn open_inventory(&self) -> Option<ContainerHandle>
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.
Sourcepub fn get_inventory(&self) -> ContainerHandleRef
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.
Sourcepub fn get_held_item(&self) -> ItemStack
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?
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
impl Client
Sourcepub fn position(&self) -> Vec3
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?
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
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}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
impl Client
Sourcepub fn dimensions(&self) -> EntityDimensions
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?
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
impl Client
Sourcepub fn eye_position(&self) -> Vec3
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?
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
impl Client
Sourcepub fn health(&self) -> f32
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?
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
impl Client
Sourcepub fn uuid(&self) -> Uuid
pub fn uuid(&self) -> Uuid
Get the Minecraft UUID of this client.
This is a shortcut for **self.component::<EntityUuid>().
Examples found in repository?
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
impl Client
Sourcepub fn minecraft_id(&self) -> MinecraftEntityId
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
impl Client
Sourcepub fn attributes(&self) -> Attributes
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?
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
impl Client
Sourcepub fn world_name(&self) -> WorldName
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
impl Client
Sourcepub fn is_alive(&self) -> bool
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
impl Client
Sourcepub fn exists(&self) -> bool
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
impl Client
Sourcepub fn get_tick_broadcaster(&self) -> Receiver<()>
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;
}
}Sourcepub fn get_update_broadcaster(&self) -> Receiver<()>
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 PathfinderClientExt for Client
impl PathfinderClientExt for Client
Source§async fn goto(&self, goal: impl Goal + 'static)
async fn goto(&self, goal: impl Goal + 'static)
Source§async fn goto_with_opts(&self, goal: impl Goal + 'static, opts: PathfinderOpts)
async fn goto_with_opts(&self, goal: impl Goal + 'static, opts: PathfinderOpts)
Self::goto, but allows you to set custom options for
pathfinding, including disabling mining and setting custom moves. Read moreSource§fn start_goto(&self, goal: impl Goal + 'static)
fn start_goto(&self, goal: impl Goal + 'static)
Source§fn start_goto_with_opts(&self, goal: impl Goal + 'static, opts: PathfinderOpts)
fn start_goto_with_opts(&self, goal: impl Goal + 'static, opts: PathfinderOpts)
Self::start_goto, but allows you to set custom
options for pathfinding, including disabling mining and setting custom
moves. Read moreSource§fn stop_pathfinding(&self)
fn stop_pathfinding(&self)
Source§fn force_stop_pathfinding(&self)
fn force_stop_pathfinding(&self)
Source§async fn wait_until_goto_target_reached(&self)
async fn wait_until_goto_target_reached(&self)
Source§fn is_goto_target_reached(&self) -> bool
fn is_goto_target_reached(&self) -> bool
Source§fn is_executing_path(&self) -> bool
fn is_executing_path(&self) -> bool
Source§fn is_calculating_path(&self) -> bool
fn is_calculating_path(&self) -> bool
Auto Trait Implementations§
impl Freeze for Client
impl !RefUnwindSafe for Client
impl Send for Client
impl Sync for Client
impl Unpin for Client
impl UnsafeUnpin for Client
impl !UnwindSafe for Client
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> CompatExt for T
impl<T> CompatExt for T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
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>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
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)
fn as_any(&self) -> &(dyn Any + 'static)
&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)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.