use std::{cell::UnsafeCell, ops::RangeInclusive}; use azalea_block::{ BlockState, BlockStates, block_state::BlockStateIntegerRepr, properties::Waterlogged, }; use azalea_inventory::Menu; use azalea_registry::builtin::BlockKind; 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>, inventory_menu: Option, water_block_state_range: RangeInclusive, lava_block_state_range: RangeInclusive, falling_blocks: Vec, } impl MiningCache { pub fn new(inventory_menu: Option) -> Self { let water_block_states = BlockStates::from(BlockKind::Water); let lava_block_states = BlockStates::from(BlockKind::Lava); let mut water_block_state_range_min = BlockStateIntegerRepr::MAX; let mut water_block_state_range_max = BlockStateIntegerRepr::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 = BlockStateIntegerRepr::MAX; let mut lava_block_state_range_max = BlockStateIntegerRepr::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 = vec![ BlockKind::Sand.into(), BlockKind::RedSand.into(), BlockKind::Gravel.into(), BlockKind::Anvil.into(), BlockKind::ChippedAnvil.into(), BlockKind::DamagedAnvil.into(), // concrete powders BlockKind::WhiteConcretePowder.into(), BlockKind::OrangeConcretePowder.into(), BlockKind::MagentaConcretePowder.into(), BlockKind::LightBlueConcretePowder.into(), BlockKind::YellowConcretePowder.into(), BlockKind::LimeConcretePowder.into(), BlockKind::PinkConcretePowder.into(), BlockKind::GrayConcretePowder.into(), BlockKind::LightGrayConcretePowder.into(), BlockKind::CyanConcretePowder.into(), BlockKind::PurpleConcretePowder.into(), BlockKind::BlueConcretePowder.into(), BlockKind::BrownConcretePowder.into(), BlockKind::GreenConcretePowder.into(), BlockKind::RedConcretePowder.into(), BlockKind::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 { // this already runs in about 1 nanosecond, so if you wanna try optimizing it at // least run the benchmarks (in benches/checks.rs) self.water_block_state_range.contains(&block.id()) || self.lava_block_state_range.contains(&block.id()) || is_waterlogged(block) } pub fn is_falling_block(&self, block: BlockState) -> bool { self.falling_blocks .binary_search_by_key(&block.id(), |block| block.id()) .is_ok() } } pub fn is_waterlogged(block: BlockState) -> bool { block.property::().unwrap_or_default() }