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::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    // we don't use all the default azalea plugins because we don't need all of them
65    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        // for mining
73        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 is never actually used by the pathfinder so
110            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
132/// Simulate the Minecraft world to see if certain movements would be possible.
133pub 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        // return true if the component is present and Some
165        self.get_component::<azalea_client::mining::MineBlockPos>()
166            .and_then(|c| *c)
167            .is_some()
168    }
169}
170
171/// A set of simulations, useful for efficiently doing multiple simulations.
172pub 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}