implement headless on all supported versions

This commit is contained in:
Julian Krings 2025-02-08 12:07:13 +01:00
parent c2dfbac641
commit 013bc365a9
No known key found for this signature in database
GPG Key ID: 208C6E08C3B718D2
26 changed files with 3328 additions and 3 deletions

View File

@ -122,9 +122,7 @@ public interface INMSBinding {
return 441;
}
default IRegionStorage createRegionStorage(Engine engine) {
return null;
}
IRegionStorage createRegionStorage(Engine engine);
KList<String> getStructureKeys();
}

View File

@ -21,6 +21,7 @@ package com.volmit.iris.core.nms.v1X;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
@ -109,6 +110,11 @@ public class NMSBinding1X implements INMSBinding {
return Color.GREEN;
}
@Override
public IRegionStorage createRegionStorage(Engine engine) {
return null;
}
@Override
public KList<String> getStructureKeys() {
var list = StreamSupport.stream(Registry.STRUCTURE.spliterator(), false)

View File

@ -5,6 +5,8 @@ import com.mojang.datafixers.util.Pair;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.v1_20_R1.headless.RegionStorage;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.util.collection.KList;
@ -628,4 +630,9 @@ public class NMSBinding implements INMSBinding {
}
}
}
@Override
public IRegionStorage createRegionStorage(Engine engine) {
return new RegionStorage(engine);
}
}

View File

@ -0,0 +1,209 @@
package com.volmit.iris.core.nms.v1_20_R1.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.util.data.IrisCustomData;
import com.volmit.iris.util.math.Position2;
import lombok.Data;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
import static com.volmit.iris.core.nms.v1_20_R1.headless.RegionStorage.registryAccess;
@Data
public final class DirectTerrainChunk implements SerializableChunk {
private final ChunkAccess access;
private final int minHeight, maxHeight;
private final Registry<net.minecraft.world.level.biome.Biome> biomes;
public DirectTerrainChunk(ChunkAccess access) {
this.access = access;
this.minHeight = access.getMinBuildHeight();
this.maxHeight = access.getMaxBuildHeight();
this.biomes = registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME);
}
@Override
public BiomeBaseInjector getBiomeBaseInjector() {
return null;
}
@NotNull
@Override
public Biome getBiome(int x, int z) {
return getBiome(x, 0, z);
}
@NotNull
@Override
public Biome getBiome(int x, int y, int z) {
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
return CraftBlock.biomeBaseToBiome(biomes, access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
}
@Override
public void setBiome(int x, int z, Biome bio) {
for (int y = minHeight; y < maxHeight; y += 4) {
setBiome(x, y, z, bio);
}
}
@Override
public void setBiome(int x, int y, int z, Biome bio) {
if (y < minHeight || y > maxHeight) return;
access.setBiome(x & 15, y, z & 15, CraftBlock.biomeToBiomeBase(biomes, bio));
}
public void setBlock(int x, int y, int z, Material material) {
this.setBlock(x, y, z, material.createBlockData());
}
public void setBlock(int x, int y, int z, MaterialData material) {
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
}
@Override
public void setBlock(int x, int y, int z, BlockData blockData) {
if (blockData == null) {
Iris.error("NULL BD");
}
if (blockData instanceof IrisCustomData data)
blockData = data.getBase();
if (!(blockData instanceof CraftBlockData craftBlockData))
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
}
public Material getType(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
}
public MaterialData getTypeAndData(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
}
public BlockData getBlockData(int x, int y, int z) {
return CraftBlockData.fromData(this.getTypeId(x, y, z));
}
@Override
public ChunkGenerator.ChunkData getRaw() {
return null;
}
@Override
public void setRaw(ChunkGenerator.ChunkData data) {
}
@Override
public void inject(ChunkGenerator.BiomeGrid biome) {
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
return;
if (xMin < 0) {
xMin = 0;
}
if (yMin < this.minHeight) {
yMin = this.minHeight;
}
if (zMin < 0) {
zMin = 0;
}
if (xMax > 16) {
xMax = 16;
}
if (yMax > this.maxHeight) {
yMax = this.maxHeight;
}
if (zMax > 16) {
zMax = 16;
}
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
return;
for (int y = yMin; y < yMax; ++y) {
for (int x = xMin; x < xMax; ++x) {
for (int z = zMin; z < zMax; ++z) {
this.setBlock(x, y, z, type);
}
}
}
}
public BlockState getTypeId(int x, int y, int z) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return Blocks.AIR.defaultBlockState();
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
}
public byte getData(int x, int y, int z) {
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
}
private void setBlock(int x, int y, int z, BlockState type) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return;
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
if (type.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
if (tileEntity == null) {
access.removeBlockEntity(blockPosition);
} else {
access.setBlockEntity(tileEntity);
}
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
access.removeBlockEntity(blockPosition);
}
}
@Override
public Position2 getPos() {
return new Position2(access.getPos().x, access.getPos().z);
}
@Override
public Object serialize() {
return RegionStorage.serialize(access);
}
}

View File

@ -0,0 +1,60 @@
package com.volmit.iris.core.nms.v1_20_R1.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import lombok.NonNull;
import lombok.Synchronized;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFile;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
class Region implements IRegion {
private final RegionFile regionFile;
transient long references;
Region(Path path, Path folder) throws IOException {
this.regionFile = new RegionFile(path, folder, true);
}
@Override
@Synchronized
public boolean exists(int x, int z) {
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
if (din == null) return false;
return !"empty".equals(NbtIo.read(din).getString("Status"));
} catch (IOException e) {
return false;
}
}
@Override
@Synchronized
public void write(@NonNull SerializableChunk chunk) throws IOException {
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
NbtIo.write((CompoundTag) chunk.serialize(), dos);
}
}
@Override
public void close() {
--references;
}
public boolean remove() {
if (references > 0) return false;
try {
regionFile.close();
} catch (IOException e) {
Iris.error("Failed to close region file");
e.printStackTrace();
}
return true;
}
}

View File

