1use std::sync::Arc;
4
5use azalea_client::{
6 ClientMovementState, interact::BlockStatePredictionHandler, local_player::PreviousGameMode,
7 mining::MineBundle,
8};
9use azalea_core::{
10 entity_id::MinecraftEntityId, game_type::GameMode, position::Vec3, tick::GameTick,
11};
12use azalea_entity::{
13 Attributes, LookDirection, Physics, Position, dimensions::EntityDimensions,
14 inventory::Inventory,
15};
16use azalea_registry::builtin::EntityKind;
17use azalea_world::{ChunkStorage, PartialWorld, World, WorldName, Worlds};
18use bevy_app::App;
19use bevy_ecs::{prelude::*, schedule::SingleThreadedExecutor};
20use parking_lot::RwLock;
21use uuid::Uuid;
22
23#[derive(Bundle, Clone)]
24pub struct SimulatedPlayerBundle {
25 pub position: Position,
26 pub physics: Physics,
27 pub physics_state: ClientMovementState,
28 pub look_direction: LookDirection,
29 pub attributes: Attributes,
30 pub inventory: Inventory,
31}
32
33impl SimulatedPlayerBundle {
34 pub fn new(position: Vec3) -> Self {
35 let dimensions = EntityDimensions::from(EntityKind::Player);
36
37 SimulatedPlayerBundle {
38 position: Position::new(position),
39 physics: Physics::new(&dimensions, position),
40 physics_state: ClientMovementState::default(),
41 look_direction: LookDirection::default(),
42 attributes: Attributes::new(EntityKind::Player),
43 inventory: Inventory::default(),
44 }
45 }
46}
47
48fn simulation_world_name() -> WorldName {
49 WorldName::new("azalea:simulation")
50}
51
52fn create_simulation_world(chunks: ChunkStorage) -> (App, Arc<RwLock<World>>) {
53 let world_name = simulation_world_name();
54
55 let world = Arc::new(RwLock::new(World {
56 chunks,
57 ..Default::default()
58 }));
59
60 let mut app = App::new();
61 app.add_plugins((
63 azalea_physics::PhysicsPlugin,
64 azalea_entity::EntityPlugin,
65 azalea_client::movement::MovementPlugin,
66 super::PathfinderPlugin,
67 crate::bot::BotPlugin,
68 azalea_client::task_pool::TaskPoolPlugin::default(),
69 azalea_client::inventory::InventoryPlugin,
71 azalea_client::mining::MiningPlugin,
72 azalea_client::interact::InteractPlugin,
73 azalea_client::loading::PlayerLoadedPlugin,
74 ))
75 .insert_resource(Worlds {
76 map: [(world_name.clone(), Arc::downgrade(&world.clone()))]
77 .iter()
78 .cloned()
79 .collect(),
80 });
81
82 app.edit_schedule(bevy_app::Main, |schedule| {
83 schedule.set_executor(SingleThreadedExecutor::new());
84 });
85
86 app.finish();
87
88 (app, world)
89}
90
91fn create_simulation_player_complete_bundle(
92 world: Arc<RwLock<World>>,
93 player: &SimulatedPlayerBundle,
94) -> impl Bundle {
95 let world_name = simulation_world_name();
96
97 (
98 MinecraftEntityId(0),
99 azalea_entity::LocalEntity,
100 azalea_entity::metadata::PlayerMetadataBundle::default(),
101 azalea_entity::EntityBundle::new(
102 Uuid::nil(),
103 *player.position,
104 EntityKind::Player,
105 world_name,
106 ),
107 azalea_client::local_player::WorldHolder {
108 partial: Arc::new(RwLock::new(PartialWorld::default())),
110 shared: world.clone(),
111 },
112 Inventory::default(),
113 GameMode::Survival,
114 PreviousGameMode(None),
115 MineBundle::default(),
116 BlockStatePredictionHandler::default(),
117 azalea_client::local_player::PermissionLevel::default(),
118 azalea_entity::PlayerAbilities::default(),
119 )
120}
121
122pub fn create_simulation_player(
123 ecs: &mut bevy_ecs::world::World,
124 world: Arc<RwLock<World>>,
125 player: SimulatedPlayerBundle,
126) -> Entity {
127 let mut entity = ecs.spawn(create_simulation_player_complete_bundle(world, &player));
128 entity.insert(player);
129 entity.id()
130}
131
132pub struct Simulation {
134 pub app: App,
135 pub entity: Entity,
136 pub world: Arc<RwLock<World>>,
137}
138
139impl Simulation {
140 pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self {
141 let (mut app, world) = create_simulation_world(chunks);
142 let entity = create_simulation_player(app.world_mut(), world.clone(), player);
143 Self { app, entity, world }
144 }
145
146 pub fn reset(&mut self, player: SimulatedPlayerBundle) {
150 self.app.world_mut().despawn(self.entity);
151 let entity = create_simulation_player(self.app.world_mut(), self.world.clone(), player);
152 self.entity = entity;
153 }
154
155 pub fn tick(&mut self) {
156 self.run_update_schedule();
157 self.run_gametick_schedule();
158 }
159
160 pub fn run_update_schedule(&mut self) {
161 self.app.update();
162 }
163 pub fn run_gametick_schedule(&mut self) {
164 self.app.world_mut().run_schedule(GameTick);
165 }
166
167 pub fn component<T: Component + Clone>(&self) -> T {
168 self.app.world().get::<T>(self.entity).unwrap().clone()
169 }
170 pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
171 self.app.world().get::<T>(self.entity).cloned()
172 }
173 pub fn position(&self) -> Vec3 {
174 *self.component::<Position>()
175 }
176 pub fn physics(&self) -> Physics {
177 self.component::<Physics>().clone()
178 }
179 pub fn is_mining(&self) -> bool {
180 self.get_component::<azalea_client::mining::MineBlockPos>()
182 .and_then(|c| *c)
183 .is_some()
184 }
185}
186
187pub struct SimulationSet {
189 pub app: App,
190 world: Arc<RwLock<World>>,
191}
192impl SimulationSet {
193 pub fn new(chunks: ChunkStorage) -> Self {
194 let (app, world) = create_simulation_world(chunks);
195 Self { app, world }
196 }
197 pub fn tick(&mut self) {
198 self.app.update();
199 self.app.world_mut().run_schedule(GameTick);
200 }
201
202 pub fn spawn(&mut self, player: SimulatedPlayerBundle) -> Entity {
203 create_simulation_player(self.app.world_mut(), self.world.clone(), player)
204 }
205 pub fn despawn(&mut self, entity: Entity) {
206 self.app.world_mut().despawn(entity);
207 }
208
209 pub fn position(&self, entity: Entity) -> Vec3 {
210 **self.app.world().get::<Position>(entity).unwrap()
211 }
212}