azalea/pathfinder/
simulation.rs

1//! Simulate the Minecraft world, currently only used for tests.
2
3use std::sync::Arc;
4
5use azalea_client::{inventory::Inventory, packet_handling::game::SendPacketEvent, PhysicsState};
6use azalea_core::{position::Vec3, resource_location::ResourceLocation, tick::GameTick};
7use azalea_entity::{
8    attributes::AttributeInstance, Attributes, EntityDimensions, LookDirection, Physics, Position,
9};
10use azalea_registry::EntityKind;
11use azalea_world::{ChunkStorage, Instance, InstanceContainer, MinecraftEntityId, PartialInstance};
12use bevy_app::App;
13use bevy_ecs::prelude::*;
14use parking_lot::RwLock;
15use uuid::Uuid;
16
17#[derive(Bundle, Clone)]
18pub struct SimulatedPlayerBundle {
19    pub position: Position,
20    pub physics: Physics,
21    pub physics_state: PhysicsState,
22    pub look_direction: LookDirection,
23    pub attributes: Attributes,
24    pub inventory: Inventory,
25}
26
27impl SimulatedPlayerBundle {
28    pub fn new(position: Vec3) -> Self {
29        let dimensions = EntityDimensions::from(EntityKind::Player);
30
31        SimulatedPlayerBundle {
32            position: Position::new(position),
33            physics: Physics::new(dimensions, position),
34            physics_state: PhysicsState::default(),
35            look_direction: LookDirection::default(),
36            attributes: Attributes {
37                speed: AttributeInstance::new(0.1),
38                attack_speed: AttributeInstance::new(4.0),
39                water_movement_efficiency: AttributeInstance::new(0.0),
40            },
41            inventory: Inventory::default(),
42        }
43    }
44}
45
46fn simulation_instance_name() -> ResourceLocation {
47    ResourceLocation::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::PlayerMovePlugin,
64        super::PathfinderPlugin,
65        crate::BotPlugin,
66        azalea_client::task_pool::TaskPoolPlugin::default(),
67        // for mining
68        azalea_client::inventory::InventoryPlugin,
69        azalea_client::mining::MinePlugin,
70        azalea_client::interact::InteractPlugin,
71    ))
72    .insert_resource(InstanceContainer {
73        instances: [(instance_name.clone(), Arc::downgrade(&instance.clone()))]
74            .iter()
75            .cloned()
76            .collect(),
77    })
78    .add_event::<SendPacketEvent>();
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            azalea_registry::EntityKind::Player,
101            instance_name,
102        ),
103        azalea_client::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    )
110}
111
112fn create_simulation_player(
113    ecs: &mut World,
114    instance: Arc<RwLock<Instance>>,
115    player: SimulatedPlayerBundle,
116) -> Entity {
117    let mut entity = ecs.spawn(create_simulation_player_complete_bundle(instance, &player));
118    entity.insert(player);
119    entity.id()
120}
121
122/// Simulate the Minecraft world to see if certain movements would be possible.
123pub struct Simulation {
124    pub app: App,
125    pub entity: Entity,
126    _instance: Arc<RwLock<Instance>>,
127}
128
129impl Simulation {
130    pub fn new(chunks: ChunkStorage, player: SimulatedPlayerBundle) -> Self {
131        let (mut app, instance) = create_simulation_instance(chunks);
132        let entity = create_simulation_player(app.world_mut(), instance.clone(), player);
133        Self {
134            app,
135            entity,
136            _instance: instance,
137        }
138    }
139
140    pub fn tick(&mut self) {
141        self.app.update();
142        self.app.world_mut().run_schedule(GameTick);
143    }
144    pub fn component<T: Component + Clone>(&self) -> T {
145        self.app.world().get::<T>(self.entity).unwrap().clone()
146    }
147    pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
148        self.app.world().get::<T>(self.entity).cloned()
149    }
150    pub fn position(&self) -> Vec3 {
151        *self.component::<Position>()
152    }
153    pub fn is_mining(&self) -> bool {
154        // return true if the component is present and Some
155        self.get_component::<azalea_client::mining::MineBlockPos>()
156            .and_then(|c| *c)
157            .is_some()
158    }
159}
160
161/// A set of simulations, useful for efficiently doing multiple simulations.
162pub struct SimulationSet {
163    pub app: App,
164    instance: Arc<RwLock<Instance>>,
165}
166impl SimulationSet {
167    pub fn new(chunks: ChunkStorage) -> Self {
168        let (app, instance) = create_simulation_instance(chunks);
169        Self { app, instance }
170    }
171    pub fn tick(&mut self) {
172        self.app.update();
173        self.app.world_mut().run_schedule(GameTick);
174    }
175
176    pub fn spawn(&mut self, player: SimulatedPlayerBundle) -> Entity {
177        create_simulation_player(self.app.world_mut(), self.instance.clone(), player)
178    }
179    pub fn despawn(&mut self, entity: Entity) {
180        self.app.world_mut().despawn(entity);
181    }
182
183    pub fn position(&self, entity: Entity) -> Vec3 {
184        **self.app.world().get::<Position>(entity).unwrap()
185    }
186}