azalea/pathfinder/
mining.rs

1use std::{cell::UnsafeCell, ops::RangeInclusive};
2
3use azalea_block::{
4    block_state::BlockStateIntegerRepr, properties::Waterlogged, BlockState, BlockStates,
5};
6use azalea_inventory::Menu;
7use nohash_hasher::IntMap;
8
9use super::costs::BLOCK_BREAK_ADDITIONAL_PENALTY;
10use crate::auto_tool::best_tool_in_hotbar_for_block;
11
12pub struct MiningCache {
13    block_state_id_costs: UnsafeCell<IntMap<BlockStateIntegerRepr, f32>>,
14    inventory_menu: Option<Menu>,
15
16    water_block_state_range: RangeInclusive<BlockStateIntegerRepr>,
17    lava_block_state_range: RangeInclusive<BlockStateIntegerRepr>,
18
19    falling_blocks: Vec<BlockState>,
20}
21
22impl MiningCache {
23    pub fn new(inventory_menu: Option<Menu>) -> Self {
24        let water_block_states = BlockStates::from(azalea_registry::Block::Water);
25        let lava_block_states = BlockStates::from(azalea_registry::Block::Lava);
26
27        let mut water_block_state_range_min = BlockStateIntegerRepr::MAX;
28        let mut water_block_state_range_max = BlockStateIntegerRepr::MIN;
29        for state in water_block_states {
30            water_block_state_range_min = water_block_state_range_min.min(state.id);
31            water_block_state_range_max = water_block_state_range_max.max(state.id);
32        }
33        let water_block_state_range = water_block_state_range_min..=water_block_state_range_max;
34
35        let mut lava_block_state_range_min = BlockStateIntegerRepr::MAX;
36        let mut lava_block_state_range_max = BlockStateIntegerRepr::MIN;
37        for state in lava_block_states {
38            lava_block_state_range_min = lava_block_state_range_min.min(state.id);
39            lava_block_state_range_max = lava_block_state_range_max.max(state.id);
40        }
41        let lava_block_state_range = lava_block_state_range_min..=lava_block_state_range_max;
42
43        let mut falling_blocks: Vec<BlockState> = vec![
44            azalea_registry::Block::Sand.into(),
45            azalea_registry::Block::RedSand.into(),
46            azalea_registry::Block::Gravel.into(),
47            azalea_registry::Block::Anvil.into(),
48            azalea_registry::Block::ChippedAnvil.into(),
49            azalea_registry::Block::DamagedAnvil.into(),
50            // concrete powders
51            azalea_registry::Block::WhiteConcretePowder.into(),
52            azalea_registry::Block::OrangeConcretePowder.into(),
53            azalea_registry::Block::MagentaConcretePowder.into(),
54            azalea_registry::Block::LightBlueConcretePowder.into(),
55            azalea_registry::Block::YellowConcretePowder.into(),
56            azalea_registry::Block::LimeConcretePowder.into(),
57            azalea_registry::Block::PinkConcretePowder.into(),
58            azalea_registry::Block::GrayConcretePowder.into(),
59            azalea_registry::Block::LightGrayConcretePowder.into(),
60            azalea_registry::Block::CyanConcretePowder.into(),
61            azalea_registry::Block::PurpleConcretePowder.into(),
62            azalea_registry::Block::BlueConcretePowder.into(),
63            azalea_registry::Block::BrownConcretePowder.into(),
64            azalea_registry::Block::GreenConcretePowder.into(),
65            azalea_registry::Block::RedConcretePowder.into(),
66            azalea_registry::Block::BlackConcretePowder.into(),
67        ];
68        falling_blocks.sort_unstable_by_key(|block| block.id);
69
70        Self {
71            block_state_id_costs: UnsafeCell::new(IntMap::default()),
72            inventory_menu,
73            water_block_state_range,
74            lava_block_state_range,
75            falling_blocks,
76        }
77    }
78
79    pub fn cost_for(&self, block: BlockState) -> f32 {
80        let Some(inventory_menu) = &self.inventory_menu else {
81            return f32::INFINITY;
82        };
83
84        // SAFETY: mining is single-threaded, so this is safe
85        let block_state_id_costs = unsafe { &mut *self.block_state_id_costs.get() };
86
87        if let Some(cost) = block_state_id_costs.get(&block.id) {
88            *cost
89        } else {
90            let best_tool_result = best_tool_in_hotbar_for_block(block, inventory_menu);
91            let mut cost = 1. / best_tool_result.percentage_per_tick;
92
93            cost += BLOCK_BREAK_ADDITIONAL_PENALTY;
94
95            block_state_id_costs.insert(block.id, cost);
96            cost
97        }
98    }
99
100    pub fn is_liquid(&self, block: BlockState) -> bool {
101        // this already runs in about 1 nanosecond, so if you wanna try optimizing it at
102        // least run the benchmarks (in benches/checks.rs)
103
104        self.water_block_state_range.contains(&block.id)
105            || self.lava_block_state_range.contains(&block.id)
106            || is_waterlogged(block)
107    }
108
109    pub fn is_falling_block(&self, block: BlockState) -> bool {
110        self.falling_blocks
111            .binary_search_by_key(&block.id, |block| block.id)
112            .is_ok()
113    }
114}
115
116pub fn is_waterlogged(block: BlockState) -> bool {
117    block.property::<Waterlogged>().unwrap_or_default()
118}