azalea/pathfinder/
mining.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::{cell::UnsafeCell, ops::RangeInclusive};

use azalea_block::{BlockState, BlockStates};
use azalea_inventory::Menu;
use nohash_hasher::IntMap;

use super::costs::BLOCK_BREAK_ADDITIONAL_PENALTY;
use crate::auto_tool::best_tool_in_hotbar_for_block;

pub struct MiningCache {
    block_state_id_costs: UnsafeCell<IntMap<u32, f32>>,
    inventory_menu: Option<Menu>,

    water_block_state_range: RangeInclusive<u32>,
    lava_block_state_range: RangeInclusive<u32>,

    falling_blocks: Vec<BlockState>,
}

impl MiningCache {
    pub fn new(inventory_menu: Option<Menu>) -> Self {
        let water_block_states = BlockStates::from(azalea_registry::Block::Water);
        let lava_block_states = BlockStates::from(azalea_registry::Block::Lava);

        let mut water_block_state_range_min = u32::MAX;
        let mut water_block_state_range_max = u32::MIN;
        for state in water_block_states {
            water_block_state_range_min = water_block_state_range_min.min(state.id);
            water_block_state_range_max = water_block_state_range_max.max(state.id);
        }
        let water_block_state_range = water_block_state_range_min..=water_block_state_range_max;

        let mut lava_block_state_range_min = u32::MAX;
        let mut lava_block_state_range_max = u32::MIN;
        for state in lava_block_states {
            lava_block_state_range_min = lava_block_state_range_min.min(state.id);
            lava_block_state_range_max = lava_block_state_range_max.max(state.id);
        }
        let lava_block_state_range = lava_block_state_range_min..=lava_block_state_range_max;

        let mut falling_blocks: Vec<BlockState> = vec![
            azalea_registry::Block::Sand.into(),
            azalea_registry::Block::RedSand.into(),
            azalea_registry::Block::Gravel.into(),
            azalea_registry::Block::Anvil.into(),
            azalea_registry::Block::ChippedAnvil.into(),
            azalea_registry::Block::DamagedAnvil.into(),
            // concrete powders
            azalea_registry::Block::WhiteConcretePowder.into(),
            azalea_registry::Block::OrangeConcretePowder.into(),
            azalea_registry::Block::MagentaConcretePowder.into(),
            azalea_registry::Block::LightBlueConcretePowder.into(),
            azalea_registry::Block::YellowConcretePowder.into(),
            azalea_registry::Block::LimeConcretePowder.into(),
            azalea_registry::Block::PinkConcretePowder.into(),
            azalea_registry::Block::GrayConcretePowder.into(),
            azalea_registry::Block::LightGrayConcretePowder.into(),
            azalea_registry::Block::CyanConcretePowder.into(),
            azalea_registry::Block::PurpleConcretePowder.into(),
            azalea_registry::Block::BlueConcretePowder.into(),
            azalea_registry::Block::BrownConcretePowder.into(),
            azalea_registry::Block::GreenConcretePowder.into(),
            azalea_registry::Block::RedConcretePowder.into(),
            azalea_registry::Block::BlackConcretePowder.into(),
        ];
        falling_blocks.sort_unstable_by_key(|block| block.id);

        Self {
            block_state_id_costs: UnsafeCell::new(IntMap::default()),
            inventory_menu,
            water_block_state_range,
            lava_block_state_range,
            falling_blocks,
        }
    }

    pub fn cost_for(&self, block: BlockState) -> f32 {
        let Some(inventory_menu) = &self.inventory_menu else {
            return f32::INFINITY;
        };

        // SAFETY: mining is single-threaded, so this is safe
        let block_state_id_costs = unsafe { &mut *self.block_state_id_costs.get() };

        if let Some(cost) = block_state_id_costs.get(&block.id) {
            *cost
        } else {
            let best_tool_result = best_tool_in_hotbar_for_block(block, inventory_menu);
            let mut cost = 1. / best_tool_result.percentage_per_tick;

            cost += BLOCK_BREAK_ADDITIONAL_PENALTY;

            block_state_id_costs.insert(block.id, cost);
            cost
        }
    }

    pub fn is_liquid(&self, block: BlockState) -> bool {
        self.water_block_state_range.contains(&block.id)
            || self.lava_block_state_range.contains(&block.id)
    }

    pub fn is_falling_block(&self, block: BlockState) -> bool {
        self.falling_blocks
            .binary_search_by_key(&block.id, |block| block.id)
            .is_ok()
    }
}