@ -0,0 +1,292 @@
package com.volmit.iris.core.nms.v1_20_R1.headless;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext;
import com.volmit.iris.util.math.RNG;
import lombok.Getter;
import lombok.NonNull;
import net.minecraft.FileUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.*;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
private final KMap<Long, Region> regions = new KMap<>();
private final Path folder;
private final Engine engine;
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
private final RNG biomeRng;
private final @Getter int minBuildHeight;
private final @Getter int height;
private transient boolean closed = false;
public RegionStorage(Engine engine) {
this.engine = engine;
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
this.minBuildHeight = engine.getDimension().getMinHeight();
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
AtomicInteger failed = new AtomicInteger();
var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) {
if (!biome.isCustom()) continue;
for (var custom : biome.getCustomDerivitives()) {
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
failed.incrementAndGet();
});
}
}
if (failed.get() > 0) {
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
}
Registry<Biome> registry = registryAccess().registryOrThrow(Registries.BIOME);
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
.collect(Collectors.toMap(Function.identity(), b -> CraftBlock.biomeToBiomeBase(registry, b))));
minecraftBiomes.values().removeAll(customBiomes.values());
}
@Override
public boolean exists(int x, int z) {
try (IRegion region = getRegion(x, z, true)) {
return region != null && region.exists(x, z);
} catch (Exception e) {
return false;
}
}
@Override
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
AtomicReference<IOException> exception = new AtomicReference<>();
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
if (regions.size() >= 256) {
regions.values().removeIf(Region::remove);
}
try {
FileUtil.createDirectoriesSafe(this.folder);
Path path = folder.resolve("r." + x + "." + z + ".mca");
if (existingOnly && !Files.exists(path)) {
return null;
} else {
return new Region(path, this.folder);
}
} catch (IOException e) {
exception.set(e);
return null;
}
});
if (region == null) {
if (exception.get() != null)
throw exception.get();
return null;
}
region.references++;
return region;
}
@NotNull
@Override
public SerializableChunk createChunk(int x, int z) {
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
}
@Override
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
if (!(chunk instanceof DirectTerrainChunk tc))
return;
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
}
@Override
public synchronized void close() {
if (closed) return;
while (!regions.isEmpty()) {
regions.values().removeIf(Region::remove);
}
closed = true;
customBiomes.clear();
minecraftBiomes.clear();
}
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
int m = y - engine.getMinHeight();
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
if (ib.isCustom()) {
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
} else {
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
}
}
static RegistryAccess registryAccess() {
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
}
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, path)));
}
static CompoundTag serialize(ChunkAccess chunk) {
ChunkPos chunkPos = chunk.getPos();
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
tag.putInt("xPos", chunkPos.x);
tag.putInt("yPos", chunk.getMinSection());
tag.putInt("zPos", chunkPos.z);
tag.putLong("LastUpdate", 0);
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
BlendingData blendingdata = chunk.getBlendingData();
if (blendingdata != null) {
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
}
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
if (retrogen != null) {
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
}
UpgradeData upgradeData = chunk.getUpgradeData();
if (!upgradeData.isEmpty()) {
tag.put("UpgradeData", upgradeData.write());
}
LevelChunkSection[] sections = chunk.getSections();
ListTag sectionsTag = new ListTag();
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
boolean flag = chunk.isLightCorrect();
int minLightSection = chunk.getMinSection() - 1;
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
for(int y = minLightSection; y < maxLightSection; ++y) {
int j = chunk.getSectionIndexFromSectionY(y);
if (j < 0 || j >= sections.length)
continue;
CompoundTag sectionTag = new CompoundTag();
LevelChunkSection section = sections[j];
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, LogUtils.getLogger()::error));
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, LogUtils.getLogger()::error));
if (!sectionTag.isEmpty()) {
sectionTag.putByte("Y", (byte) y);
sectionsTag.add(sectionTag);
}
}
tag.put("sections", sectionsTag);
if (flag) {
tag.putBoolean("isLightOn", true);
}
ListTag blockEntities = new ListTag();
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
if (entityNbt != null) {
blockEntities.add(entityNbt);
}
}
tag.put("block_entities", blockEntities);
if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.PROTOCHUNK) {
ProtoChunk protochunk = (ProtoChunk)chunk;
ListTag entities = new ListTag();
entities.addAll(protochunk.getEntities());
tag.put("entities", entities);
CompoundTag carvingMasks = new CompoundTag();
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
CarvingMask mask = protochunk.getCarvingMask(carving);
if (mask != null) {
carvingMasks.putLongArray(carving.toString(), mask.toArray());
}
}
tag.put("CarvingMasks", carvingMasks);
}
saveTicks(tag, chunk.getTicksForSerialization());
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
CompoundTag heightMaps = new CompoundTag();
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
}
}
tag.put("Heightmaps", heightMaps);
CompoundTag structureData = new CompoundTag();
structureData.put("starts", new CompoundTag());
structureData.put("References", new CompoundTag());
tag.put("structures", structureData);
if (!chunk.persistentDataContainer.isEmpty()) {
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
}
return tag;
}
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
}
}

View File

@ -16,6 +16,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.v1_20_R2.headless.RegionStorage;
import com.volmit.iris.util.scheduling.J;
import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag;
@ -630,4 +632,9 @@ public class NMSBinding implements INMSBinding {
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
}
@Override
public IRegionStorage createRegionStorage(Engine engine) {
return new RegionStorage(engine);
}
}

View File

@ -0,0 +1,204 @@
package com.volmit.iris.core.nms.v1_20_R2.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.util.data.IrisCustomData;
import com.volmit.iris.util.math.Position2;
import lombok.Data;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBiome;
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_20_R2.util.CraftMagicNumbers;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
@Data
public final class DirectTerrainChunk implements SerializableChunk {
private final ChunkAccess access;
private final int minHeight, maxHeight;
public DirectTerrainChunk(ChunkAccess access) {
this.access = access;
this.minHeight = access.getMinBuildHeight();
this.maxHeight = access.getMaxBuildHeight();
}
@Override
public BiomeBaseInjector getBiomeBaseInjector() {
return null;
}
@NotNull
@Override
public Biome getBiome(int x, int z) {
return getBiome(x, 0, z);
}
@NotNull
@Override
public Biome getBiome(int x, int y, int z) {
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
}
@Override
public void setBiome(int x, int z, Biome bio) {
for (int y = minHeight; y < maxHeight; y += 4) {
setBiome(x, y, z, bio);
}
}
@Override
public void setBiome(int x, int y, int z, Biome bio) {
if (y < minHeight || y > maxHeight) return;
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
}
public void setBlock(int x, int y, int z, Material material) {
this.setBlock(x, y, z, material.createBlockData());
}
public void setBlock(int x, int y, int z, MaterialData material) {
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
}
@Override
public void setBlock(int x, int y, int z, BlockData blockData) {
if (blockData == null) {
Iris.error("NULL BD");
}
if (blockData instanceof IrisCustomData data)
blockData = data.getBase();
if (!(blockData instanceof CraftBlockData craftBlockData))
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
}
public Material getType(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
}
public MaterialData getTypeAndData(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
}
public BlockData getBlockData(int x, int y, int z) {
return CraftBlockData.fromData(this.getTypeId(x, y, z));
}
@Override
public ChunkGenerator.ChunkData getRaw() {
return null;
}
@Override
public void setRaw(ChunkGenerator.ChunkData data) {
}
@Override
public void inject(ChunkGenerator.BiomeGrid biome) {
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
return;
if (xMin < 0) {
xMin = 0;
}
if (yMin < this.minHeight) {
yMin = this.minHeight;
}
if (zMin < 0) {
zMin = 0;
}
if (xMax > 16) {
xMax = 16;
}
if (yMax > this.maxHeight) {
yMax = this.maxHeight;
}
if (zMax > 16) {
zMax = 16;
}
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
return;
for (int y = yMin; y < yMax; ++y) {
for (int x = xMin; x < xMax; ++x) {
for (int z = zMin; z < zMax; ++z) {
this.setBlock(x, y, z, type);
}
}
}
}
public BlockState getTypeId(int x, int y, int z) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return Blocks.AIR.defaultBlockState();
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
}
public byte getData(int x, int y, int z) {
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
}
private void setBlock(int x, int y, int z, BlockState type) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return;
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
if (type.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
if (tileEntity == null) {
access.removeBlockEntity(blockPosition);
} else {
access.setBlockEntity(tileEntity);
}
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
access.removeBlockEntity(blockPosition);
}
}
@Override
public Position2 getPos() {
return new Position2(access.getPos().x, access.getPos().z);
}
@Override
public Object serialize() {
return RegionStorage.serialize(access);
}
}

View File

@ -0,0 +1,60 @@
package com.volmit.iris.core.nms.v1_20_R2.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import lombok.NonNull;
import lombok.Synchronized;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFile;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
class Region implements IRegion {
private final RegionFile regionFile;
transient long references;
Region(Path path, Path folder) throws IOException {
this.regionFile = new RegionFile(path, folder, true);
}
@Override
@Synchronized
public boolean exists(int x, int z) {
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
if (din == null) return false;
return !"empty".equals(NbtIo.read(din).getString("Status"));
} catch (IOException e) {
return false;
}
}
@Override
@Synchronized
public void write(@NonNull SerializableChunk chunk) throws IOException {
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
NbtIo.write((CompoundTag) chunk.serialize(), dos);
}
}
@Override
public void close() {
--references;
}
public boolean remove() {
if (references > 0) return false;
try {
regionFile.close();
} catch (IOException e) {
Iris.error("Failed to close region file");
e.printStackTrace();
}
return true;
}
}

View File

