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, EntityDimensions, LookDirection, Physics, Position, default_attributes,
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 ))
73 .insert_resource(InstanceContainer {
74 instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))]
75 .iter()
76 .cloned()
77 .collect(),
78 })
79 .add_event::<SendPacketEvent>();
80
81 app.edit_schedule(bevy_app::Main, |schedule| {
82 schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
83 });
84
85 (app, instance)
86}
87
88fn create_simulation_player_complete_bundle(
89 instance: Arc<RwLock<Instance>>,
90 player: &SimulatedPlayerBundle,
91) -> impl Bundle {
92 let instance_name = simulation_instance_name();
93
94 (
95 MinecraftEntityId(0),
96 azalea_entity::LocalEntity,
97 azalea_entity::metadata::PlayerMetadataBundle::default(),
98 azalea_entity::EntityBundle::new(
99 Uuid::nil(),
100 *player.position,
101 azalea_registry::EntityKind::Player,
102 instance_name,
103 ),
104 azalea_client::local_player::InstanceHolder {
105 partial_instance: Arc::new(RwLock::new(PartialInstance::default())),
107 instance: instance.clone(),
108 },
109 Inventory::default(),
110 LocalGameMode::from(GameMode::Survival),
111 MineBundle::default(),
112 BlockStatePredictionHandler::default(),
113 azalea_client::local_player::PermissionLevel::default(),
114 azalea_client::local_player::PlayerAbilities::default(),
115 )
116}
117
118fn create_simulation_player(
119 ecs: &mut World,
120 instance: Arc<RwLock<Instance>>,
121 player: SimulatedPlayerBundle,
122) -> Entity {
123 let mut entity = ecs.spawn(create_simulation_player_complete_bundle(instance, &player));
124 entity.insert(player);
125 entity.id()
126}
127
128pub struct Simulation {
130 pub app: App,
131 pub entity: Entity,
132 _instance: Arc<RwLock<Instance>>,
133}
134
135impl Simulation {
136 pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self {
137 let (mut app, instance) = create_simulation_instance(chunks);
138 let entity = create_simulation_player(app.world_mut(), instance.clone(), player);
139 Self {
140 app,
141 entity,
142 _instance: instance,
143 }
144 }
145
146 pub fn tick(&mut self) {
147 self.app.update();
148 self.app.world_mut().run_schedule(GameTick);
149 }
150 pub fn component<T: Component + Clone>(&self) -> T {
151 self.app.world().get::<T>(self.entity).unwrap().clone()
152 }
153 pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
154 self.app.world().get::<T>(self.entity).cloned()
155 }
156 pub fn position(&self) -> Vec3 {
157 *self.component::<Position>()
158 }
159 pub fn is_mining(&self) -> bool {
160 self.get_component::<azalea_client::mining::MineBlockPos>()
162 .and_then(|c| *c)
163 .is_some()
164 }
165}
166
167pub struct SimulationSet {
169 pub app: App,
170 instance: Arc<RwLock<Instance>>,
171}
172impl SimulationSet {
173 pub fn new(chunks: ChunkStorage) -> Self {
174 let (app, instance) = create_simulation_instance(chunks);
175 Self { app, instance }
176 }
177 pub fn tick(&mut self) {
178 self.app.update();
179 self.app.world_mut().run_schedule(GameTick);
180 }
181
182 pub fn spawn(&mut self, player: SimulatedPlayerBundle) -> Entity {
183 create_simulation_player(self.app.world_mut(), self.instance.clone(), player)
184 }
185 pub fn despawn(&mut self, entity: Entity) {
186 self.app.world_mut().despawn(entity);
187 }
188
189 pub fn position(&self, entity: Entity) -> Vec3 {
190 **self.app.world().get::<Position>(entity).unwrap()
191 }
192}