1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

heightmaps

This commit is contained in:
mat 2023-09-17 21:44:17 -05:00
parent 61e63c0896
commit 856a3252f6
10 changed files with 321 additions and 33 deletions

View file

@ -9,6 +9,7 @@ use azalea_entity::{
Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition, LoadedBy, LookDirection,
Physics, PlayerBundle, Position, RelativeEntityUpdate,
};
use azalea_nbt::NbtCompound;
use azalea_protocol::{
connect::{ReadConnection, WriteConnection},
packets::game::{
@ -578,9 +579,20 @@ pub fn process_packet_events(ecs: &mut World) {
}
}
let heightmaps = p
.chunk_data
.heightmaps
.as_compound()
.and_then(|c| c.get(""))
.and_then(|c| c.as_compound());
// necessary to make the unwrap_or work
let empty_nbt_compound = NbtCompound::default();
let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound);
if let Err(e) = partial_world.chunks.replace_with_packet_data(
&pos,
&mut Cursor::new(&p.chunk_data.data),
heightmaps,
&mut world.chunks,
) {
error!("Couldn't set chunk data: {}", e);
@ -632,6 +644,7 @@ pub fn process_packet_events(ecs: &mut World) {
ClientboundGamePacket::SetEntityData(p) => {
debug!("Got set entity data packet {:?}", p);
#[allow(clippy::type_complexity)]
let mut system_state: SystemState<(
Commands,
Query<(&EntityIdIndex, &LocalPlayer)>,

View file

@ -9,17 +9,17 @@ pub static SIN: LazyLock<[f32; 65536]> = LazyLock::new(|| {
});
/// A sine function that uses a lookup table.
pub fn sin(var0: f32) -> f32 {
let var0 = var0 * 10430.378;
let var0 = var0 as usize;
SIN[var0 & 65535]
pub fn sin(x: f32) -> f32 {
let x = x * 10430.378;
let x = x as usize;
SIN[x & 65535]
}
/// A cosine function that uses a lookup table.
pub fn cos(var0: f32) -> f32 {
let var0 = var0 * 10430.378 + 16384.0;
let var0 = var0 as usize;
SIN[var0 & 65535]
pub fn cos(x: f32) -> f32 {
let x = x * 10430.378 + 16384.0;
let x = x as usize;
SIN[x & 65535]
}
// TODO: make this generic
@ -56,6 +56,10 @@ pub fn lerp<T: num_traits::Float>(amount: T, a: T, b: T) -> T {
a + amount * (b - a)
}
pub fn ceil_log2(x: u32) -> u32 {
u32::BITS - x.leading_zeros()
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -346,6 +346,17 @@ impl From<Vec3> for ChunkPos {
}
}
impl From<&Vec3> for ChunkBlockPos {
fn from(pos: &Vec3) -> Self {
ChunkBlockPos::from(&BlockPos::from(pos))
}
}
impl From<Vec3> for ChunkBlockPos {
fn from(pos: Vec3) -> Self {
ChunkBlockPos::from(&pos)
}
}
const PACKED_X_LENGTH: u64 = 1 + 25; // minecraft does something a bit more complicated to get this 25
const PACKED_Z_LENGTH: u64 = PACKED_X_LENGTH;
const PACKED_Y_LENGTH: u64 = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH;

View file

@ -27,3 +27,34 @@ pub struct BlockEntity {
pub kind: azalea_registry::BlockEntityKind,
pub data: Nbt,
}
// Compound(NbtCompound {
// inner: [("", Compound(NbtCompound {
// inner: [
// ("MOTION_BLOCKING", LongArray([2310355422147575936,
// 2292305770412047999, 2310355422147575423, 2292305770412310656,
// 2310355422013095551, 2292305839266005120, 2310320168921529983,
// 2310355422147575936, 2292305770412048512, 2310355422147575935,
// 2292305839266005120, 2310355422147313279, 2292305770546528384,
// 2310355353293618815, 2292305839266005120, 2292305770412047999,
// 2310355422147575936, 2292305770412047999, 2310355422147575423,
// 2292305770412048512, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 17079008895])),
// ("WORLD_SURFACE", LongArray([2310355422147575936,
// 2292340954784136831, 2310355422147575423, 2292305770412310656,
// 2310355422013095551, 2292305839266005120, 2310320168921529983,
// 2310355422147575936, 2292305770412048512, 2310355422147575935,
// 2292305839266005120, 2310355422147313279, 2292305770546528384,
// 2310355353293618815, 2292305839266005120, 2292305770412047999,
// 2310355422147575936, 2292305770412047999, 2310355422147575423,
// 2292305770412048512, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 2292305770412047999,
// 2292305770412047999, 2292305770412047999, 17079008895]))] }))]
// })

View file

@ -106,7 +106,7 @@ impl BitStorage {
// 0 bit storage
if data.is_empty() {
return Ok(BitStorage {
data: Vec::with_capacity(0),
data: Vec::new(),
bits,
size,
..Default::default()

View file

@ -1,10 +1,14 @@
use crate::heightmap::Heightmap;
use crate::heightmap::HeightmapKind;
use crate::palette::PalettedContainer;
use crate::palette::PalettedContainerKind;
use azalea_block::BlockState;
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use azalea_nbt::NbtCompound;
use log::{debug, trace, warn};
use parking_lot::RwLock;
use std::str::FromStr;
use std::{
collections::HashMap,
fmt::Debug,
@ -43,6 +47,10 @@ pub struct ChunkStorage {
#[derive(Debug)]
pub struct Chunk {
pub sections: Vec<Section>,
/// Heightmaps are used for identifying the surface blocks in a chunk.
/// Usually for clients only `WorldSurface` and `MotionBlocking` are
/// present.
pub heightmaps: HashMap<HeightmapKind, Heightmap>,
}
/// A section of a chunk, i.e. a 16*16*16 block area.
@ -73,6 +81,7 @@ impl Default for Chunk {
fn default() -> Self {
Chunk {
sections: vec![Section::default(); (384 / 16) as usize],
heightmaps: HashMap::new(),
}
}
}
@ -119,6 +128,7 @@ impl PartialChunkStorage {
&mut self,
pos: &ChunkPos,
data: &mut Cursor<&[u8]>,
heightmaps: &NbtCompound,
chunk_storage: &mut ChunkStorage,
) -> Result<(), BufReadError> {
debug!("Replacing chunk at {:?}", pos);
@ -127,7 +137,12 @@ impl PartialChunkStorage {
return Ok(());
}
let chunk = Chunk::read_with_dimension_height(data, chunk_storage.height)?;
let chunk = Chunk::read_with_dimension_height(
data,
chunk_storage.height,
chunk_storage.min_y,
heightmaps,
)?;
trace!("Loaded chunk {:?}", pos);
self.set(pos, Some(chunk), chunk_storage);
@ -229,6 +244,8 @@ impl Chunk {
pub fn read_with_dimension_height(
buf: &mut Cursor<&[u8]>,
dimension_height: u32,
min_y: i32,
heightmaps_nbt: &NbtCompound,
) -> Result<Self, BufReadError> {
let section_count = dimension_height / SECTION_HEIGHT;
let mut sections = Vec::with_capacity(section_count as usize);
@ -236,23 +253,30 @@ impl Chunk {
let section = Section::read_from(buf)?;
sections.push(section);
}
Ok(Chunk { sections })
let mut heightmaps = HashMap::new();
for (name, heightmap) in heightmaps_nbt.iter() {
let Ok(kind) = HeightmapKind::from_str(name) else {
warn!("Unknown heightmap kind: {name}");
continue;
};
let Some(data) = heightmap.as_long_array() else {
warn!("Heightmap {name} is not a long array");
continue;
};
let data: Vec<u64> = data.iter().map(|x| *x as u64).collect();
let heightmap = Heightmap::new(kind, dimension_height, min_y, data);
heightmaps.insert(kind, heightmap);
}
Ok(Chunk {
sections,
heightmaps,
})
}
pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> Option<BlockState> {
if pos.y < min_y {
// y position is out of bounds
return None;
}
let section_index = section_index(pos.y, min_y) as usize;
if section_index >= self.sections.len() {
// y position is out of bounds
return None;
};
// TODO: make sure the section exists
let section = &self.sections[section_index];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
Some(section.get(chunk_section_pos))
get_block_state_from_sections(&self.sections, pos, min_y)
}
pub fn get_and_set(
@ -265,7 +289,13 @@ impl Chunk {
// TODO: make sure the section exists
let section = &mut self.sections[section_index as usize];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
section.get_and_set(chunk_section_pos, state)
let previous_state = section.get_and_set(chunk_section_pos, state);
for heightmap in self.heightmaps.values_mut() {
heightmap.update(pos, state, &self.sections);
}
previous_state
}
pub fn set(&mut self, pos: &ChunkBlockPos, state: BlockState, min_y: i32) {
@ -274,7 +304,33 @@ impl Chunk {
let section = &mut self.sections[section_index as usize];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
section.set(chunk_section_pos, state);
for heightmap in self.heightmaps.values_mut() {
heightmap.update(pos, state, &self.sections);
}
}
}
/// Get the block state at the given position from a list of sections. Returns
/// `None` if the position is out of bounds.
pub fn get_block_state_from_sections(
sections: &[Section],
pos: &ChunkBlockPos,
min_y: i32,
) -> Option<BlockState> {
if pos.y < min_y {
// y position is out of bounds
return None;
}
let section_index = section_index(pos.y, min_y) as usize;
if section_index >= sections.len() {
// y position is out of bounds
return None;
};
// TODO: make sure the section exists
let section = &sections[section_index];
let chunk_section_pos = ChunkSectionBlockPos::from(pos);
Some(section.get(chunk_section_pos))
}
impl McBufWritable for Chunk {

View file

@ -0,0 +1,151 @@
use std::{fmt::Display, str::FromStr};
use azalea_block::BlockState;
use azalea_core::{math, ChunkBlockPos};
use azalea_registry::tags::blocks::LEAVES;
use crate::{chunk_storage::get_block_state_from_sections, BitStorage, Section};
// (wg stands for worldgen)
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum HeightmapKind {
WorldSurfaceWg,
WorldSurface,
OceanFloorWg,
OceanFloor,
MotionBlocking,
MotionBlockingNoLeaves,
}
#[derive(Clone, Debug)]
pub struct Heightmap {
pub data: BitStorage,
pub min_y: i32,
pub kind: HeightmapKind,
}
fn blocks_motion(block_state: BlockState) -> bool {
// TODO
!block_state.is_air()
}
fn motion_blocking(block_state: BlockState) -> bool {
// TODO
!block_state.is_air() || block_state.waterlogged()
}
impl HeightmapKind {
pub fn is_opaque(self, block_state: BlockState) -> bool {
let block = Box::<dyn azalea_block::Block>::from(block_state);
let registry_block = block.as_registry_block();
match self {
HeightmapKind::WorldSurfaceWg => !block_state.is_air(),
HeightmapKind::WorldSurface => !block_state.is_air(),
HeightmapKind::OceanFloorWg => blocks_motion(block_state),
HeightmapKind::OceanFloor => blocks_motion(block_state),
HeightmapKind::MotionBlocking => motion_blocking(block_state),
HeightmapKind::MotionBlockingNoLeaves => {
motion_blocking(block_state) && !LEAVES.contains(&registry_block)
}
}
}
}
impl Heightmap {
pub fn new(kind: HeightmapKind, dimension_height: u32, min_y: i32, data: Vec<u64>) -> Self {
let bits = math::ceil_log2(dimension_height + 1);
let data = BitStorage::new(bits as usize, 16 * 16, Some(data)).unwrap();
Self { kind, data, min_y }
}
pub fn get_index(x: u8, z: u8) -> usize {
(x as usize) + (z as usize) * 16
}
pub fn get_first_available_at_index(&self, index: usize) -> i32 {
self.data.get(index) as i32 + self.min_y
}
pub fn get_first_available(&self, x: u8, z: u8) -> i32 {
self.get_first_available_at_index(Self::get_index(x, z))
}
pub fn get_highest_taken(&self, x: u8, z: u8) -> i32 {
self.get_first_available(x, z) - 1
}
pub fn set_height(&mut self, x: u8, z: u8, height: i32) {
self.data
.set(Self::get_index(x, z), (height - self.min_y) as u64);
}
/// Updates the heightmap with the given block state at the given position.
pub fn update(
&mut self,
pos: &ChunkBlockPos,
block_state: BlockState,
sections: &[Section],
) -> bool {
let first_available_y = self.get_first_available(pos.x, pos.z);
if pos.y <= first_available_y - 2 {
return false;
}
if self.kind.is_opaque(block_state) {
// increase y
if pos.y >= first_available_y {
self.set_height(pos.x, pos.z, pos.y + 1);
return true;
}
} else if first_available_y - 1 == pos.y {
// decrease y
for y in (self.min_y..pos.y).rev() {
if self.kind.is_opaque(
get_block_state_from_sections(
sections,
&ChunkBlockPos::new(pos.x, y, pos.z),
self.min_y,
)
.unwrap_or_default(),
) {
self.set_height(pos.x, pos.z, y + 1);
return true;
}
}
self.set_height(pos.x, pos.z, self.min_y);
return true;
}
false
}
}
impl FromStr for HeightmapKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"WORLD_SURFACE_WG" => Ok(HeightmapKind::WorldSurfaceWg),
"WORLD_SURFACE" => Ok(HeightmapKind::WorldSurface),
"OCEAN_FLOOR_WG" => Ok(HeightmapKind::OceanFloorWg),
"OCEAN_FLOOR" => Ok(HeightmapKind::OceanFloor),
"MOTION_BLOCKING" => Ok(HeightmapKind::MotionBlocking),
"MOTION_BLOCKING_NO_LEAVES" => Ok(HeightmapKind::MotionBlockingNoLeaves),
_ => Err(()),
}
}
}
impl Display for HeightmapKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HeightmapKind::WorldSurfaceWg => write!(f, "WORLD_SURFACE_WG"),
HeightmapKind::WorldSurface => write!(f, "WORLD_SURFACE"),
HeightmapKind::OceanFloorWg => write!(f, "OCEAN_FLOOR_WG"),
HeightmapKind::OceanFloor => write!(f, "OCEAN_FLOOR"),
HeightmapKind::MotionBlocking => write!(f, "MOTION_BLOCKING"),
HeightmapKind::MotionBlockingNoLeaves => write!(f, "MOTION_BLOCKING_NO_LEAVES"),
}
}
}

View file

@ -5,6 +5,7 @@
mod bit_storage;
mod chunk_storage;
mod container;
pub mod heightmap;
pub mod iterators;
pub mod palette;
mod world;

View file

@ -3,17 +3,18 @@
#![feature(type_alias_impl_trait)]
use azalea::ecs::query::With;
use azalea::entity::metadata::Player;
use azalea::entity::{EyeHeight, Position};
use azalea::entity::{metadata::Player, EyeHeight, Position};
use azalea::interact::HitResultComponent;
use azalea::inventory::ItemSlot;
use azalea::pathfinder::goals::BlockPosGoal;
use azalea::protocol::packets::game::ClientboundGamePacket;
use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
use azalea::{Account, Client, Event};
use azalea_client::SprintDirection;
use azalea_core::Vec3;
use azalea_world::{InstanceName, MinecraftEntityId};
use azalea::world::{heightmap::HeightmapKind, InstanceName, MinecraftEntityId};
use azalea::SprintDirection;
use azalea::{prelude::*, swarm::prelude::*};
use azalea::{
Account, BlockPos, ChunkPos, Client, Event, GameProfileComponent, Vec3, WalkDirection,
};
use azalea_core::ChunkBlockPos;
use std::time::Duration;
#[derive(Default, Clone, Component)]
@ -288,6 +289,26 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
bot.chat("no entities found");
}
}
"heightmap" => {
let position = bot.position();
let chunk_pos = ChunkPos::from(position);
let chunk_block_pos = ChunkBlockPos::from(position);
let chunk = bot.world().read().chunks.get(&chunk_pos);
if let Some(chunk) = chunk {
let heightmaps = &chunk.read().heightmaps;
let Some(world_surface_heightmap) =
heightmaps.get(&HeightmapKind::WorldSurface)
else {
bot.chat("no world surface heightmap");
return Ok(());
};
let highest_y = world_surface_heightmap
.get_highest_taken(chunk_block_pos.x, chunk_block_pos.z);
bot.chat(&format!("highest_y: {highest_y}",));
} else {
bot.chat("no chunk found");
}
}
_ => {}
}
}

View file

@ -20,7 +20,7 @@ pub use azalea_block as blocks;
pub use azalea_brigadier as brigadier;
pub use azalea_chat::FormattedText;
pub use azalea_client::*;
pub use azalea_core::{BlockPos, Vec3};
pub use azalea_core::{BlockPos, ChunkPos, Vec3};
pub use azalea_entity as entity;
pub use azalea_protocol as protocol;
pub use azalea_registry::{Block, EntityKind, Item};