@ -0,0 +1,291 @@
package com.volmit.iris.core.nms.v1_20_R2.headless;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext;
import com.volmit.iris.util.math.RNG;
import lombok.Getter;
import lombok.NonNull;
import net.minecraft.FileUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.*;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBiome;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
private final KMap<Long, Region> regions = new KMap<>();
private final Path folder;
private final Engine engine;
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
private final RNG biomeRng;
private final @Getter int minBuildHeight;
private final @Getter int height;
private transient boolean closed = false;
public RegionStorage(Engine engine) {
this.engine = engine;
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
this.minBuildHeight = engine.getDimension().getMinHeight();
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
AtomicInteger failed = new AtomicInteger();
var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) {
if (!biome.isCustom()) continue;
for (var custom : biome.getCustomDerivitives()) {
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
failed.incrementAndGet();
});
}
}
if (failed.get() > 0) {
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
}
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
minecraftBiomes.values().removeAll(customBiomes.values());
}
@Override
public boolean exists(int x, int z) {
try (IRegion region = getRegion(x, z, true)) {
return region != null && region.exists(x, z);
} catch (Exception e) {
return false;
}
}
@Override
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
AtomicReference<IOException> exception = new AtomicReference<>();
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
if (regions.size() >= 256) {
regions.values().removeIf(Region::remove);
}
try {
FileUtil.createDirectoriesSafe(this.folder);
Path path = folder.resolve("r." + x + "." + z + ".mca");
if (existingOnly && !Files.exists(path)) {
return null;
} else {
return new Region(path, this.folder);
}
} catch (IOException e) {
exception.set(e);
return null;
}
});
if (region == null) {
if (exception.get() != null)
throw exception.get();
return null;
}
region.references++;
return region;
}
@NotNull
@Override
public SerializableChunk createChunk(int x, int z) {
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
}
@Override
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
if (!(chunk instanceof DirectTerrainChunk tc))
return;
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
}
@Override
public synchronized void close() {
if (closed) return;
while (!regions.isEmpty()) {
regions.values().removeIf(Region::remove);
}
closed = true;
customBiomes.clear();
minecraftBiomes.clear();
}
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
int m = y - engine.getMinHeight();
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
if (ib.isCustom()) {
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
} else {
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
}
}
private static RegistryAccess registryAccess() {
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
}
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, path)));
}
static CompoundTag serialize(ChunkAccess chunk) {
ChunkPos chunkPos = chunk.getPos();
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
tag.putInt("xPos", chunkPos.x);
tag.putInt("yPos", chunk.getMinSection());
tag.putInt("zPos", chunkPos.z);
tag.putLong("LastUpdate", 0);
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
BlendingData blendingdata = chunk.getBlendingData();
if (blendingdata != null) {
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
}
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
if (retrogen != null) {
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
}
UpgradeData upgradeData = chunk.getUpgradeData();
if (!upgradeData.isEmpty()) {
tag.put("UpgradeData", upgradeData.write());
}
LevelChunkSection[] sections = chunk.getSections();
ListTag sectionsTag = new ListTag();
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
boolean flag = chunk.isLightCorrect();
int minLightSection = chunk.getMinSection() - 1;
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
for(int y = minLightSection; y < maxLightSection; ++y) {
int j = chunk.getSectionIndexFromSectionY(y);
if (j < 0 || j >= sections.length)
continue;
CompoundTag sectionTag = new CompoundTag();
LevelChunkSection section = sections[j];
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, LogUtils.getLogger()::error));
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, LogUtils.getLogger()::error));
if (!sectionTag.isEmpty()) {
sectionTag.putByte("Y", (byte) y);
sectionsTag.add(sectionTag);
}
}
tag.put("sections", sectionsTag);
if (flag) {
tag.putBoolean("isLightOn", true);
}
ListTag blockEntities = new ListTag();
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
if (entityNbt != null) {
blockEntities.add(entityNbt);
}
}
tag.put("block_entities", blockEntities);
if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.PROTOCHUNK) {
ProtoChunk protochunk = (ProtoChunk)chunk;
ListTag entities = new ListTag();
entities.addAll(protochunk.getEntities());
tag.put("entities", entities);
CompoundTag carvingMasks = new CompoundTag();
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
CarvingMask mask = protochunk.getCarvingMask(carving);
if (mask != null) {
carvingMasks.putLongArray(carving.toString(), mask.toArray());
}
}
tag.put("CarvingMasks", carvingMasks);
}
saveTicks(tag, chunk.getTicksForSerialization());
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
CompoundTag heightMaps = new CompoundTag();
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
}
}
tag.put("Heightmaps", heightMaps);
CompoundTag structureData = new CompoundTag();
structureData.put("starts", new CompoundTag());
structureData.put("References", new CompoundTag());
tag.put("structures", structureData);
if (!chunk.persistentDataContainer.isEmpty()) {
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
}
return tag;
}
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
}
}

View File

@ -16,6 +16,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.v1_20_R3.headless.RegionStorage;
import com.volmit.iris.util.scheduling.J;
import net.minecraft.nbt.*;
import net.minecraft.nbt.Tag;
@ -631,4 +633,9 @@ public class NMSBinding implements INMSBinding {
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
}
@Override
public IRegionStorage createRegionStorage(Engine engine) {
return new RegionStorage(engine);
}
}

View File

@ -0,0 +1,205 @@
package com.volmit.iris.core.nms.v1_20_R3.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.util.data.IrisCustomData;
import com.volmit.iris.util.math.Position2;
import lombok.Data;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockType;
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_20_R3.util.CraftMagicNumbers;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
@Data
public final class DirectTerrainChunk implements SerializableChunk {
private final ChunkAccess access;
private final int minHeight, maxHeight;
public DirectTerrainChunk(ChunkAccess access) {
this.access = access;
this.minHeight = access.getMinBuildHeight();
this.maxHeight = access.getMaxBuildHeight();
}
@Override
public BiomeBaseInjector getBiomeBaseInjector() {
return null;
}
@NotNull
@Override
public Biome getBiome(int x, int z) {
return getBiome(x, 0, z);
}
@NotNull
@Override
public Biome getBiome(int x, int y, int z) {
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
}
@Override
public void setBiome(int x, int z, Biome bio) {
for (int y = minHeight; y < maxHeight; y += 4) {
setBiome(x, y, z, bio);
}
}
@Override
public void setBiome(int x, int y, int z, Biome bio) {
if (y < minHeight || y > maxHeight) return;
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
}
public void setBlock(int x, int y, int z, Material material) {
this.setBlock(x, y, z, material.createBlockData());
}
public void setBlock(int x, int y, int z, MaterialData material) {
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
}
@Override
public void setBlock(int x, int y, int z, BlockData blockData) {
if (blockData == null) {
Iris.error("NULL BD");
}
if (blockData instanceof IrisCustomData data)
blockData = data.getBase();
if (!(blockData instanceof CraftBlockData craftBlockData))
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
}
public Material getType(int x, int y, int z) {
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
}
public MaterialData getTypeAndData(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
}
public BlockData getBlockData(int x, int y, int z) {
return CraftBlockData.fromData(this.getTypeId(x, y, z));
}
@Override
public ChunkGenerator.ChunkData getRaw() {
return null;
}
@Override
public void setRaw(ChunkGenerator.ChunkData data) {
}
@Override
public void inject(ChunkGenerator.BiomeGrid biome) {
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
return;
if (xMin < 0) {
xMin = 0;
}
if (yMin < this.minHeight) {
yMin = this.minHeight;
}
if (zMin < 0) {
zMin = 0;
}
if (xMax > 16) {
xMax = 16;
}
if (yMax > this.maxHeight) {
yMax = this.maxHeight;
}
if (zMax > 16) {
zMax = 16;
}
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
return;
for (int y = yMin; y < yMax; ++y) {
for (int x = xMin; x < xMax; ++x) {
for (int z = zMin; z < zMax; ++z) {
this.setBlock(x, y, z, type);
}
}
}
}
public BlockState getTypeId(int x, int y, int z) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return Blocks.AIR.defaultBlockState();
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
}
public byte getData(int x, int y, int z) {
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
}
private void setBlock(int x, int y, int z, BlockState type) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return;
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
if (type.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
if (tileEntity == null) {
access.removeBlockEntity(blockPosition);
} else {
access.setBlockEntity(tileEntity);
}
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
access.removeBlockEntity(blockPosition);
}
}
@Override
public Position2 getPos() {
return new Position2(access.getPos().x, access.getPos().z);
}
@Override
public Object serialize() {
return RegionStorage.serialize(access);
}
}

