mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-01 23:47:21 +00:00
implement headless on all supported versions
This commit is contained in:
parent
c2dfbac641
commit
013bc365a9
@ -122,9 +122,7 @@ public interface INMSBinding {
|
||||
return 441;
|
||||
}
|
||||
|
||||
default IRegionStorage createRegionStorage(Engine engine) {
|
||||
return null;
|
||||
}
|
||||
IRegionStorage createRegionStorage(Engine engine);
|
||||
|
||||
KList<String> getStructureKeys();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user