azalea/pathfinder/
simulation.rs

1//! Simulate the Minecraft world, currently only used for tests.
2
3use 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    // we don't use all the default azalea plugins because we don't need all of them
59    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        // for mining
67        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 is never actually used by the pathfinder so
104            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
126/// Simulate the Minecraft world to see if certain movements would be possible.
127pub 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        // return true if the component is present and Some
159        self.get_component::<azalea_client::mining::MineBlockPos>()
160            .and_then(|c| *c)
161            .is_some()
162    }
163}
164
165/// A set of simulations, useful for efficiently doing multiple simulations.
166pub 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}