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