View File

@ -0,0 +1,60 @@
package com.volmit.iris.core.nms.v1_20_R3.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import lombok.NonNull;
import lombok.Synchronized;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFile;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
class Region implements IRegion {
private final RegionFile regionFile;
transient long references;
Region(Path path, Path folder) throws IOException {
this.regionFile = new RegionFile(path, folder, true);
}
@Override
@Synchronized
public boolean exists(int x, int z) {
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
if (din == null) return false;
return !"empty".equals(NbtIo.read(din).getString("Status"));
} catch (IOException e) {
return false;
}
}
@Override
@Synchronized
public void write(@NonNull SerializableChunk chunk) throws IOException {
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
NbtIo.write((CompoundTag) chunk.serialize(), dos);
}
}
@Override
public void close() {
--references;
}
public boolean remove() {
if (references > 0) return false;
try {
regionFile.close();
} catch (IOException e) {
Iris.error("Failed to close region file");
e.printStackTrace();
}
return true;
}
}

View File

@ -0,0 +1,291 @@
package com.volmit.iris.core.nms.v1_20_R3.headless;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext;
import com.volmit.iris.util.math.RNG;
import lombok.Getter;
import lombok.NonNull;
import net.minecraft.FileUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.*;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
private final KMap<Long, Region> regions = new KMap<>();
private final Path folder;
private final Engine engine;
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
private final RNG biomeRng;
private final @Getter int minBuildHeight;
private final @Getter int height;
private transient boolean closed = false;
public RegionStorage(Engine engine) {
this.engine = engine;
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
this.minBuildHeight = engine.getDimension().getMinHeight();
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
AtomicInteger failed = new AtomicInteger();
var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) {
if (!biome.isCustom()) continue;
for (var custom : biome.getCustomDerivitives()) {
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
failed.incrementAndGet();
});
}
}
if (failed.get() > 0) {
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
}
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
minecraftBiomes.values().removeAll(customBiomes.values());
}
@Override
public boolean exists(int x, int z) {
try (IRegion region = getRegion(x, z, true)) {
return region != null && region.exists(x, z);
} catch (Exception e) {
return false;
}
}
@Override
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
AtomicReference<IOException> exception = new AtomicReference<>();
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
if (regions.size() >= 256) {
regions.values().removeIf(Region::remove);
}
try {
FileUtil.createDirectoriesSafe(this.folder);
Path path = folder.resolve("r." + x + "." + z + ".mca");
if (existingOnly && !Files.exists(path)) {
return null;
} else {
return new Region(path, this.folder);
}
} catch (IOException e) {
exception.set(e);
return null;
}
});
if (region == null) {
if (exception.get() != null)
throw exception.get();
return null;
}
region.references++;
return region;
}
@NotNull
@Override
public SerializableChunk createChunk(int x, int z) {
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
}
@Override
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
if (!(chunk instanceof DirectTerrainChunk tc))
return;
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
}
@Override
public synchronized void close() {
if (closed) return;
while (!regions.isEmpty()) {
regions.values().removeIf(Region::remove);
}
closed = true;
customBiomes.clear();
minecraftBiomes.clear();
}
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
int m = y - engine.getMinHeight();
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
if (ib.isCustom()) {
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
} else {
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
}
}
private static RegistryAccess registryAccess() {
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
}
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, path)));
}
static CompoundTag serialize(ChunkAccess chunk) {
ChunkPos chunkPos = chunk.getPos();
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
tag.putInt("xPos", chunkPos.x);
tag.putInt("yPos", chunk.getMinSection());
tag.putInt("zPos", chunkPos.z);
tag.putLong("LastUpdate", 0);
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
BlendingData blendingdata = chunk.getBlendingData();
if (blendingdata != null) {
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
}
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
if (retrogen != null) {
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
}
UpgradeData upgradeData = chunk.getUpgradeData();
if (!upgradeData.isEmpty()) {
tag.put("UpgradeData", upgradeData.write());
}
LevelChunkSection[] sections = chunk.getSections();
ListTag sectionsTag = new ListTag();
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
boolean flag = chunk.isLightCorrect();
int minLightSection = chunk.getMinSection() - 1;
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
for(int y = minLightSection; y < maxLightSection; ++y) {
int j = chunk.getSectionIndexFromSectionY(y);
if (j < 0 || j >= sections.length)
continue;
CompoundTag sectionTag = new CompoundTag();
LevelChunkSection section = sections[j];
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, LogUtils.getLogger()::error));
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, LogUtils.getLogger()::error));
if (!sectionTag.isEmpty()) {
sectionTag.putByte("Y", (byte) y);
sectionsTag.add(sectionTag);
}
}
tag.put("sections", sectionsTag);
if (flag) {
tag.putBoolean("isLightOn", true);
}
ListTag blockEntities = new ListTag();
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
if (entityNbt != null) {
blockEntities.add(entityNbt);
}
}
tag.put("block_entities", blockEntities);
if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.PROTOCHUNK) {
ProtoChunk protochunk = (ProtoChunk)chunk;
ListTag entities = new ListTag();
entities.addAll(protochunk.getEntities());
tag.put("entities", entities);
CompoundTag carvingMasks = new CompoundTag();
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
CarvingMask mask = protochunk.getCarvingMask(carving);
if (mask != null) {
carvingMasks.putLongArray(carving.toString(), mask.toArray());
}
}
tag.put("CarvingMasks", carvingMasks);
}
saveTicks(tag, chunk.getTicksForSerialization());
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
CompoundTag heightMaps = new CompoundTag();
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
}
}
tag.put("Heightmaps", heightMaps);
CompoundTag structureData = new CompoundTag();
structureData.put("starts", new CompoundTag());
structureData.put("References", new CompoundTag());
tag.put("structures", structureData);
if (!chunk.persistentDataContainer.isEmpty()) {
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
}
return tag;
}
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
}
}

View File

@ -10,6 +10,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.v1_20_R4.headless.RegionStorage;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import com.volmit.iris.util.scheduling.J;
import net.minecraft.core.*;
@ -650,4 +652,9 @@ public class NMSBinding implements INMSBinding {
return keys;
}
@Override
public IRegionStorage createRegionStorage(Engine engine) {
return new RegionStorage(engine);
}
}

View File

