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