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