@ -0,0 +1,205 @@
package com.volmit.iris.core.nms.v1_20_R4.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.util.data.IrisCustomData;
import com.volmit.iris.util.math.Position2;
import lombok.Data;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBiome;
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockType;
import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_20_R4.util.CraftMagicNumbers;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
@Data
public final class DirectTerrainChunk implements SerializableChunk {
private final ChunkAccess access;
private final int minHeight, maxHeight;
public DirectTerrainChunk(ChunkAccess access) {
this.access = access;
this.minHeight = access.getMinBuildHeight();
this.maxHeight = access.getMaxBuildHeight();
}
@Override
public BiomeBaseInjector getBiomeBaseInjector() {
return null;
}
@NotNull
@Override
public Biome getBiome(int x, int z) {
return getBiome(x, 0, z);
}
@NotNull
@Override
public Biome getBiome(int x, int y, int z) {
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
}
@Override
public void setBiome(int x, int z, Biome bio) {
for (int y = minHeight; y < maxHeight; y += 4) {
setBiome(x, y, z, bio);
}
}
@Override
public void setBiome(int x, int y, int z, Biome bio) {
if (y < minHeight || y > maxHeight) return;
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
}
public void setBlock(int x, int y, int z, Material material) {
this.setBlock(x, y, z, material.createBlockData());
}
public void setBlock(int x, int y, int z, MaterialData material) {
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
}
@Override
public void setBlock(int x, int y, int z, BlockData blockData) {
if (blockData == null) {
Iris.error("NULL BD");
}
if (blockData instanceof IrisCustomData data)
blockData = data.getBase();
if (!(blockData instanceof CraftBlockData craftBlockData))
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
}
public Material getType(int x, int y, int z) {
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
}
public MaterialData getTypeAndData(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
}
public BlockData getBlockData(int x, int y, int z) {
return CraftBlockData.fromData(this.getTypeId(x, y, z));
}
@Override
public ChunkGenerator.ChunkData getRaw() {
return null;
}
@Override
public void setRaw(ChunkGenerator.ChunkData data) {
}
@Override
public void inject(ChunkGenerator.BiomeGrid biome) {
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
return;
if (xMin < 0) {
xMin = 0;
}
if (yMin < this.minHeight) {
yMin = this.minHeight;
}
if (zMin < 0) {
zMin = 0;
}
if (xMax > 16) {
xMax = 16;
}
if (yMax > this.maxHeight) {
yMax = this.maxHeight;
}
if (zMax > 16) {
zMax = 16;
}
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
return;
for (int y = yMin; y < yMax; ++y) {
for (int x = xMin; x < xMax; ++x) {
for (int z = zMin; z < zMax; ++z) {
this.setBlock(x, y, z, type);
}
}
}
}
public BlockState getTypeId(int x, int y, int z) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return Blocks.AIR.defaultBlockState();
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
}
public byte getData(int x, int y, int z) {
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
}
private void setBlock(int x, int y, int z, BlockState type) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return;
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
if (type.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
if (tileEntity == null) {
access.removeBlockEntity(blockPosition);
} else {
access.setBlockEntity(tileEntity);
}
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
access.removeBlockEntity(blockPosition);
}
}
@Override
public Position2 getPos() {
return new Position2(access.getPos().x, access.getPos().z);
}
@Override
public Object serialize() {
return RegionStorage.serialize(access);
}
}

View File

@ -0,0 +1,63 @@
package com.volmit.iris.core.nms.v1_20_R4.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import lombok.NonNull;
import lombok.Synchronized;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.storage.RegionFile;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
class Region implements IRegion {
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
private final RegionFile regionFile;
transient long references;
Region(Path path, Path folder) throws IOException {
this.regionFile = new RegionFile(info, path, folder, true);
}
@Override
@Synchronized
public boolean exists(int x, int z) {
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
if (din == null) return false;
return !"empty".equals(NbtIo.read(din).getString("Status"));
} catch (IOException e) {
return false;
}
}
@Override
@Synchronized
public void write(@NonNull SerializableChunk chunk) throws IOException {
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
NbtIo.write((CompoundTag) chunk.serialize(), dos);
}
}
@Override
public void close() {
--references;
}
public boolean remove() {
if (references > 0) return false;
try {
regionFile.close();
} catch (IOException e) {
Iris.error("Failed to close region file");
e.printStackTrace();
}
return true;
}
}

View File

@ -0,0 +1,291 @@
package com.volmit.iris.core.nms.v1_20_R4.headless;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext;
import com.volmit.iris.util.math.RNG;
import lombok.Getter;
import lombok.NonNull;
import net.minecraft.FileUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.chunk.status.ChunkType;
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_20_R4.CraftServer;
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBiome;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
private final KMap<Long, Region> regions = new KMap<>();
private final Path folder;
private final Engine engine;
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
private final RNG biomeRng;
private final @Getter int minBuildHeight;
private final @Getter int height;
private transient boolean closed = false;
public RegionStorage(Engine engine) {
this.engine = engine;
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
this.minBuildHeight = engine.getDimension().getMinHeight();
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
AtomicInteger failed = new AtomicInteger();
var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) {
if (!biome.isCustom()) continue;
for (var custom : biome.getCustomDerivitives()) {
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
failed.incrementAndGet();
});
}
}
if (failed.get() > 0) {
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
}
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
minecraftBiomes.values().removeAll(customBiomes.values());
}
@Override
public boolean exists(int x, int z) {
try (IRegion region = getRegion(x, z, true)) {
return region != null && region.exists(x, z);
} catch (Exception e) {
return false;
}
}
@Override
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
AtomicReference<IOException> exception = new AtomicReference<>();
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
if (regions.size() >= 256) {
regions.values().removeIf(Region::remove);
}
try {
FileUtil.createDirectoriesSafe(this.folder);
Path path = folder.resolve("r." + x + "." + z + ".mca");
if (existingOnly && !Files.exists(path)) {
return null;
} else {
return new Region(path, this.folder);
}
} catch (IOException e) {
exception.set(e);
return null;
}
});
if (region == null) {
if (exception.get() != null)
throw exception.get();
return null;
}
region.references++;
return region;
}
@NotNull
@Override
public SerializableChunk createChunk(int x, int z) {
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
}
@Override
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
if (!(chunk instanceof DirectTerrainChunk tc))
return;
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
}
@Override
public synchronized void close() {
if (closed) return;
while (!regions.isEmpty()) {
regions.values().removeIf(Region::remove);
}
closed = true;
customBiomes.clear();
minecraftBiomes.clear();
}
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
int m = y - engine.getMinHeight();
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
if (ib.isCustom()) {
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
} else {
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
}
}
private static RegistryAccess registryAccess() {
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
}
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(new ResourceLocation(namespace, path));
}
static CompoundTag serialize(ChunkAccess chunk) {
ChunkPos chunkPos = chunk.getPos();
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
tag.putInt("xPos", chunkPos.x);
tag.putInt("yPos", chunk.getMinSection());
tag.putInt("zPos", chunkPos.z);
tag.putLong("LastUpdate", 0);
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
BlendingData blendingdata = chunk.getBlendingData();
if (blendingdata != null) {
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
}
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
if (retrogen != null) {
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
}
UpgradeData upgradeData = chunk.getUpgradeData();
if (!upgradeData.isEmpty()) {
tag.put("UpgradeData", upgradeData.write());
}
LevelChunkSection[] sections = chunk.getSections();
ListTag sectionsTag = new ListTag();
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
boolean flag = chunk.isLightCorrect();
int minLightSection = chunk.getMinSection() - 1;
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
for(int y = minLightSection; y < maxLightSection; ++y) {
int j = chunk.getSectionIndexFromSectionY(y);
if (j < 0 || j >= sections.length)
continue;
CompoundTag sectionTag = new CompoundTag();
LevelChunkSection section = sections[j];
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow());
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow());
if (!sectionTag.isEmpty()) {
sectionTag.putByte("Y", (byte) y);
sectionsTag.add(sectionTag);
}
}
tag.put("sections", sectionsTag);
if (flag) {
tag.putBoolean("isLightOn", true);
}
ListTag blockEntities = new ListTag();
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos, registryAccess());
if (entityNbt != null) {
blockEntities.add(entityNbt);
}
}
tag.put("block_entities", blockEntities);
if (chunk.getStatus().getChunkType() == ChunkType.PROTOCHUNK) {
ProtoChunk protochunk = (ProtoChunk)chunk;
ListTag entities = new ListTag();
entities.addAll(protochunk.getEntities());
tag.put("entities", entities);
CompoundTag carvingMasks = new CompoundTag();
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
CarvingMask mask = protochunk.getCarvingMask(carving);
if (mask != null) {
carvingMasks.putLongArray(carving.toString(), mask.toArray());
}
}
tag.put("CarvingMasks", carvingMasks);
}
saveTicks(tag, chunk.getTicksForSerialization());
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
CompoundTag heightMaps = new CompoundTag();
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
}
}
tag.put("Heightmaps", heightMaps);
CompoundTag structureData = new CompoundTag();
structureData.put("starts", new CompoundTag());
structureData.put("References", new CompoundTag());
tag.put("structures", structureData);
if (!chunk.persistentDataContainer.isEmpty()) {
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
}
return tag;
}
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
}
}

