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