Add mechanism for retrieving BlockEntity data (#524)

* Add mechanism for retrieving BlockEntity data

This commit adds a mechanism for retrieving block entity data.
Block entity data is required to support for example text on signs,
banner patterns, or mods such as Domum Ornamentum.

* Fix the coordinate-packing for block entity-loading

This commit fixes the incorrect shifting of bits when
packing the chunk-local coordinates of a block entity
into a 64-bit long for lookups.

* Change mapping type of BlockEntity lookups

This commit changes the type stored for BlockEntity
mappings from a class of the type associated with the
ID to a method reference to its constructor.

* Tidy BlockEntity mappings

This commit introduces a small functional interface
to make the type less ungodly. Also silences the warning
about referencing subclasses in the superclass, it is
fine in this case, we're just storing a reference to
the constructor.

* Add missing license headers

The license headers were missing. Oops.
This commit is contained in:
Gerber Lóránt Viktor 2024-03-20 23:23:03 +01:00 committed by GitHub
parent 2689cd10e0
commit a847e247e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 540 additions and 1 deletions

View File

@ -56,6 +56,7 @@ import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.World;
import de.bluecolored.bluemap.core.world.block.Block;
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
import java.io.IOException;
import java.nio.file.Path;
@ -528,6 +529,11 @@ public class Commands<S> {
lines.put("block-light", block.getBlockLightLevel());
lines.put("sun-light", block.getSunLightLevel());
BlockEntity blockEntity = block.getBlockEntity();
if (blockEntity != null) {
lines.put("block-entity", blockEntity);
}
Object[] textElements = lines.entrySet().stream()
.flatMap(e -> Stream.of(TextColor.GRAY, e.getKey(), ": ", TextColor.WHITE, e.getValue(), "\n"))
.toArray(Object[]::new);

View File

@ -24,6 +24,9 @@
*/
package de.bluecolored.bluemap.core.world;
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
public interface Chunk {
Chunk EMPTY_CHUNK = new Chunk() {};
@ -72,4 +75,5 @@ public interface Chunk {
default int getOceanFloorY(int x, int z) { return 0; }
default @Nullable BlockEntity getBlockEntity(int x, int y, int z) { return null; };
}

View File

@ -28,6 +28,8 @@ import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.LightData;
import de.bluecolored.bluemap.core.world.World;
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
public class Block<T extends Block<T>> {
@ -147,6 +149,10 @@ public class Block<T extends Block<T>> {
return getLightData().getBlockLight();
}
public @Nullable BlockEntity getBlockEntity() {
return getChunk().getBlockEntity(x, y, z);
}
@Override
public String toString() {
if (world != null) {
@ -174,5 +180,4 @@ public class Block<T extends Block<T>> {
protected T self() {
return (T) this;
}
}

View File

@ -0,0 +1,86 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world.block.entity;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class BannerBlockEntity extends BlockEntity {
private final List<Pattern> patterns = new ArrayList<>();
protected BannerBlockEntity(Map<String, Object> data) {
super(data);
@SuppressWarnings("unchecked")
List<Map<String, Object>> patterns = (List<Map<String, Object>>) data.getOrDefault("Patterns", List.of());
for (Map<String, Object> compound : patterns) {
this.patterns.add(new Pattern(compound));
}
}
public List<Pattern> getPatterns() {
return patterns;
}
@Override
public String toString() {
return "BannerBlockEntity{" +
"patterns=" + patterns +
"} " + super.toString();
}
public static class Pattern {
private final String code;
private final Color color;
private Pattern(Map<String, Object> data) {
this.code = (String) data.get("Pattern");
this.color = Color.values()[(int) data.get("Color")];
}
public String getCode() {
return code;
}
public Color getColor() {
return color;
}
@Override
public String toString() {
return "Pattern{" +
"code='" + code + '\'' +
", color=" + color +
'}';
}
}
public enum Color {
WHITE, ORANGE, MAGENTA, LIGHT_BLUE, YELLOW, LIME, PINK, GRAY, LIGHT_GRAY, CYAN, PURPLE, BLUE, BROWN, GREEN,
RED, BLACK
}
}

View File

@ -0,0 +1,137 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world.block.entity;
import com.google.gson.reflect.TypeToken;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluenbt.BlueNBT;
import de.bluecolored.bluenbt.NBTDeserializer;
import de.bluecolored.bluenbt.NBTReader;
import de.bluecolored.bluenbt.TypeDeserializer;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Collectors;
@NBTDeserializer(BlockEntity.BlockEntityDeserializer.class)
public class BlockEntity {
private static final BlueNBT BLUENBT = new BlueNBT();
@FunctionalInterface
private interface BlockEntityInitializer {
BlockEntity create(Map<String, Object> data);
}
@SuppressWarnings("StaticInitializerReferencesSubClass")
private static final Map<String, BlockEntityInitializer> ID_MAPPING = Map.of(
"minecraft:sign", SignBlockEntity::new,
"minecraft:skull", SkullBlockEntity::new,
"minecraft:banner", BannerBlockEntity::new
);
protected final String id;
protected final int x, y, z;
protected final boolean keepPacked;
protected BlockEntity(Map<String, Object> data) {
this.id = (String) data.get("id");
this.x = (int) data.get("x");
this.y = (int) data.get("y");
this.z = (int) data.get("z");
this.keepPacked = (byte) data.getOrDefault("keepPacked", (byte) 0) == 1;
}
public String getId() {
return id;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public boolean isKeepPacked() {
return keepPacked;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BlockEntity that = (BlockEntity) o;
return x == that.x && y == that.y && z == that.z && keepPacked == that.keepPacked && Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id, x, y, z, keepPacked);
}
@Override
public String toString() {
return "BlockEntity{" +
"id='" + id + '\'' +
", x=" + x +
", y=" + y +
", z=" + z +
", keepPacked=" + keepPacked +
'}';
}
public static class BlockEntityDeserializer implements TypeDeserializer<BlockEntity> {
@Override
public BlockEntity read(NBTReader reader) throws IOException {
@SuppressWarnings("unchecked") Map<String, Object> data =
(Map<String, Object>) BLUENBT.read(reader, TypeToken.getParameterized(Map.class, String.class, Object.class));
String id = (String) data.get("id");
if (id == null || id.isBlank()) {
return null;
}
BlockEntityInitializer instance = ID_MAPPING.getOrDefault(id, BlockEntity::new);
try {
return instance.create(data);
} catch (Exception e) {
Logger.global.logError("Failed to instantiate BlockEntity instance!", e);
}
return null;
}
}
}

View File

@ -0,0 +1,113 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world.block.entity;
import java.util.List;
import java.util.Map;
public class SignBlockEntity extends BlockEntity {
private final TextData frontText;
private final TextData backText;
@SuppressWarnings("unchecked")
protected SignBlockEntity(Map<String, Object> data) {
super(data);
// Versions before 1.20 used a different format
if (data.containsKey("front_text")) {
this.frontText = new TextData((Map<String, Object>) data.getOrDefault("front_text", Map.of()));
this.backText = new TextData((Map<String, Object>) data.getOrDefault("back_text", Map.of()));
} else {
this.frontText = new TextData(
(byte) data.getOrDefault("GlowingText", (byte) 0) == 1,
(String) data.getOrDefault("Color", ""),
List.of(
(String) data.getOrDefault("Text1", ""),
(String) data.getOrDefault("Text2", ""),
(String) data.getOrDefault("Text3", ""),
(String) data.getOrDefault("Text4", "")
)
);
this.backText = new TextData(false, "", List.of());
}
}
public TextData getFrontText() {
return frontText;
}
public TextData getBackText() {
return backText;
}
@Override
public String toString() {
return "SignBlockEntity{" +
"frontText=" + frontText +
", backText=" + backText +
"} " + super.toString();
}
public static class TextData {
private final boolean hasGlowingText;
private final String color;
private final List<String> messages;
@SuppressWarnings("unchecked")
private TextData(Map<String, Object> data) {
this.hasGlowingText = (byte) data.getOrDefault("has_glowing_text", (byte) 0) == 1;
this.color = (String) data.getOrDefault("color", "");
this.messages = (List<String>) data.getOrDefault("messages", List.of());
}
public TextData(boolean hasGlowingText, String color, List<String> messages) {
this.hasGlowingText = hasGlowingText;
this.color = color;
this.messages = messages;
}
public boolean isHasGlowingText() {
return hasGlowingText;
}
public String getColor() {
return color;
}
public List<String> getMessages() {
return messages;
}
@Override
public String toString() {
return "TextData{" +
"hasGlowingText=" + hasGlowingText +
", color='" + color + '\'' +
", messages=" + messages +
'}';
}
}
}

View File

@ -0,0 +1,137 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.world.block.entity;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class SkullBlockEntity extends BlockEntity {
private final @Nullable String noteBlockSound;
private final @Nullable String extraType;
private final @Nullable SkullOwner skullOwner;
protected SkullBlockEntity(Map<String, Object> data) {
super(data);
this.noteBlockSound = (String) data.get("note_block_sound");
this.extraType = (String) data.get("ExtraType");
@SuppressWarnings("unchecked")
Map<String, Object> ownerData = (Map<String, Object>) data.get("SkullOwner");
this.skullOwner = ownerData != null ? new SkullOwner(ownerData) : null;
}
public @Nullable String getNoteBlockSound() {
return noteBlockSound;
}
public @Nullable String getExtraType() {
return extraType;
}
public SkullOwner getSkullOwner() {
return skullOwner;
}
@Override
public String toString() {
return "SkullBlockEntity{" +
"noteBlockSound='" + noteBlockSound + '\'' +
", extraType='" + extraType + '\'' +
", skullOwner=" + skullOwner +
"} " + super.toString();
}
public static class SkullOwner {
private final @Nullable UUID id;
private final @Nullable String name;
private final List<Texture> textures = new ArrayList<>();
@SuppressWarnings("unchecked")
private SkullOwner(Map<String, Object> data) {
int[] uuidInts = (int[]) data.get("Id");
this.id = new UUID((long) uuidInts[0] << 32 | uuidInts[1], (long) uuidInts[2] << 32 | uuidInts[3]);
this.name = (String) data.get("Name");
Map<String, Object> properties = (Map<String, Object>) data.getOrDefault("Properties", Map.of());
List<Map<String, Object>> textures = (List<Map<String, Object>>) properties.getOrDefault("textures", List.of());
for (Map<String, Object> compound : textures) {
this.textures.add(new Texture(compound));
}
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public List<Texture> getTextures() {
return textures;
}
@Override
public String toString() {
return "SkullOwner{" +
"id=" + id +
", name='" + name + '\'' +
", textures=" + textures +
'}';
}
}
public static class Texture {
private final @Nullable String signature;
private final String value;
private Texture(Map<String, Object> data) {
this.signature = (String) data.get("signature");
this.value = (String) data.getOrDefault("value", "");
}
public String getSignature() {
return signature;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return "Texture{" +
"signature='" + signature + '\'' +
", value='" + value + '\'' +
'}';
}
}
}

View File

@ -27,6 +27,7 @@ package de.bluecolored.bluemap.core.world.mca;
import com.google.gson.reflect.TypeToken;
import de.bluecolored.bluemap.core.util.Key;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
import de.bluecolored.bluemap.core.world.mca.data.BlockStateDeserializer;
import de.bluecolored.bluemap.core.world.mca.data.KeyDeserializer;
import de.bluecolored.bluenbt.BlueNBT;
@ -37,6 +38,7 @@ public class MCAUtil {
static {
BLUENBT.register(TypeToken.get(BlockState.class), new BlockStateDeserializer());
BLUENBT.register(TypeToken.get(Key.class), new KeyDeserializer());
BLUENBT.register(TypeToken.get(BlockEntity.class), new BlockEntity.BlockEntityDeserializer());
}
/**

View File

@ -30,12 +30,17 @@ import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.DimensionType;
import de.bluecolored.bluemap.core.world.LightData;
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
import de.bluecolored.bluenbt.NBTName;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Chunk_1_13 extends MCAChunk {
private static final Key STATUS_EMPTY = new Key("minecraft", "empty");
@ -58,6 +63,7 @@ public class Chunk_1_13 extends MCAChunk {
private final int sectionMin, sectionMax;
final int[] biomes;
private final Map<Long, BlockEntity> blockEntities;
public Chunk_1_13(MCAWorld world, Data data) {
super(world, data);
@ -113,6 +119,10 @@ public class Chunk_1_13 extends MCAChunk {
this.sectionMin = 0;
this.sectionMax = 0;
}
this.blockEntities = level.blockEntities.stream().collect(Collectors.toMap(
it -> (long) it.getY() << 8 | (it.getX() & 0xF) << 4 | it.getZ() & 0xF, it -> it
));
}
@Override
@ -195,6 +205,11 @@ public class Chunk_1_13 extends MCAChunk {
);
}
@Override
public @Nullable BlockEntity getBlockEntity(int x, int y, int z) {
return blockEntities.get((long) y << 8 | (x & 0xF) << 4 | z & 0xF);
}
private @Nullable Section getSection(int y) {
y -= sectionMin;
if (y < 0 || y >= this.sections.length) return null;
@ -273,6 +288,7 @@ public class Chunk_1_13 extends MCAChunk {
private HeightmapsData heightmaps = new HeightmapsData();
private SectionData @Nullable [] sections = null;
private int[] biomes = EMPTY_INT_ARRAY;
@NBTName("TileEntities") private List<BlockEntity> blockEntities = List.of();
}
@Getter

View File

@ -30,6 +30,7 @@ import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.DimensionType;
import de.bluecolored.bluemap.core.world.LightData;
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
import de.bluecolored.bluemap.core.world.mca.PackedIntArrayAccess;
@ -37,6 +38,10 @@ import de.bluecolored.bluenbt.NBTName;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Chunk_1_16 extends MCAChunk {
private static final Key STATUS_EMPTY = new Key("minecraft", "empty");
@ -57,6 +62,7 @@ public class Chunk_1_16 extends MCAChunk {
private final int sectionMin, sectionMax;
private final int[] biomes;
private final Map<Long, BlockEntity> blockEntities;
public Chunk_1_16(MCAWorld world, Data data) {
super(world, data);
@ -112,6 +118,10 @@ public class Chunk_1_16 extends MCAChunk {
this.sectionMin = 0;
this.sectionMax = 0;
}
this.blockEntities = level.blockEntities.stream().collect(Collectors.toMap(
it -> (long) it.getY() << 8 | (it.getX() & 0xF) << 4 | it.getZ() & 0xF, it -> it
));
}
@Override
@ -191,6 +201,11 @@ public class Chunk_1_16 extends MCAChunk {
return oceanFloorHeights.get((z & 0xF) << 4 | x & 0xF);
}
@Override
public @Nullable BlockEntity getBlockEntity(int x, int y, int z) {
return blockEntities.get((long) y << 8 | (x & 0xF) << 4 | z & 0xF);
}
private @Nullable Section getSection(int y) {
y -= sectionMin;
if (y < 0 || y >= this.sections.length) return null;
@ -261,6 +276,7 @@ public class Chunk_1_16 extends MCAChunk {
private HeightmapsData heightmaps = new HeightmapsData();
private SectionData @Nullable [] sections = null;
private int[] biomes = EMPTY_INT_ARRAY;
@NBTName("TileEntities") private List<BlockEntity> blockEntities = List.of();
}
@Getter

View File

@ -30,6 +30,7 @@ import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.DimensionType;
import de.bluecolored.bluemap.core.world.LightData;
import de.bluecolored.bluemap.core.world.block.entity.BlockEntity;
import de.bluecolored.bluemap.core.world.mca.MCAUtil;
import de.bluecolored.bluemap.core.world.mca.MCAWorld;
import de.bluecolored.bluemap.core.world.mca.PackedIntArrayAccess;
@ -37,6 +38,10 @@ import de.bluecolored.bluenbt.NBTName;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Chunk_1_18 extends MCAChunk {
private static final Key STATUS_EMPTY = new Key("minecraft", "empty");
@ -57,6 +62,8 @@ public class Chunk_1_18 extends MCAChunk {
private final Section[] sections;
private final int sectionMin, sectionMax;
private final Map<Long, BlockEntity> blockEntities;
public Chunk_1_18(MCAWorld world, Data data) {
super(world, data);
@ -108,6 +115,10 @@ public class Chunk_1_18 extends MCAChunk {
this.sectionMin = 0;
this.sectionMax = 0;
}
this.blockEntities = data.blockEntities.stream().collect(Collectors.toMap(
it -> (long) it.getY() << 8 | (it.getX() & 0xF) << 4 | it.getZ() & 0xF, it -> it
));
}
@Override
@ -182,6 +193,11 @@ public class Chunk_1_18 extends MCAChunk {
return oceanFloorHeights.get((z & 0xF) << 4 | x & 0xF) + worldMinY;
}
@Override
public @Nullable BlockEntity getBlockEntity(int x, int y, int z) {
return blockEntities.get((long) y << 8 | (x & 0xF) << 4 | z & 0xF);
}
private @Nullable Section getSection(int y) {
y -= sectionMin;
if (y < 0 || y >= this.sections.length) return null;
@ -263,6 +279,7 @@ public class Chunk_1_18 extends MCAChunk {
private long inhabitedTime = 0;
private HeightmapsData heightmaps = new HeightmapsData();
private SectionData @Nullable [] sections = null;
@NBTName("block_entities") private List<BlockEntity> blockEntities = List.of();
}
@Getter