View File

@ -14,6 +14,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.v1_21_R1.headless.RegionStorage;
import com.volmit.iris.util.scheduling.J;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.*;
@ -657,4 +659,9 @@ public class NMSBinding implements INMSBinding {
return keys;
}
@Override
public IRegionStorage createRegionStorage(Engine engine) {
return new RegionStorage(engine);
}
}

View File

@ -0,0 +1,205 @@
package com.volmit.iris.core.nms.v1_21_R1.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.util.data.IrisCustomData;
import com.volmit.iris.util.math.Position2;
import lombok.Data;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBiome;
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockType;
import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_21_R1.util.CraftMagicNumbers;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
@Data
public final class DirectTerrainChunk implements SerializableChunk {
private final ChunkAccess access;
private final int minHeight, maxHeight;
public DirectTerrainChunk(ChunkAccess access) {
this.access = access;
this.minHeight = access.getMinBuildHeight();
this.maxHeight = access.getMaxBuildHeight();
}
@Override
public BiomeBaseInjector getBiomeBaseInjector() {
return null;
}
@NotNull
@Override
public Biome getBiome(int x, int z) {
return getBiome(x, 0, z);
}
@NotNull
@Override
public Biome getBiome(int x, int y, int z) {
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
}
@Override
public void setBiome(int x, int z, Biome bio) {
for (int y = minHeight; y < maxHeight; y += 4) {
setBiome(x, y, z, bio);
}
}
@Override
public void setBiome(int x, int y, int z, Biome bio) {
if (y < minHeight || y > maxHeight) return;
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
}
public void setBlock(int x, int y, int z, Material material) {
this.setBlock(x, y, z, material.createBlockData());
}
public void setBlock(int x, int y, int z, MaterialData material) {
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
}
@Override
public void setBlock(int x, int y, int z, BlockData blockData) {
if (blockData == null) {
Iris.error("NULL BD");
}
if (blockData instanceof IrisCustomData data)
blockData = data.getBase();
if (!(blockData instanceof CraftBlockData craftBlockData))
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
}
public Material getType(int x, int y, int z) {
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
}
public MaterialData getTypeAndData(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
}
public BlockData getBlockData(int x, int y, int z) {
return CraftBlockData.fromData(this.getTypeId(x, y, z));
}
@Override
public ChunkGenerator.ChunkData getRaw() {
return null;
}
@Override
public void setRaw(ChunkGenerator.ChunkData data) {
}
@Override
public void inject(ChunkGenerator.BiomeGrid biome) {
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
return;
if (xMin < 0) {
xMin = 0;
}
if (yMin < this.minHeight) {
yMin = this.minHeight;
}
if (zMin < 0) {
zMin = 0;
}
if (xMax > 16) {
xMax = 16;
}
if (yMax > this.maxHeight) {
yMax = this.maxHeight;
}
if (zMax > 16) {
zMax = 16;
}
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
return;
for (int y = yMin; y < yMax; ++y) {
for (int x = xMin; x < xMax; ++x) {
for (int z = zMin; z < zMax; ++z) {
this.setBlock(x, y, z, type);
}
}
}
}
public BlockState getTypeId(int x, int y, int z) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return Blocks.AIR.defaultBlockState();
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
}
public byte getData(int x, int y, int z) {
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
}
private void setBlock(int x, int y, int z, BlockState type) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return;
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
if (type.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
if (tileEntity == null) {
access.removeBlockEntity(blockPosition);
} else {
access.setBlockEntity(tileEntity);
}
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
access.removeBlockEntity(blockPosition);
}
}
@Override
public Position2 getPos() {
return new Position2(access.getPos().x, access.getPos().z);
}
@Override
public Object serialize() {
return RegionStorage.serialize(access);
}
}

View File

@ -0,0 +1,63 @@
package com.volmit.iris.core.nms.v1_21_R1.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import lombok.NonNull;
import lombok.Synchronized;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.storage.RegionFile;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
class Region implements IRegion {
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
private final RegionFile regionFile;
transient long references;
Region(Path path, Path folder) throws IOException {
this.regionFile = new RegionFile(info, path, folder, true);
}
@Override
@Synchronized
public boolean exists(int x, int z) {
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
if (din == null) return false;
return !"empty".equals(NbtIo.read(din).getString("Status"));
} catch (IOException e) {
return false;
}
}
@Override
@Synchronized
public void write(@NonNull SerializableChunk chunk) throws IOException {
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
NbtIo.write((CompoundTag) chunk.serialize(), dos);
}
}
@Override
public void close() {
--references;
}
public boolean remove() {
if (references > 0) return false;
try {
regionFile.close();
} catch (IOException e) {
Iris.error("Failed to close region file");
e.printStackTrace();
}
return true;
}
}

View File

