azalea/pathfinder/
mining.rs

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