1use std::sync::Arc;
4
5use azalea_client::{
6 PhysicsState, interact::CurrentSequenceNumber, 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, attributes::AttributeInstance,
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: Attributes {
42 speed: AttributeInstance::new(0.1),
43 attack_speed: AttributeInstance::new(4.0),
44 water_movement_efficiency: AttributeInstance::new(0.0),
45 },
46 inventory: Inventory::default(),
47 }
48 }
49}
50
51fn simulation_instance_name() -> ResourceLocation {
52 ResourceLocation::new("azalea:simulation")
53}
54
55fn create_simulation_instance(chunks: ChunkStorage) -> (App, Arc<RwLock<Instance>>) {
56 let instance_name = simulation_instance_name();
57
58 let instance = Arc::new(RwLock::new(Instance {
59 chunks,
60 ..Default::default()
61 }));
62
63 let mut app = App::new();
64 app.add_plugins((
66 azalea_physics::PhysicsPlugin,
67 azalea_entity::EntityPlugin,
68 azalea_client::movement::MovementPlugin,
69 super::PathfinderPlugin,
70 crate::BotPlugin,
71 azalea_client::task_pool::TaskPoolPlugin::default(),
72 azalea_client::inventory::InventoryPlugin,
74 azalea_client::mining::MiningPlugin,
75 azalea_client::interact::InteractPlugin,
76 ))
77 .insert_resource(InstanceContainer {
78 instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))]
79 .iter()
80 .cloned()
81 .collect(),
82 })
83 .add_event::<SendPacketEvent>();
84
85 app.edit_schedule(bevy_app::Main, |schedule| {
86 schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
87 });
88
89 (app, instance)
90}
91
92fn create_simulation_player_complete_bundle(
93 instance: Arc<RwLock<Instance>>,
94 player: &SimulatedPlayerBundle,
95) -> impl Bundle {
96 let instance_name = simulation_instance_name();
97
98 (
99 MinecraftEntityId(0),
100 azalea_entity::LocalEntity,
101 azalea_entity::metadata::PlayerMetadataBundle::default(),
102 azalea_entity::EntityBundle::new(
103 Uuid::nil(),
104 *player.position,
105 azalea_registry::EntityKind::Player,
106 instance_name,
107 ),
108 azalea_client::local_player::InstanceHolder {
109 partial_instance: Arc::new(RwLock::new(PartialInstance::default())),
111 instance: instance.clone(),
112 },
113 Inventory::default(),
114 LocalGameMode::from(GameMode::Survival),
115 MineBundle::default(),
116 CurrentSequenceNumber::default(),
117 azalea_client::local_player::PermissionLevel::default(),
118 azalea_client::local_player::PlayerAbilities::default(),
119 )
120}
121
122fn create_simulation_player(
123 ecs: &mut World,
124 instance: Arc<RwLock<Instance>>,
125 player: SimulatedPlayerBundle,
126) -> Entity {
127 let mut entity = ecs.spawn(create_simulation_player_complete_bundle(instance, &player));
128 entity.insert(player);
129 entity.id()
130}
131
132pub struct Simulation {
134 pub app: App,
135 pub entity: Entity,
136 _instance: Arc<RwLock<Instance>>,
137}
138
139impl Simulation {
140 pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self {
141 let (mut app, instance) = create_simulation_instance(chunks);
142 let entity = create_simulation_player(app.world_mut(), instance.clone(), player);
143 Self {
144 app,
145 entity,
146 _instance: instance,
147 }
148 }
149
150 pub fn tick(&mut self) {
151 self.app.update();
152 self.app.world_mut().run_schedule(GameTick);
153 }
154 pub fn component<T: Component + Clone>(&self) -> T {
155 self.app.world().get::<T>(self.entity).unwrap().clone()
156 }
157 pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
158 self.app.world().get::<T>(self.entity).cloned()
159 }
160 pub fn position(&self) -> Vec3 {
161 *self.component::<Position>()
162 }
163 pub fn is_mining(&self) -> bool {
164 self.get_component::<azalea_client::mining::MineBlockPos>()
166 .and_then(|c| *c)
167 .is_some()
168 }
169}
170
171pub struct SimulationSet {
173 pub app: App,
174 instance: Arc<RwLock<Instance>>,
175}
176impl SimulationSet {
177 pub fn new(chunks: ChunkStorage) -> Self {
178 let (app, instance) = create_simulation_instance(chunks);
179 Self { app, instance }
180 }
181 pub fn tick(&mut self) {
182 self.app.update();
183 self.app.world_mut().run_schedule(GameTick);
184 }
185
186 pub fn spawn(&mut self, player: SimulatedPlayerBundle) -> Entity {
187 create_simulation_player(self.app.world_mut(), self.instance.clone(), player)
188 }
189 pub fn despawn(&mut self, entity: Entity) {
190 self.app.world_mut().despawn(entity);
191 }
192
193 pub fn position(&self, entity: Entity) -> Vec3 {
194 **self.app.world().get::<Position>(entity).unwrap()
195 }
196}