@ -0,0 +1,287 @@
package com.volmit.iris.core.nms.v1_21_R1.headless;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext;
import com.volmit.iris.util.math.RNG;
import lombok.Getter;
import lombok.NonNull;
import net.minecraft.FileUtil;
import net.minecraft.core.*;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.chunk.status.ChunkType;
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBiome;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.packOffsets;
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
private final KMap<Long, Region> regions = new KMap<>();
private final Path folder;
private final Engine engine;
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
private final RNG biomeRng;
private final @Getter int minBuildHeight;
private final @Getter int height;
private transient boolean closed = false;
public RegionStorage(Engine engine) {
this.engine = engine;
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
this.minBuildHeight = engine.getDimension().getMinHeight();
this.height = engine.getDimension().getMaxHeight() - minBuildHeight;
AtomicInteger failed = new AtomicInteger();
var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) {
if (!biome.isCustom()) continue;
for (var custom : biome.getCustomDerivitives()) {
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
failed.incrementAndGet();
});
}
}
if (failed.get() > 0) {
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
}
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
minecraftBiomes.values().removeAll(customBiomes.values());
}
@Override
public boolean exists(int x, int z) {
try (IRegion region = getRegion(x, z, true)) {
return region != null && region.exists(x, z);
} catch (Exception e) {
return false;
}
}
@Override
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
AtomicReference<IOException> exception = new AtomicReference<>();
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
if (regions.size() >= 256) {
regions.values().removeIf(Region::remove);
}
try {
FileUtil.createDirectoriesSafe(this.folder);
Path path = folder.resolve("r." + x + "." + z + ".mca");
if (existingOnly && !Files.exists(path)) {
return null;
} else {
return new Region(path, this.folder);
}
} catch (IOException e) {
exception.set(e);
return null;
}
});
if (region == null) {
if (exception.get() != null)
throw exception.get();
return null;
}
region.references++;
return region;
}
@NotNull
@Override
public SerializableChunk createChunk(int x, int z) {
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().registryOrThrow(Registries.BIOME), null));
}
@Override
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
if (!(chunk instanceof DirectTerrainChunk tc))
return;
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
}
@Override
public synchronized void close() {
if (closed) return;
while (!regions.isEmpty()) {
regions.values().removeIf(Region::remove);
}
closed = true;
customBiomes.clear();
minecraftBiomes.clear();
}
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
int m = y - engine.getMinHeight();
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
if (ib.isCustom()) {
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
} else {
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
}
}
private static RegistryAccess registryAccess() {
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
}
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
return registryAccess().registryOrThrow(Registries.BIOME).getHolder(ResourceLocation.fromNamespaceAndPath(namespace, path));
}
static CompoundTag serialize(ChunkAccess chunk) {
ChunkPos chunkPos = chunk.getPos();
CompoundTag tag = NbtUtils.addCurrentDataVersion(new CompoundTag());
tag.putInt("xPos", chunkPos.x);
tag.putInt("yPos", chunk.getMinSection());
tag.putInt("zPos", chunkPos.z);
tag.putLong("LastUpdate", 0);
tag.putLong("InhabitedTime", chunk.getInhabitedTime());
tag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getPersistedStatus()).toString());
BlendingData blendingdata = chunk.getBlendingData();
if (blendingdata != null) {
DataResult<Tag> dataresult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("blending_data", nbt));
}
BelowZeroRetrogen retrogen = chunk.getBelowZeroRetrogen();
if (retrogen != null) {
DataResult<Tag> dataresult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, retrogen);
dataresult.resultOrPartial(LogUtils.getLogger()::error).ifPresent((nbt) -> tag.put("below_zero_retrogen", nbt));
}
UpgradeData upgradeData = chunk.getUpgradeData();
if (!upgradeData.isEmpty()) {
tag.put("UpgradeData", upgradeData.write());
}
LevelChunkSection[] sections = chunk.getSections();
ListTag sectionsTag = new ListTag();
Registry<Biome> biomeRegistry = registryAccess().registryOrThrow(Registries.BIOME);
Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
boolean flag = chunk.isLightCorrect();
int minLightSection = chunk.getMinSection() - 1;
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
for(int y = minLightSection; y < maxLightSection; ++y) {
int j = chunk.getSectionIndexFromSectionY(y);
if (j < 0 || j >= sections.length)
continue;
CompoundTag sectionTag = new CompoundTag();
LevelChunkSection section = sections[j];
sectionTag.put("block_states", BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow());
sectionTag.put("biomes", codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow());
if (!sectionTag.isEmpty()) {
sectionTag.putByte("Y", (byte) y);
sectionsTag.add(sectionTag);
}
}
tag.put("sections", sectionsTag);
if (flag) {
tag.putBoolean("isLightOn", true);
}
ListTag blockEntities = new ListTag();
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
CompoundTag entityNbt = chunk.getBlockEntityNbtForSaving(blockPos, registryAccess());
if (entityNbt != null) {
blockEntities.add(entityNbt);
}
}
tag.put("block_entities", blockEntities);
if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) {
ProtoChunk protochunk = (ProtoChunk)chunk;
ListTag entities = new ListTag();
entities.addAll(protochunk.getEntities());
tag.put("entities", entities);
CompoundTag carvingMasks = new CompoundTag();
for(GenerationStep.Carving carving : GenerationStep.Carving.values()) {
CarvingMask mask = protochunk.getCarvingMask(carving);
if (mask != null) {
carvingMasks.putLongArray(carving.toString(), mask.toArray());
}
}
tag.put("CarvingMasks", carvingMasks);
}
saveTicks(tag, chunk.getTicksForSerialization());
tag.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
CompoundTag heightMaps = new CompoundTag();
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) {
heightMaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
}
}
tag.put("Heightmaps", heightMaps);
CompoundTag structureData = new CompoundTag();
structureData.put("starts", new CompoundTag());
structureData.put("References", new CompoundTag());
tag.put("structures", structureData);
if (!chunk.persistentDataContainer.isEmpty()) {
tag.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
}
return tag;
}
private static void saveTicks(CompoundTag tag, ChunkAccess.TicksToSave ticks) {
tag.put("block_ticks", ticks.blocks().save(0, (block) -> BuiltInRegistries.BLOCK.getKey(block).toString()));
tag.put("fluid_ticks", ticks.fluids().save(0, (fluid) -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
}
}

View File

@ -9,6 +9,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.v1_21_R2.headless.RegionStorage;
import com.volmit.iris.util.scheduling.J;
import net.minecraft.core.*;
import net.minecraft.core.Registry;
@ -644,4 +646,9 @@ public class NMSBinding implements INMSBinding {
return keys;
}
@Override
public IRegionStorage createRegionStorage(Engine engine) {
return new RegionStorage(engine);
}
}

View File

@ -0,0 +1,205 @@
package com.volmit.iris.core.nms.v1_21_R2.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.util.data.IrisCustomData;
import com.volmit.iris.util.math.Position2;
import lombok.Data;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_21_R2.block.CraftBiome;
import org.bukkit.craftbukkit.v1_21_R2.block.CraftBlockType;
import org.bukkit.craftbukkit.v1_21_R2.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_21_R2.util.CraftMagicNumbers;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
@Data
public final class DirectTerrainChunk implements SerializableChunk {
private final ChunkAccess access;
private final int minHeight, maxHeight;
public DirectTerrainChunk(ChunkAccess access) {
this.access = access;
this.minHeight = access.getMinY();
this.maxHeight = access.getMaxY();
}
@Override
public BiomeBaseInjector getBiomeBaseInjector() {
return null;
}
@NotNull
@Override
public Biome getBiome(int x, int z) {
return getBiome(x, 0, z);
}
@NotNull
@Override
public Biome getBiome(int x, int y, int z) {
if (y < minHeight || y > maxHeight) return Biome.PLAINS;
return CraftBiome.minecraftHolderToBukkit(access.getNoiseBiome(x >> 2, y >> 2, z >> 2));
}
@Override
public void setBiome(int x, int z, Biome bio) {
for (int y = minHeight; y < maxHeight; y += 4) {
setBiome(x, y, z, bio);
}
}
@Override
public void setBiome(int x, int y, int z, Biome bio) {
if (y < minHeight || y > maxHeight) return;
access.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
}
public void setBlock(int x, int y, int z, Material material) {
this.setBlock(x, y, z, material.createBlockData());
}
public void setBlock(int x, int y, int z, MaterialData material) {
this.setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
}
@Override
public void setBlock(int x, int y, int z, BlockData blockData) {
if (blockData == null) {
Iris.error("NULL BD");
}
if (blockData instanceof IrisCustomData data)
blockData = data.getBase();
if (!(blockData instanceof CraftBlockData craftBlockData))
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
access.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData) blockData).getState());
}
public Material getType(int x, int y, int z) {
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
}
public MaterialData getTypeAndData(int x, int y, int z) {
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
}
public BlockData getBlockData(int x, int y, int z) {
return CraftBlockData.fromData(this.getTypeId(x, y, z));
}
@Override
public ChunkGenerator.ChunkData getRaw() {
return null;
}
@Override
public void setRaw(ChunkGenerator.ChunkData data) {
}
@Override
public void inject(ChunkGenerator.BiomeGrid biome) {
}
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
if (xMin > 15 || yMin >= this.maxHeight || zMin > 15)
return;
if (xMin < 0) {
xMin = 0;
}
if (yMin < this.minHeight) {
yMin = this.minHeight;
}
if (zMin < 0) {
zMin = 0;
}
if (xMax > 16) {
xMax = 16;
}
if (yMax > this.maxHeight) {
yMax = this.maxHeight;
}
if (zMax > 16) {
zMax = 16;
}
if (xMin >= xMax || yMin >= yMax || zMin >= zMax)
return;
for (int y = yMin; y < yMax; ++y) {
for (int x = xMin; x < xMax; ++x) {
for (int z = zMin; z < zMax; ++z) {
this.setBlock(x, y, z, type);
}
}
}
}
public BlockState getTypeId(int x, int y, int z) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return Blocks.AIR.defaultBlockState();
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
}
public byte getData(int x, int y, int z) {
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
}
private void setBlock(int x, int y, int z, BlockState type) {
if (x != (x & 15) || y < this.minHeight || y >= this.maxHeight || z != (z & 15))
return;
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
if (type.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) type.getBlock()).newBlockEntity(blockPosition, type);
if (tileEntity == null) {
access.removeBlockEntity(blockPosition);
} else {
access.setBlockEntity(tileEntity);
}
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
access.removeBlockEntity(blockPosition);
}
}
@Override
public Position2 getPos() {
return new Position2(access.getPos().x, access.getPos().z);
}
@Override
public Object serialize() {
return RegionStorage.serialize(access);
}
}

View File

