diff --git a/README.md b/README.md index 65fd799b..4397ac0a 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ * [Where to go for questions and discussions](#where-to-go-for-questions-and-discussions) * [Where to go to make donations](#where-to-go-to-make-donations) # How to build -Dynmap 3.x+ uses Gradle for building support for all platforms, with all resulting artifacts produced in the /targets directory. Due to Minecraft 1.17.x+ requirements, the developer's -default JDK must be a JDK 16 (or later) versions - older versions will still be compiled -to run on the default JDK for those platforms (JDK 8), as will all common libraries. +Dynmap 3.x+ uses Gradle for building support for all platforms, with all resulting artifacts produced in the /targets directory. Due to Minecraft 1.18.x+ requirements, the developer's +default JDK must be a JDK 17 (or later) versions - older versions will still be compiled +to run on the default JDK for those platforms (JDK 8, or JDK 16 for 1.17.x, and common libraries are built JDK 8. To build, run: diff --git a/bukkit-helper-118/.gitignore b/bukkit-helper-118/.gitignore new file mode 100644 index 00000000..84c048a7 --- /dev/null +++ b/bukkit-helper-118/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/bukkit-helper-118/bin/.gitignore b/bukkit-helper-118/bin/.gitignore new file mode 100644 index 00000000..ddf9c656 --- /dev/null +++ b/bukkit-helper-118/bin/.gitignore @@ -0,0 +1 @@ +/main/ diff --git a/bukkit-helper-118/build.gradle b/bukkit-helper-118/build.gradle new file mode 100644 index 00000000..aabf5a1f --- /dev/null +++ b/bukkit-helper-118/build.gradle @@ -0,0 +1,32 @@ + +plugins { + id "io.papermc.paperweight.userdev" version "1.2.0" +} + +description = 'bukkit-helper-1.18' + +dependencies { + implementation project(':bukkit-helper') + implementation project(':dynmap-api') + implementation project(path: ':DynmapCore', configuration: 'shadow') + paperDevBundle("1.17.1-R0.1-SNAPSHOT") +} + + +tasks { + // Run reobfJar on build + build { + dependsOn(reobfJar) + } + + compileJava { + options.encoding = Charsets.UTF_8.name() + options.release.set(17) + } + javadoc { + options.encoding = Charsets.UTF_8.name() + } + processResources { + filteringCharset = Charsets.UTF_8.name() + } +} diff --git a/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/BukkitVersionHelperSpigot118.java b/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/BukkitVersionHelperSpigot118.java new file mode 100644 index 00000000..5a926240 --- /dev/null +++ b/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/BukkitVersionHelperSpigot118.java @@ -0,0 +1,429 @@ +package org.dynmap.bukkit.helper.v118; + +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_18_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_18_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.bukkit.helper.BukkitMaterial; +import org.dynmap.bukkit.helper.BukkitVersionHelper; +import org.dynmap.bukkit.helper.BukkitWorld; +import org.dynmap.bukkit.helper.BukkitVersionHelperGeneric.TexturesPayload; +import org.dynmap.bukkit.helper.v118.MapChunkCache118; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +import com.google.common.base.Charsets; +import com.google.common.collect.Iterables; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; + +import net.minecraft.core.IdMapper; +import net.minecraft.core.Registry; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * Helper for isolation of bukkit version specific issues + */ +public class BukkitVersionHelperSpigot118 extends BukkitVersionHelper { + public BukkitVersionHelperSpigot118() { + } + + /** + * Get block short name list + */ + @Override + public String[] getBlockNames() { + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + Block b = bs.getBlock(); + // If this is new block vs last, it's the base block state + if (b != baseb) { + baseb = b; + continue; + } + String bn = b.getDescriptionId(); + if (bn != null) { + names.add(bn); + } + } + return names.toArray(new String[0]); + } + + private static Registry reg = null; + + private static Registry getBiomeReg() { + if (reg == null) { + reg = MinecraftServer.getServer().registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY); + } + return reg; + } + + private Object[] biomelist; + /** + * Get list of defined biomebase objects + */ + @Override + public Object[] getBiomeBaseList() { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + /** Get ID from biomebase */ + @Override + public int getBiomeBaseID(Object bb) { + return getBiomeReg().getId((Biome)bb); + } + + public static IdentityHashMap dataToState; + + /** + * Initialize block states (org.dynmap.blockstate.DynmapBlockState) + */ + @Override + public void initializeBlockStates() { + dataToState = new IdentityHashMap(); + HashMap lastBlockState = new HashMap(); + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + // Loop through block data states + while (iter.hasNext()) { + BlockState bd = iter.next(); + String bname = bd.getBlock().getDescriptionId(); + DynmapBlockState lastbs = lastBlockState.get(bname); // See if we have seen this one + int idx = 0; + if (lastbs != null) { // Yes + idx = lastbs.getStateCount(); // Get number of states so far, since this is next + } + // Build state name + String sb = ""; + String fname = bd.toString(); + int off1 = fname.indexOf('['); + if (off1 >= 0) { + int off2 = fname.indexOf(']'); + sb = fname.substring(off1+1, off2); + } + net.minecraft.world.level.material.Material mat = bd.getMaterial(); + DynmapBlockState bs = new DynmapBlockState(lastbs, idx, bname, sb, mat.toString()); + if ((!bd.getFluidState().isEmpty()) && ((bd.getBlock() instanceof LiquidBlock) == false)) { // Test if fluid type for block is not empty + bs.setWaterlogged(); + } + if (mat == net.minecraft.world.level.material.Material.AIR) { // AIR + bs.setAir(); + } + if (mat == net.minecraft.world.level.material.Material.LEAVES) { // LEAVES + bs.setLeaves(); + } + if (mat == net.minecraft.world.level.material.Material.WOOD) { // WOOD + bs.setLog(); + } + if (mat.isSolid()) { + bs.setSolid(); + } + dataToState.put(bd, bs); + lastBlockState.put(bname, (lastbs == null) ? bs : lastbs); + Log.verboseinfo("blk=" + bname + ", idx=" + idx + ", state=" + sb + ", waterlogged=" + bs.isWaterlogged()); + } + } + /** + * Create chunk cache for given chunks of given world + * @param dw - world + * @param chunks - chunk list + * @return cache + */ + @Override + public MapChunkCache getChunkCache(BukkitWorld dw, List chunks) { + MapChunkCache118 c = new MapChunkCache118(); + c.setChunks(dw, chunks); + return c; + } + + /** + * Get biome base water multiplier + */ + @Override + public int getBiomeBaseWaterMult(Object bb) { + Biome biome = (Biome) bb; + return biome.getWaterColor(); // waterColor + } + + /** Get temperature from biomebase */ + @Override + public float getBiomeBaseTemperature(Object bb) { + return ((Biome)bb).getBaseTemperature(); + } + + /** Get humidity from biomebase */ + @Override + public float getBiomeBaseHumidity(Object bb) { + return ((Biome)bb).getDownfall(); + } + + @Override + public Polygon getWorldBorder(World world) { + Polygon p = null; + WorldBorder wb = world.getWorldBorder(); + if (wb != null) { + Location c = wb.getCenter(); + double size = wb.getSize(); + if ((size > 1) && (size < 1E7)) { + size = size / 2; + p = new Polygon(); + p.addVertex(c.getX()-size, c.getZ()-size); + p.addVertex(c.getX()+size, c.getZ()-size); + p.addVertex(c.getX()+size, c.getZ()+size); + p.addVertex(c.getX()-size, c.getZ()+size); + } + } + return p; + } + // Send title/subtitle to user + public void sendTitleText(Player p, String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTIcks) { + if (p != null) { + p.sendTitle(title, subtitle, fadeInTicks, stayTicks, fadeOutTIcks); + } + } + + /** + * Get material map by block ID + */ + @Override + public BukkitMaterial[] getMaterialList() { + return new BukkitMaterial[4096]; // Not used + } + + @Override + public void unloadChunkNoSave(World w, org.bukkit.Chunk c, int cx, int cz) { + Log.severe("unloadChunkNoSave not implemented"); + } + + private String[] biomenames; + @Override + public String[] getBiomeNames() { + if (biomenames == null) { + biomenames = new String[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = getBiomeReg().getId(b); + if (bidx >= biomenames.length) { + biomenames = Arrays.copyOf(biomenames, bidx + biomenames.length); + } + biomenames[bidx] = b.toString(); + } + } + return biomenames; + } + + @Override + public String getStateStringByCombinedId(int blkid, int meta) { + Log.severe("getStateStringByCombinedId not implemented"); + return null; + } + @Override + /** Get ID string from biomebase */ + public String getBiomeBaseIDString(Object bb) { + String s = ((Biome)bb).toString(); + if (s != null) { + String[] ss = s.split("\\."); + return ss[ss.length-1]; + } + return null; + } + + @Override + public Object getUnloadQueue(World world) { + System.out.println("getUnloadQueue not implemented yet"); + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isInUnloadQueue(Object unloadqueue, int x, int z) { + System.out.println("isInUnloadQueue not implemented yet"); + // TODO Auto-generated method stub + return false; + } + + @Override + public Object[] getBiomeBaseFromSnapshot(ChunkSnapshot css) { + System.out.println("getBiomeBaseFromSnapshot not implemented yet"); + // TODO Auto-generated method stub + return new Object[256]; + } + + @Override + public long getInhabitedTicks(Chunk c) { + return ((CraftChunk)c).getHandle().getInhabitedTime(); + } + + @Override + public Map getTileEntitiesForChunk(Chunk c) { + return ((CraftChunk)c).getHandle().blockEntities; + } + + @Override + public int getTileEntityX(Object te) { + BlockEntity tileent = (BlockEntity) te; + return tileent.getBlockPos().getX(); + } + + @Override + public int getTileEntityY(Object te) { + BlockEntity tileent = (BlockEntity) te; + return tileent.getBlockPos().getY(); + } + + @Override + public int getTileEntityZ(Object te) { + BlockEntity tileent = (BlockEntity) te; + return tileent.getBlockPos().getZ(); + } + + @Override + public Object readTileEntityNBT(Object te) { + BlockEntity tileent = (BlockEntity) te; + CompoundTag nbt = tileent.saveWithId(); + return nbt; + } + + @Override + public Object getFieldValue(Object nbt, String field) { + CompoundTag rec = (CompoundTag) nbt; + Tag val = rec.get(field); + if(val == null) return null; + if(val instanceof ByteTag) { + return ((ByteTag)val).getAsByte(); + } + else if(val instanceof ShortTag) { + return ((ShortTag)val).getAsShort(); + } + else if(val instanceof IntTag) { + return ((IntTag)val).getAsInt(); + } + else if(val instanceof LongTag) { + return ((LongTag)val).getAsLong(); + } + else if(val instanceof FloatTag) { + return ((FloatTag)val).getAsFloat(); + } + else if(val instanceof DoubleTag) { + return ((DoubleTag)val).getAsDouble(); + } + else if(val instanceof ByteArrayTag) { + return ((ByteArrayTag)val).getAsByteArray(); + } + else if(val instanceof StringTag) { + return ((StringTag)val).getAsString(); + } + else if(val instanceof IntArrayTag) { + return ((IntArrayTag)val).getAsIntArray(); + } + return null; + } + + @Override + public Player[] getOnlinePlayers() { + Collection p = Bukkit.getServer().getOnlinePlayers(); + return p.toArray(new Player[0]); + } + + @Override + public double getHealth(Player p) { + return p.getHealth(); + } + + private static final Gson gson = new GsonBuilder().create(); + + /** + * Get skin URL for player + * @param player + */ + @Override + public String getSkinURL(Player player) { + String url = null; + CraftPlayer cp = (CraftPlayer)player; + GameProfile profile = cp.getProfile(); + if (profile != null) { + PropertyMap pm = profile.getProperties(); + if (pm != null) { + Collection txt = pm.get("textures"); + Property textureProperty = Iterables.getFirst(pm.get("textures"), null); + if (textureProperty != null) { + String val = textureProperty.getValue(); + if (val != null) { + TexturesPayload result = null; + try { + String json = new String(Base64Coder.decode(val), Charsets.UTF_8); + result = gson.fromJson(json, TexturesPayload.class); + } catch (JsonParseException e) { + } catch (IllegalArgumentException x) { + Log.warning("Malformed response from skin URL check: " + val); + } + if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { + url = result.textures.get("SKIN").url; + } + } + } + } + } + return url; + } + // Get minY for world + @Override + public int getWorldMinY(World w) { + CraftWorld cw = (CraftWorld) w; + return cw.getMinHeight(); + } +} diff --git a/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/MapChunkCache118.java b/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/MapChunkCache118.java new file mode 100644 index 00000000..73434495 --- /dev/null +++ b/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/MapChunkCache118.java @@ -0,0 +1,514 @@ +package org.dynmap.bukkit.helper.v118; + +import org.bukkit.ChunkSnapshot; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.craftbukkit.v1_18_R1.CraftWorld; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCore; +import org.dynmap.bukkit.helper.AbstractMapChunkCache; +import org.dynmap.bukkit.helper.BukkitVersionHelper; +import org.dynmap.bukkit.helper.SnapshotCache; +import org.dynmap.bukkit.helper.SnapshotCache.SnapshotRec; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.DataBitsPacked; +import org.dynmap.utils.DynIntHashMap; +import org.dynmap.utils.VisibilityLimit; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.BitStorage; +import net.minecraft.util.SimpleBitStorage; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class MapChunkCache118 extends AbstractMapChunkCache { + + public static class NBTSnapshot implements Snapshot { + private static interface Section { + public DynmapBlockState getBlockType(int x, int y, int z); + public int getBlockSkyLight(int x, int y, int z); + public int getBlockEmittedLight(int x, int y, int z); + public boolean isEmpty(); + } + private final int x, z; + private final Section[] section; + private final int sectionOffset; + private final int[] hmap; // Height map + private final int[] biome; + private final Object[] biomebase; + private final long captureFulltime; + private final int sectionCnt; + private final long inhabitedTicks; + + private static final int BLOCKS_PER_SECTION = 16 * 16 * 16; + private static final int COLUMNS_PER_CHUNK = 16 * 16; + private static final int V1_15_BIOME_PER_CHUNK = 4 * 4 * 64; + private static final byte[] emptyData = new byte[BLOCKS_PER_SECTION / 2]; + private static final byte[] fullData = new byte[BLOCKS_PER_SECTION / 2]; + + static + { + Arrays.fill(fullData, (byte)0xFF); + } + + private static byte[] dataCopy(byte[] v) { + if (Arrays.equals(v, emptyData)) + return emptyData; + else if (Arrays.equals(v, fullData)) + return fullData; + else + return v.clone(); + } + + private static class EmptySection implements Section { + @Override + public DynmapBlockState getBlockType(int x, int y, int z) { + return DynmapBlockState.AIR; + } + @Override + public int getBlockSkyLight(int x, int y, int z) { + return 15; + } + @Override + public int getBlockEmittedLight(int x, int y, int z) { + return 0; + } + @Override + public boolean isEmpty() { + return true; + } + } + + private static final EmptySection empty_section = new EmptySection(); + + private static class StdSection implements Section { + DynmapBlockState[] states; + byte[] skylight; + byte[] emitlight; + + public StdSection() { + states = new DynmapBlockState[BLOCKS_PER_SECTION]; + Arrays.fill(states, DynmapBlockState.AIR); + skylight = emptyData; + emitlight = emptyData; + } + @Override + public DynmapBlockState getBlockType(int x, int y, int z) { + return states[((y & 0xF) << 8) | (z << 4) | x]; + } + @Override + public int getBlockSkyLight(int x, int y, int z) { + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); + return (skylight[off] >> (4 * (x & 1))) & 0xF; + } + @Override + public int getBlockEmittedLight(int x, int y, int z) + { + int off = ((y & 0xF) << 7) | (z << 3) | (x >> 1); + return (emitlight[off] >> (4 * (x & 1))) & 0xF; + } + @Override + public boolean isEmpty() { + return false; + } + } + /** + * Construct empty chunk snapshot + * + * @param x + * @param z + */ + public NBTSnapshot(int worldheight, int x, int z, long captime, long inhabitedTime) + { + this.x = x; + this.z = z; + this.captureFulltime = captime; + this.biome = new int[COLUMNS_PER_CHUNK]; + this.biomebase = new Object[COLUMNS_PER_CHUNK]; + this.sectionCnt = worldheight / 16; + /* Allocate arrays indexed by section */ + this.section = new Section[this.sectionCnt+1]; + this.sectionOffset = 0; + /* Fill with empty data */ + for (int i = 0; i <= this.sectionCnt; i++) { + this.section[i] = empty_section; + } + + /* Create empty height map */ + this.hmap = new int[16 * 16]; + + this.inhabitedTicks = inhabitedTime; + } + + public NBTSnapshot(CompoundTag nbt, int worldheight) { + this.x = nbt.getInt("xPos"); + this.z = nbt.getInt("zPos"); + this.captureFulltime = 0; + this.hmap = nbt.getIntArray("HeightMap"); + this.sectionCnt = worldheight / 16; + if (nbt.contains("InhabitedTime")) { + this.inhabitedTicks = nbt.getLong("InhabitedTime"); + } + else { + this.inhabitedTicks = 0; + } + /* Allocate arrays indexed by section */ + LinkedList
sections = new LinkedList
(); + int sectoff = 0; // Default to zero + int sectcnt = 0; + /* Fill with empty data */ + for (int i = 0; i <= this.sectionCnt; i++) { + sections.add(empty_section); + sectcnt++; + } + /* Get sections */ + ListTag sect = nbt.getList("Sections", 10); + for (int i = 0; i < sect.size(); i++) { + CompoundTag sec = sect.getCompound(i); + int secnum = sec.getByte("Y"); + // Beyond end - extend up + while (secnum >= (sectcnt - sectoff)) { + sections.addLast(empty_section); // Pad with empty + sectcnt++; + } + // Negative - see if we need to extend sectionOffset + while ((secnum + sectoff) < 0) { + sections.addFirst(empty_section); // Pad with empty + sectoff++; + sectcnt++; + } + //System.out.println("section(" + secnum + ")=" + sec.asString()); + // Create normal section to initialize + StdSection cursect = new StdSection(); + sections.set(secnum + sectoff, cursect); + DynmapBlockState[] states = cursect.states; + DynmapBlockState[] palette = null; + // If we've got palette and block states list, process non-empty section + if (sec.contains("Palette", 9) && sec.contains("BlockStates", 12)) { + ListTag plist = sec.getList("Palette", 10); + long[] statelist = sec.getLongArray("BlockStates"); + palette = new DynmapBlockState[plist.size()]; + for (int pi = 0; pi < plist.size(); pi++) { + CompoundTag tc = plist.getCompound(pi); + String pname = tc.getString("Name"); + if (tc.contains("Properties")) { + StringBuilder statestr = new StringBuilder(); + CompoundTag prop = tc.getCompound("Properties"); + for (String pid : prop.getAllKeys()) { + if (statestr.length() > 0) statestr.append(','); + statestr.append(pid).append('=').append(prop.get(pid).getAsString()); + } + palette[pi] = DynmapBlockState.getStateByNameAndState(pname, statestr.toString()); + } + if (palette[pi] == null) { + palette[pi] = DynmapBlockState.getBaseStateByName(pname); + } + if (palette[pi] == null) { + palette[pi] = DynmapBlockState.AIR; + } + } + int recsperblock = (4096 + statelist.length - 1) / statelist.length; + int bitsperblock = 64 / recsperblock; + BitStorage db = null; + DataBitsPacked dbp = null; + try { + db = new SimpleBitStorage(bitsperblock, 4096, statelist); + } catch (Exception x) { // Handle legacy encoded + bitsperblock = (statelist.length * 64) / 4096; + dbp = new DataBitsPacked(bitsperblock, 4096, statelist); + } + if (bitsperblock > 8) { // Not palette + for (int j = 0; j < 4096; j++) { + int v = (dbp != null) ? dbp.getAt(j) : db.get(j); + states[j] = DynmapBlockState.getStateByGlobalIndex(v); + } + } + else { + for (int j = 0; j < 4096; j++) { + int v = (dbp != null) ? dbp.getAt(j) : db.get(j); + states[j] = (v < palette.length) ? palette[v] : DynmapBlockState.AIR; + } + } + } + if (sec.contains("BlockLight")) { + cursect.emitlight = dataCopy(sec.getByteArray("BlockLight")); + } + if (sec.contains("SkyLight")) { + cursect.skylight = dataCopy(sec.getByteArray("SkyLight")); + } + } + /* Get biome data */ + this.biome = new int[COLUMNS_PER_CHUNK]; + this.biomebase = new Object[COLUMNS_PER_CHUNK]; + Object[] bbl = BukkitVersionHelper.helper.getBiomeBaseList(); + if (nbt.contains("Biomes")) { + int[] bb = nbt.getIntArray("Biomes"); + if (bb != null) { + // If v1.15+ format + if (bb.length > COLUMNS_PER_CHUNK) { + // For now, just pad the grid with the first 16 + for (int i = 0; i < COLUMNS_PER_CHUNK; i++) { + int off = ((i >> 4) & 0xC) + ((i >> 2) & 0x3); + int bv = bb[off + 64]; // Offset to y=64 + if (bv < 0) bv = 0; + this.biome[i] = bv; + this.biomebase[i] = bbl[bv]; + } + } + else { // Else, older chunks + for (int i = 0; i < bb.length; i++) { + int bv = bb[i]; + if (bv < 0) bv = 0; + this.biome[i] = bv; + this.biomebase[i] = bbl[bv]; + } + } + } + } + // Finalize sections array + this.section = sections.toArray(new Section[sections.size()]); + this.sectionOffset = sectoff; + } + + public int getX() + { + return x; + } + + public int getZ() + { + return z; + } + + public DynmapBlockState getBlockType(int x, int y, int z) + { + int idx = (y >> 4) + sectionOffset; + if ((idx < 0) || (idx >= section.length)) return DynmapBlockState.AIR; + return section[idx].getBlockType(x, y, z); + } + + public int getBlockSkyLight(int x, int y, int z) + { + int idx = (y >> 4) + sectionOffset; + if ((idx < 0) || (idx >= section.length)) return 15; + return section[idx].getBlockSkyLight(x, y, z); + } + + public int getBlockEmittedLight(int x, int y, int z) + { + int idx = (y >> 4) + sectionOffset; + if ((idx < 0) || (idx >= section.length)) return 0; + return section[idx].getBlockEmittedLight(x, y, z); + } + + public int getHighestBlockYAt(int x, int z) + { + return hmap[z << 4 | x]; + } + + public final long getCaptureFullTime() + { + return captureFulltime; + } + + public boolean isSectionEmpty(int sy) + { + int idx = sy + sectionOffset; + if ((idx < 0) || (idx >= section.length)) return true; + return section[idx].isEmpty(); + } + + public long getInhabitedTicks() { + return inhabitedTicks; + } + + @Override + public Biome getBiome(int x, int z) { + return AbstractMapChunkCache.getBiomeByID(biome[z << 4 | x]); + } + + @Override + public Object[] getBiomeBaseFromSnapshot() { + return this.biomebase; + } + } + + private CompoundTag fetchLoadedChunkNBT(World w, int x, int z) { + CraftWorld cw = (CraftWorld) w; + CompoundTag nbt = null; + if (cw.isChunkLoaded(x, z)) { + LevelChunk c = cw.getHandle().getChunk(x, z); + if ((c != null) && c.loaded) { // c.loaded + nbt = ChunkSerializer.write(cw.getHandle(), c); + } + } + if (nbt != null) { + nbt = nbt.getCompound("Level"); + if (nbt != null) { + String stat = nbt.getString("Status"); + ChunkStatus cs = ChunkStatus.byName(stat); + if ((stat == null) || (!cs.isOrAfter(ChunkStatus.LIGHT))) { // ChunkStatus.LIGHT + nbt = null; + } + } + } + return nbt; + } + + private CompoundTag loadChunkNBT(World w, int x, int z) { + CraftWorld cw = (CraftWorld) w; + CompoundTag nbt = null; + ChunkPos cc = new ChunkPos(x, z); + try { + nbt = cw.getHandle().getChunkSource().chunkMap.read(cc); // playerChunkMap + } catch (IOException iox) { + } + if (nbt != null) { + nbt = nbt.getCompound("Level"); + if (nbt != null) { + String stat = nbt.getString("Status"); + if ((stat == null) || (stat.equals("full") == false)) { + nbt = null; + if ((stat == null) || stat.equals("") && DynmapCore.migrateChunks()) { + LevelChunk c = cw.getHandle().getChunk(x, z); + if (c != null) { + nbt = fetchLoadedChunkNBT(w, x, z); + cw.getHandle().unload(c); + } + } + } + } + } + return nbt; + } + + @Override + public Snapshot wrapChunkSnapshot(ChunkSnapshot css) { + // TODO Auto-generated method stub + return null; + } + + // Load chunk snapshots + @Override + public int loadChunks(int max_to_load) { + if(dw.isLoaded() == false) + return 0; + int cnt = 0; + if(iterator == null) + iterator = chunks.listIterator(); + + DynmapCore.setIgnoreChunkLoads(true); + // Load the required chunks. + while((cnt < max_to_load) && iterator.hasNext()) { + long startTime = System.nanoTime(); + DynmapChunk chunk = iterator.next(); + boolean vis = true; + if(visible_limits != null) { + vis = false; + for(VisibilityLimit limit : visible_limits) { + if (limit.doIntersectChunk(chunk.x, chunk.z)) { + vis = true; + break; + } + } + } + if(vis && (hidden_limits != null)) { + for(VisibilityLimit limit : hidden_limits) { + if (limit.doIntersectChunk(chunk.x, chunk.z)) { + vis = false; + break; + } + } + } + /* Check if cached chunk snapshot found */ + Snapshot ss = null; + long inhabited_ticks = 0; + DynIntHashMap tileData = null; + int idx = (chunk.x-x_min) + (chunk.z - z_min)*x_dim; + SnapshotRec ssr = SnapshotCache.sscache.getSnapshot(dw.getName(), chunk.x, chunk.z, blockdata, biome, biomeraw, highesty); + if(ssr != null) { + inhabited_ticks = ssr.inhabitedTicks; + if(!vis) { + if(hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN) + ss = STONE; + else if(hidestyle == HiddenChunkStyle.FILL_OCEAN) + ss = OCEAN; + else + ss = EMPTY; + } + else { + ss = ssr.ss; + } + snaparray[idx] = ss; + snaptile[idx] = ssr.tileData; + inhabitedTicks[idx] = inhabited_ticks; + + endChunkLoad(startTime, ChunkStats.CACHED_SNAPSHOT_HIT); + continue; + } + // Fetch NTB for chunk if loaded + CompoundTag nbt = fetchLoadedChunkNBT(w, chunk.x, chunk.z); + boolean did_load = false; + if (nbt == null) { + // Load NTB for chunk, if it exists + nbt = loadChunkNBT(w, chunk.x, chunk.z); + did_load = true; + } + if (nbt != null) { + NBTSnapshot nss = new NBTSnapshot(nbt, w.getMaxHeight()); + ss = nss; + inhabited_ticks = nss.getInhabitedTicks(); + if(!vis) { + if(hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN) + ss = STONE; + else if(hidestyle == HiddenChunkStyle.FILL_OCEAN) + ss = OCEAN; + else + ss = EMPTY; + } + } + else { + ss = EMPTY; + } + ssr = new SnapshotRec(); + ssr.ss = ss; + ssr.inhabitedTicks = inhabited_ticks; + ssr.tileData = tileData; + SnapshotCache.sscache.putSnapshot(dw.getName(), chunk.x, chunk.z, ssr, blockdata, biome, biomeraw, highesty); + snaparray[idx] = ss; + snaptile[idx] = ssr.tileData; + inhabitedTicks[idx] = inhabited_ticks; + if (nbt == null) + endChunkLoad(startTime, ChunkStats.UNGENERATED_CHUNKS); + else if (did_load) + endChunkLoad(startTime, ChunkStats.UNLOADED_CHUNKS); + else + endChunkLoad(startTime, ChunkStats.LOADED_CHUNKS); + cnt++; + } + DynmapCore.setIgnoreChunkLoads(false); + + if(iterator.hasNext() == false) { /* If we're done */ + isempty = true; + /* Fill missing chunks with empty dummy chunk */ + for(int i = 0; i < snaparray.length; i++) { + if(snaparray[i] == null) + snaparray[i] = EMPTY; + else if(snaparray[i] != EMPTY) + isempty = false; + } + } + + return cnt; + } +} diff --git a/settings.gradle b/settings.gradle index e1f76a26..5e662776 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,12 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { url "https://papermc.io/repo/repository/maven-public/" } + } +} + rootProject.name = 'dynmap-common' + include ':spigot' include ':bukkit-helper-113-2' include ':bukkit-helper-114-1' @@ -8,6 +16,7 @@ include ':bukkit-helper-116-2' include ':bukkit-helper-116-3' include ':bukkit-helper-116-4' include ':bukkit-helper-117' +//NOTYET include ':bukkit-helper-118' include ':bukkit-helper' include ':dynmap-api' include ':DynmapCore' @@ -31,6 +40,7 @@ project(':bukkit-helper-116-2').projectDir = "$rootDir/bukkit-helper-116-2" as F project(':bukkit-helper-116-3').projectDir = "$rootDir/bukkit-helper-116-3" as File project(':bukkit-helper-116-4').projectDir = "$rootDir/bukkit-helper-116-4" as File project(':bukkit-helper-117').projectDir = "$rootDir/bukkit-helper-117" as File +//NOTYET project(':bukkit-helper-118').projectDir = "$rootDir/bukkit-helper-118" as File project(':bukkit-helper').projectDir = "$rootDir/bukkit-helper" as File project(':dynmap-api').projectDir = "$rootDir/dynmap-api" as File project(':DynmapCore').projectDir = "$rootDir/DynmapCore" as File diff --git a/spigot/build.gradle b/spigot/build.gradle index 90dc9882..a9098d81 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -50,6 +50,9 @@ dependencies { implementation(project(':bukkit-helper-117')) { transitive = false } + //NOTYET implementation(project(':bukkit-helper-118')) { + //NOTYET transitive = false + //NOTYET } } processResources { @@ -81,6 +84,7 @@ shadowJar { include(dependency(':bukkit-helper-116-3')) include(dependency(':bukkit-helper-116-4')) include(dependency(':bukkit-helper-117')) + //NOTYET include(dependency(':bukkit-helper-118')) } relocate('org.bstats', 'org.dynmap.bstats') destinationDir = file '../target' diff --git a/spigot/src/main/java/org/dynmap/bukkit/Helper.java b/spigot/src/main/java/org/dynmap/bukkit/Helper.java index 495d3b4b..1d7fefc9 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/Helper.java +++ b/spigot/src/main/java/org/dynmap/bukkit/Helper.java @@ -13,6 +13,7 @@ import org.dynmap.bukkit.helper.v116_2.BukkitVersionHelperSpigot116_2; import org.dynmap.bukkit.helper.v116_3.BukkitVersionHelperSpigot116_3; import org.dynmap.bukkit.helper.v116_4.BukkitVersionHelperSpigot116_4; import org.dynmap.bukkit.helper.v117.BukkitVersionHelperSpigot117; +//NOTYET import org.dynmap.bukkit.helper.v118.BukkitVersionHelperSpigot118; public class Helper { @@ -38,6 +39,9 @@ public class Helper { Log.info("Loading Glowstone support"); BukkitVersionHelper.helper = new BukkitVersionHelperGlowstone(); } + //NOTYET else if (v.contains("(MC: 1.18")) { + //NOTYET BukkitVersionHelper.helper = new BukkitVersionHelperSpigot118(); + //NOTYET } else if (v.contains("(MC: 1.17")) { BukkitVersionHelper.helper = new BukkitVersionHelperSpigot117(); }