@ -0,0 +1,63 @@
package com.volmit.iris.core.nms.v1_21_R2.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import lombok.NonNull;
import lombok.Synchronized;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.storage.RegionFile;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Path;
class Region implements IRegion {
private static final RegionStorageInfo info = new RegionStorageInfo("headless", Level.OVERWORLD, "headless");
private final RegionFile regionFile;
transient long references;
Region(Path path, Path folder) throws IOException {
this.regionFile = new RegionFile(info, path, folder, true);
}
@Override
@Synchronized
public boolean exists(int x, int z) {
try (DataInputStream din = regionFile.getChunkDataInputStream(new ChunkPos(x, z))) {
if (din == null) return false;
return !"empty".equals(NbtIo.read(din).getString("Status"));
} catch (IOException e) {
return false;
}
}
@Override
@Synchronized
public void write(@NonNull SerializableChunk chunk) throws IOException {
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos().convert(ChunkPos::new))) {
NbtIo.write((CompoundTag) chunk.serialize(), dos);
}
}
@Override
public void close() {
--references;
}
public boolean remove() {
if (references > 0) return false;
try {
regionFile.close();
} catch (IOException e) {
Iris.error("Failed to close region file");
e.printStackTrace();
}
return true;
}
}

View File

@ -0,0 +1,225 @@
package com.volmit.iris.core.nms.v1_21_R2.headless;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.headless.IRegion;
import com.volmit.iris.core.nms.headless.IRegionStorage;
import com.volmit.iris.core.nms.headless.SerializableChunk;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.data.cache.Cache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext;
import com.volmit.iris.util.math.RNG;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import it.unimi.dsi.fastutil.shorts.ShortList;
import lombok.Getter;
import lombok.NonNull;
import net.minecraft.FileUtil;
import net.minecraft.Optionull;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_21_R2.CraftServer;
import org.bukkit.craftbukkit.v1_21_R2.block.CraftBiome;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
public class RegionStorage implements IRegionStorage, LevelHeightAccessor {
private static final AtomicCache<RegistryAccess> CACHE = new AtomicCache<>();
private final KMap<Long, Region> regions = new KMap<>();
private final Path folder;
private final Engine engine;
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<org.bukkit.block.Biome, Holder<Biome>> minecraftBiomes;
private final RNG biomeRng;
private final @Getter int minY;
private final @Getter int height;
private transient boolean closed = false;
public RegionStorage(Engine engine) {
this.engine = engine;
this.folder = new File(engine.getWorld().worldFolder(), "region").toPath();
this.biomeRng = new RNG(engine.getSeedManager().getBiome());
this.minY = engine.getDimension().getMinHeight();
this.height = engine.getDimension().getMaxHeight() - minY;
AtomicInteger failed = new AtomicInteger();
var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) {
if (!biome.isCustom()) continue;
for (var custom : biome.getCustomDerivitives()) {
biomeHolder(dimKey, custom.getId()).ifPresentOrElse(holder -> customBiomes.put(custom.getId(), holder), () -> {
Iris.error("Failed to load custom biome " + dimKey + " " + custom.getId());
failed.incrementAndGet();
});
}
}
if (failed.get() > 0) {
throw new IllegalStateException("Failed to load " + failed.get() + " custom biomes");
}
minecraftBiomes = new KMap<>(org.bukkit.Registry.BIOME.stream()
.collect(Collectors.toMap(Function.identity(), CraftBiome::bukkitToMinecraftHolder)));
minecraftBiomes.values().removeAll(customBiomes.values());
}
@Override
public boolean exists(int x, int z) {
try (IRegion region = getRegion(x, z, true)) {
return region != null && region.exists(x, z);
} catch (Exception e) {
return false;
}
}
@Override
public IRegion getRegion(int x, int z, boolean existingOnly) throws IOException {
AtomicReference<IOException> exception = new AtomicReference<>();
Region region = regions.computeIfAbsent(Cache.key(x, z), k -> {
if (regions.size() >= 256) {
regions.values().removeIf(Region::remove);
}
try {
FileUtil.createDirectoriesSafe(this.folder);
Path path = folder.resolve("r." + x + "." + z + ".mca");
if (existingOnly && !Files.exists(path)) {
return null;
} else {
return new Region(path, this.folder);
}
} catch (IOException e) {
exception.set(e);
return null;
}
});
if (region == null) {
if (exception.get() != null)
throw exception.get();
return null;
}
region.references++;
return region;
}
@NotNull
@Override
public SerializableChunk createChunk(int x, int z) {
return new DirectTerrainChunk(new ProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, this, registryAccess().lookupOrThrow(Registries.BIOME), null));
}
@Override
public void fillBiomes(@NonNull SerializableChunk chunk, @Nullable ChunkContext ctx) {
if (!(chunk instanceof DirectTerrainChunk tc))
return;
tc.getAccess().fillBiomesFromNoise((qX, qY, qZ, sampler) -> getNoiseBiome(engine, ctx, qX << 2, qY << 2, qZ << 2), null);
}
@Override
public synchronized void close() {
if (closed) return;
while (!regions.isEmpty()) {
regions.values().removeIf(Region::remove);
}
closed = true;
customBiomes.clear();
minecraftBiomes.clear();
}
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int x, int y, int z) {
int m = y - engine.getMinHeight();
IrisBiome ib = ctx == null ? engine.getSurfaceBiome(x, z) : ctx.getBiome().get(x & 15, z & 15);
if (ib.isCustom()) {
return customBiomes.get(ib.getCustomBiome(biomeRng, x, m, z).getId());
} else {
return minecraftBiomes.get(ib.getSkyBiome(biomeRng, x, m, z));
}
}
private static RegistryAccess registryAccess() {
return CACHE.aquire(() -> ((CraftServer) Bukkit.getServer()).getServer().registryAccess());
}
private static Optional<Holder.Reference<Biome>> biomeHolder(String namespace, String path) {
return registryAccess().lookupOrThrow(Registries.BIOME).get(ResourceLocation.fromNamespaceAndPath(namespace, path));
}
static CompoundTag serialize(ChunkAccess chunk) {
RegistryAccess access = registryAccess();
List<SerializableChunkData.SectionData> list = new ArrayList<>();
LevelChunkSection[] sections = chunk.getSections();
int minLightSection = chunk.getMinSectionY() - 1;
int maxLightSection = minLightSection + chunk.getSectionsCount() + 2;
for(int y = minLightSection; y < maxLightSection; ++y) {
int index = chunk.getSectionIndexFromSectionY(y);
if (index < 0 || index >= sections.length) continue;
LevelChunkSection section = sections[index].copy();
list.add(new SerializableChunkData.SectionData(y, section, null, null));
}
List<CompoundTag> blockEntities = new ArrayList<>(chunk.getBlockEntitiesPos().size());
for(BlockPos blockPos : chunk.getBlockEntitiesPos()) {
CompoundTag nbt = chunk.getBlockEntityNbtForSaving(blockPos, access);
if (nbt != null) {
blockEntities.add(nbt);
}
}
Map<Heightmap.Types, long[]> heightMap = new EnumMap<>(Heightmap.Types.class);
for(Map.Entry<Heightmap.Types, Heightmap> entry : chunk.getHeightmaps()) {
if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) {
heightMap.put(entry.getKey(), entry.getValue().getRawData().clone());
}
}
ChunkAccess.PackedTicks packedTicks = chunk.getTicksForSerialization(0);
ShortList[] postProcessing = Arrays.stream(chunk.getPostProcessing()).map((shortlist) -> shortlist != null ? new ShortArrayList(shortlist) : null).toArray(ShortList[]::new);
CompoundTag structureData = new CompoundTag();
structureData.put("starts", new CompoundTag());
structureData.put("References", new CompoundTag());
CompoundTag persistentDataContainer = null;
if (!chunk.persistentDataContainer.isEmpty()) {
persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
}
return new SerializableChunkData(access.lookupOrThrow(Registries.BIOME), chunk.getPos(),
chunk.getMinSectionY(), 0, chunk.getInhabitedTime(), chunk.getPersistedStatus(),
Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(),
chunk.getUpgradeData().copy(), null, heightMap, packedTicks, postProcessing,
chunk.isLightCorrect(), list, new ArrayList<>(), blockEntities, structureData, persistentDataContainer)
.write();
}
}