diff --git a/bukkit-helper-121/.gitignore b/bukkit-helper-121/.gitignore new file mode 100644 index 000000000..84c048a73 --- /dev/null +++ b/bukkit-helper-121/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/bukkit-helper-121/build.gradle b/bukkit-helper-121/build.gradle new file mode 100644 index 000000000..6d8ecf422 --- /dev/null +++ b/bukkit-helper-121/build.gradle @@ -0,0 +1,17 @@ +eclipse { + project { + name = "Dynmap(Spigot-1.21)" + } +} + +description = 'bukkit-helper-1.21' + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. + +dependencies { + implementation project(':bukkit-helper') + implementation project(':dynmap-api') + implementation project(path: ':DynmapCore', configuration: 'shadow') + compileOnly group: 'org.spigotmc', name: 'spigot-api', version:'1.21-R0.1-SNAPSHOT' + compileOnly group: 'org.spigotmc', name: 'spigot', version:'1.21-R0.1-SNAPSHOT' +} diff --git a/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/AsyncChunkProvider121.java b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/AsyncChunkProvider121.java new file mode 100644 index 000000000..cfe74070d --- /dev/null +++ b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/AsyncChunkProvider121.java @@ -0,0 +1,130 @@ +package org.dynmap.bukkit.helper.v121; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.WorldServer; +import net.minecraft.world.level.chunk.Chunk; +import net.minecraft.world.level.chunk.IChunkAccess; +import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R1.CraftServer; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.dynmap.MapManager; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +/** + * The provider used to work with paper libs + * Because paper libs need java 17 we can't interact with them directly + */ +@SuppressWarnings({"JavaReflectionMemberAccess"}) //java don't know about paper +public class AsyncChunkProvider121 { + private final Method getChunk; + private final Method getAsyncSaveData; + private final Method save; + private final Enum data; + private final Enum priority; + private int currTick = MinecraftServer.currentTick; + private int currChunks = 0; + + AsyncChunkProvider121() { + try { + Method getChunk1 = null; + Method getAsyncSaveData1 = null; + Method save1 = null; + Enum priority1 = null; + Enum data1 = null; + try { + Class threadClass = Class.forName("io.papermc.paper.chunk.system.io.RegionFileIOThread"); + + Class dataclass = Arrays.stream(threadClass.getDeclaredClasses()) + .filter(c -> c.getSimpleName().equals("RegionFileType")) + .findAny() + .orElseThrow(NullPointerException::new); + data1 = Enum.valueOf(cast(dataclass), "CHUNK_DATA"); + + Class priorityClass = Arrays.stream(Class.forName("ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor").getClasses()) + .filter(c -> c.getSimpleName().equals("Priority")) + .findAny() + .orElseThrow(NullPointerException::new); + //Almost lowest priority, but not quite so low as to be considered idle + //COMPLETING->BLOCKING->HIGHEST->HIGHER->HIGH->NORMAL->LOW->LOWER->LOWEST->IDLE + priority1 = Enum.valueOf(cast(priorityClass), "LOWEST"); + + getAsyncSaveData1 = ChunkRegionLoader.class.getMethod("getAsyncSaveData", WorldServer.class, IChunkAccess.class); + save1 = ChunkRegionLoader.class.getMethod("saveChunk", WorldServer.class, IChunkAccess.class, getAsyncSaveData1.getReturnType()); + getChunk1 = threadClass.getMethod("loadDataAsync", WorldServer.class, int.class, int.class, data1.getClass(), BiConsumer.class, boolean.class, priority1.getClass()); + } catch (ClassNotFoundException | NoSuchMethodException e) { + e.printStackTrace(); + } + getAsyncSaveData = Objects.requireNonNull(getAsyncSaveData1); + save = Objects.requireNonNull(save1); + getChunk = Objects.requireNonNull(getChunk1); + data = Objects.requireNonNull(data1); + priority = Objects.requireNonNull(priority1); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private T cast(Object o) { + return (T) o; + } + public CompletableFuture getChunk(WorldServer world, int x, int y) throws InvocationTargetException, IllegalAccessException { + CompletableFuture future = new CompletableFuture<>(); + getChunk.invoke(null, world, x, y, data, (BiConsumer) (nbt, exception) -> future.complete(nbt), true, priority); + return future; + } + + public synchronized Supplier getLoadedChunk(CraftWorld world, int x, int z) { + if (!world.isChunkLoaded(x, z)) return () -> null; + Chunk c = world.getHandle().getChunkIfLoaded(x, z); //already safe async on vanilla + if ((c == null) || !c.q) return () -> null; // c.loaded + if (currTick != MinecraftServer.currentTick) { + currTick = MinecraftServer.currentTick; + currChunks = 0; + } + //prepare data synchronously + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + //Null will mean that we save with spigot methods, which may be risky on async + //Since we're not in main thread, it now refuses new tasks because of shutdown, the risk is lower + if (!Bukkit.isPrimaryThread()) return null; + try { + return getAsyncSaveData.invoke(null, world.getHandle(), c); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }, ((CraftServer) Bukkit.getServer()).getServer()); + //we shouldn't stress main thread + if (++currChunks > MapManager.mapman.getMaxChunkLoadsPerTick()) { + try { + Thread.sleep(25); //hold the lock so other threads also won't stress main thread + } catch (InterruptedException ignored) {} + } + //save data asynchronously + return () -> { + Object o = null; + try { + o = future.get(); + return (NBTTagCompound) save.invoke(null, world.getHandle(), c, o); + } catch (InterruptedException e) { + return null; + } catch (InvocationTargetException e) { + //We tried to use simple spigot methods at shutdown and failed, hopes for reading from disk + if (o == null) return null; + throw new RuntimeException(e); + } catch (ReflectiveOperationException | ExecutionException e) { + throw new RuntimeException(e); + } + }; + } +} diff --git a/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/BukkitVersionHelperSpigot121.java b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/BukkitVersionHelperSpigot121.java new file mode 100644 index 000000000..bff11be80 --- /dev/null +++ b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/BukkitVersionHelperSpigot121.java @@ -0,0 +1,466 @@ +package org.dynmap.bukkit.helper.v121; + +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_21_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_21_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.renderer.DynmapBlockState; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +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.RegistryBlockID; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.core.BlockPosition; +import net.minecraft.core.IRegistry; +import net.minecraft.nbt.NBTTagByteArray; +import net.minecraft.nbt.NBTTagByte; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagDouble; +import net.minecraft.nbt.NBTTagFloat; +import net.minecraft.nbt.NBTTagIntArray; +import net.minecraft.nbt.NBTTagInt; +import net.minecraft.nbt.NBTTagLong; +import net.minecraft.nbt.NBTTagShort; +import net.minecraft.nbt.NBTTagString; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.nbt.NBTBase; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tags.TagsBlock; +import net.minecraft.world.level.BlockAccessAir; +import net.minecraft.world.level.biome.BiomeBase; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.BlockFluids; +import net.minecraft.world.level.block.entity.TileEntity; +import net.minecraft.world.level.block.state.IBlockData; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +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 BukkitVersionHelperSpigot121 extends BukkitVersionHelper { + private final boolean unsafeAsync; + + public BukkitVersionHelperSpigot121() { + boolean unsafeAsync1; + try { + Class.forName("io.papermc.paper.chunk.system.io.RegionFileIOThread"); + unsafeAsync1 = false; + } catch (ClassNotFoundException e) { + unsafeAsync1 = true; + } + this.unsafeAsync = unsafeAsync1; + } + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** + * Get block short name list + */ + @Override + public String[] getBlockNames() { + RegistryBlockID bsids = Block.q; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + while (iter.hasNext()) { + IBlockData bs = iter.next(); + Block b = bs.b(); + // If this is new block vs last, it's the base block state + if (b != baseb) { + baseb = b; + continue; + } + MinecraftKey id = BuiltInRegistries.e.b(b); + String bn = id.toString(); + if (bn != null) { + names.add(bn); + Log.info("block=" + bn); + } + } + return names.toArray(new String[0]); + } + + private static IRegistry reg = null; + + private static IRegistry getBiomeReg() { + if (reg == null) { + reg = MinecraftServer.getServer().bc().d(Registries.aF); + } + return reg; + } + + private Object[] biomelist; + /** + * Get list of defined biomebase objects + */ + @Override + public Object[] getBiomeBaseList() { + if (biomelist == null) { + biomelist = new BiomeBase[256]; + Iterator iter = getBiomeReg().iterator(); + while (iter.hasNext()) { + BiomeBase b = iter.next(); + int bidx = getBiomeReg().a(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().a((BiomeBase)bb); + } + + public static IdentityHashMap dataToState; + + /** + * Initialize block states (org.dynmap.blockstate.DynmapBlockState) + */ + @Override + public void initializeBlockStates() { + dataToState = new IdentityHashMap(); + HashMap lastBlockState = new HashMap(); + RegistryBlockID bsids = Block.q; + Block baseb = null; + Iterator iter = bsids.iterator(); + ArrayList names = new ArrayList(); + + // Loop through block data states + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + IBlockData bd = iter.next(); + Block b = bd.b(); + MinecraftKey id = BuiltInRegistries.e.b(b); + String bname = id.toString(); + 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); + } + int lightAtten = bd.b(BlockAccessAir.a, BlockPosition.c); // getLightBlock + //Log.info("statename=" + bname + "[" + sb + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(lastbs).setStateIndex(idx).setBlockName(bname).setStateName(sb).setAttenuatesLight(lightAtten); + if (bd.w() != null) { bld.setMaterial(bd.w().toString()); } + if (bd.e()) { bld.setSolid(); } + if (bd.i()) { bld.setAir(); } + if (bd.a(TagsBlock.t)) { bld.setLog(); } + if (bd.a(TagsBlock.O)) { bld.setLeaves(); } + if ((!bd.u().c()) && ((bd.b() instanceof BlockFluids) == false)) { // Test if fluid type for block is not empty + bld.setWaterlogged(); + //Log.info("statename=" + bname + "[" + sb + "] = waterlogged"); + } + DynmapBlockState dbs = bld.build(); // Build state + + dataToState.put(bd, dbs); + lastBlockState.put(bname, (lastbs == null) ? dbs : lastbs); + Log.verboseinfo("blk=" + bname + ", idx=" + idx + ", state=" + sb + ", waterlogged=" + dbs.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) { + MapChunkCache121 c = new MapChunkCache121(gencache); + c.setChunks(dw, chunks); + return c; + } + + /** + * Get biome base water multiplier + */ + @Override + public int getBiomeBaseWaterMult(Object bb) { + BiomeBase biome = (BiomeBase) bb; + return biome.i(); // waterColor + } + + /** Get temperature from biomebase */ + @Override + public float getBiomeBaseTemperature(Object bb) { + return ((BiomeBase)bb).g(); + } + + /** Get humidity from biomebase */ + @Override + public float getBiomeBaseHumidity(Object bb) { + String vals = ((BiomeBase)bb).i.toString(); // Sleazy + float humidity = 0.5F; + int idx = vals.indexOf("downfall="); + if (idx >= 0) { + humidity = Float.parseFloat(vals.substring(idx+9, vals.indexOf(']', idx))); + } + return humidity; + } + + @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()) { + BiomeBase b = iter.next(); + int bidx = getBiomeReg().a(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) { + return getBiomeReg().b((BiomeBase)bb).a(); + } + @Override + public String getBiomeBaseResourceLocsation(Object bb) { + return getBiomeReg().b((BiomeBase)bb).toString(); + } + + @Override + public Object getUnloadQueue(World world) { + Log.warning("getUnloadQueue not implemented yet"); + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isInUnloadQueue(Object unloadqueue, int x, int z) { + Log.warning("isInUnloadQueue not implemented yet"); + // TODO Auto-generated method stub + return false; + } + + @Override + public Object[] getBiomeBaseFromSnapshot(ChunkSnapshot css) { + Log.warning("getBiomeBaseFromSnapshot not implemented yet"); + // TODO Auto-generated method stub + return new Object[256]; + } + + @Override + public long getInhabitedTicks(Chunk c) { + return ((CraftChunk)c).getHandle(ChunkStatus.n).u(); + } + + @Override + public Map getTileEntitiesForChunk(Chunk c) { + return ((CraftChunk)c).getHandle(ChunkStatus.n).k; + } + + @Override + public int getTileEntityX(Object te) { + TileEntity tileent = (TileEntity) te; + return tileent.aD_().u(); + } + + @Override + public int getTileEntityY(Object te) { + TileEntity tileent = (TileEntity) te; + return tileent.aD_().v(); + } + + @Override + public int getTileEntityZ(Object te) { + TileEntity tileent = (TileEntity) te; + return tileent.aD_().w(); + } + + @Override + public Object readTileEntityNBT(Object te, org.bukkit.World w) { + TileEntity tileent = (TileEntity) te; + CraftWorld cw = (CraftWorld) w; + //NBTTagCompound nbt = tileent.o(world.registrtAccess()); + NBTTagCompound nbt = tileent.e(cw.getHandle().H_()); + return nbt; + } + + @Override + public Object getFieldValue(Object nbt, String field) { + NBTTagCompound rec = (NBTTagCompound) nbt; + NBTBase val = rec.c(field); + if(val == null) return null; + if(val instanceof NBTTagByte) { + return ((NBTTagByte)val).h(); + } + else if(val instanceof NBTTagShort) { + return ((NBTTagShort)val).g(); + } + else if(val instanceof NBTTagInt) { + return ((NBTTagInt)val).f(); + } + else if(val instanceof NBTTagLong) { + return ((NBTTagLong)val).e(); + } + else if(val instanceof NBTTagFloat) { + return ((NBTTagFloat)val).j(); + } + else if(val instanceof NBTTagDouble) { + return ((NBTTagDouble)val).i(); + } + else if(val instanceof NBTTagByteArray) { + return ((NBTTagByteArray)val).d(); + } + else if(val instanceof NBTTagString) { + return ((NBTTagString)val).s_(); + } + else if(val instanceof NBTTagIntArray) { + return ((NBTTagIntArray)val).f(); + } + 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.value(); + if (val != null) { + TexturesPayload result = null; + try { + String json = new String(Base64.getDecoder().decode(val), StandardCharsets.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(); + } + @Override + public boolean useGenericCache() { + return true; + } + +} diff --git a/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/MapChunkCache121.java b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/MapChunkCache121.java new file mode 100644 index 000000000..1eff19eec --- /dev/null +++ b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/MapChunkCache121.java @@ -0,0 +1,112 @@ +package org.dynmap.bukkit.helper.v121; + +import net.minecraft.world.level.biome.BiomeBase; +import net.minecraft.world.level.biome.BiomeFog; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.dynmap.DynmapChunk; +import org.dynmap.bukkit.helper.BukkitVersionHelper; +import org.dynmap.bukkit.helper.BukkitWorld; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.common.chunk.GenericMapChunkCache; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.level.ChunkCoordIntPair; +import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; +import net.minecraft.world.level.chunk.Chunk; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class MapChunkCache121 extends GenericMapChunkCache { + private static final AsyncChunkProvider121 provider = BukkitVersionHelper.helper.isUnsafeAsync() ? null : new AsyncChunkProvider121(); + private World w; + /** + * Construct empty cache + */ + public MapChunkCache121(GenericChunkCache cc) { + super(cc); + } + + // Load generic chunk from existing and already loaded chunk + @Override + protected Supplier getLoadedChunkAsync(DynmapChunk chunk) { + Supplier supplier = provider.getLoadedChunk((CraftWorld) w, chunk.x, chunk.z); + return () -> { + NBTTagCompound nbt = supplier.get(); + return nbt != null ? parseChunkFromNBT(new NBT.NBTCompound(nbt)) : null; + }; + } + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + CraftWorld cw = (CraftWorld) w; + if (!cw.isChunkLoaded(chunk.x, chunk.z)) return null; + Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); + if (c == null || !c.q) return null; // c.loaded + NBTTagCompound nbt = ChunkRegionLoader.a(cw.getHandle(), c); + return nbt != null ? parseChunkFromNBT(new NBT.NBTCompound(nbt)) : null; + } + + // Load generic chunk from unloaded chunk + @Override + protected Supplier loadChunkAsync(DynmapChunk chunk){ + try { + CompletableFuture nbt = provider.getChunk(((CraftWorld) w).getHandle(), chunk.x, chunk.z); + return () -> { + NBTTagCompound compound; + try { + compound = nbt.get(); + } catch (InterruptedException e) { + return null; + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound)); + }; + } catch (InvocationTargetException | IllegalAccessException ignored) { + return () -> null; + } + } + + protected GenericChunk loadChunk(DynmapChunk chunk) { + CraftWorld cw = (CraftWorld) w; + NBTTagCompound nbt = null; + ChunkCoordIntPair cc = new ChunkCoordIntPair(chunk.x, chunk.z); + GenericChunk gc = null; + try { // BUGBUG - convert this all to asyn properly, since now native async + nbt = cw.getHandle().l().a.d(cc).join().get(); // playerChunkMap + } catch (CancellationException cx) { + } catch (NoSuchElementException snex) { + } + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + public void setChunks(BukkitWorld dw, List chunks) { + this.w = dw.getWorld(); + super.setChunks(dw, chunks); + } + + @Override + public int getFoliageColor(BiomeMap bm, int[] colormap, int x, int z) { + return bm.getBiomeObject().map(BiomeBase::h).flatMap(BiomeFog::e).orElse(colormap[bm.biomeLookup()]); + } + + @Override + public int getGrassColor(BiomeMap bm, int[] colormap, int x, int z) { + BiomeFog fog = bm.getBiomeObject().map(BiomeBase::h).orElse(null); + if (fog == null) return colormap[bm.biomeLookup()]; + return fog.g().a(x, z, fog.f().orElse(colormap[bm.biomeLookup()])); + } +} diff --git a/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/NBT.java b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/NBT.java new file mode 100644 index 000000000..727afbfbd --- /dev/null +++ b/bukkit-helper-121/src/main/java/org/dynmap/bukkit/helper/v121/NBT.java @@ -0,0 +1,126 @@ +package org.dynmap.bukkit.helper.v121; + +import org.dynmap.common.chunk.GenericBitStorage; +import org.dynmap.common.chunk.GenericNBTCompound; +import org.dynmap.common.chunk.GenericNBTList; + +import java.util.Set; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.SimpleBitStorage; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final NBTTagCompound obj; + public NBTCompound(NBTTagCompound t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.e(); + } + @Override + public boolean contains(String s) { + return obj.e(s); + } + @Override + public boolean contains(String s, int i) { + return obj.b(s, i); + } + @Override + public byte getByte(String s) { + return obj.f(s); + } + @Override + public short getShort(String s) { + return obj.g(s); + } + @Override + public int getInt(String s) { + return obj.h(s); + } + @Override + public long getLong(String s) { + return obj.i(s); + } + @Override + public float getFloat(String s) { + return obj.j(s); + } + @Override + public double getDouble(String s) { + return obj.k(s); + } + @Override + public String getString(String s) { + return obj.l(s); + } + @Override + public byte[] getByteArray(String s) { + return obj.m(s); + } + @Override + public int[] getIntArray(String s) { + return obj.n(s); + } + @Override + public long[] getLongArray(String s) { + return obj.o(s); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.p(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + return new NBTList(obj.c(s, i)); + } + @Override + public boolean getBoolean(String s) { + return obj.q(s); + } + @Override + public String getAsString(String s) { + return obj.c(s).s_(); + } + @Override + public GenericBitStorage makeBitStorage(int bits, int count, long[] data) { + return new OurBitStorage(bits, count, data); + } + public String toString() { + return obj.toString(); + } + } + public static class NBTList implements GenericNBTList { + private final NBTTagList obj; + public NBTList(NBTTagList t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.j(idx); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.a(idx)); + } + public String toString() { + return obj.toString(); + } + } + public static class OurBitStorage implements GenericBitStorage { + private final SimpleBitStorage bs; + public OurBitStorage(int bits, int count, long[] data) { + bs = new SimpleBitStorage(bits, count, data); + } + @Override + public int get(int idx) { + return bs.a(idx); + } + } +} diff --git a/settings.gradle b/settings.gradle index d49a761c4..5d361966f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,6 +26,7 @@ include ':bukkit-helper-120' include ':bukkit-helper-120-2' include ':bukkit-helper-120-4' include ':bukkit-helper-120-5' +include ':bukkit-helper-121' include ':bukkit-helper' include ':dynmap-api' include ':DynmapCore' @@ -68,6 +69,7 @@ project(':bukkit-helper-120').projectDir = "$rootDir/bukkit-helper-120" as File project(':bukkit-helper-120-2').projectDir = "$rootDir/bukkit-helper-120-2" as File project(':bukkit-helper-120-4').projectDir = "$rootDir/bukkit-helper-120-4" as File project(':bukkit-helper-120-5').projectDir = "$rootDir/bukkit-helper-120-5" as File +project(':bukkit-helper-121').projectDir = "$rootDir/bukkit-helper-121" 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 2a0101c4a..e918e2183 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -88,6 +88,9 @@ dependencies { implementation(project(':bukkit-helper-120-5')) { transitive = false } + implementation(project(':bukkit-helper-121')) { + transitive = false + } } processResources { @@ -128,6 +131,7 @@ shadowJar { include(dependency(':bukkit-helper-120-2')) include(dependency(':bukkit-helper-120-4')) include(dependency(':bukkit-helper-120-5')) + include(dependency(':bukkit-helper-121')) } relocate('org.bstats', 'org.dynmap.bstats') destinationDirectory = 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 21be44914..2c8a94c85 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/Helper.java +++ b/spigot/src/main/java/org/dynmap/bukkit/Helper.java @@ -40,6 +40,9 @@ else if(Bukkit.getServer().getClass().getName().contains("GlowServer")) { Log.info("Loading Glowstone support"); BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.BukkitVersionHelperGlowstone"); } + else if (v.contains("(MC: 1.21)")) { + BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v121.BukkitVersionHelperSpigot121"); + } else if (v.contains("(MC: 1.20)") || v.contains("(MC: 1.20.1)")) { BukkitVersionHelper.helper = loadVersionHelper("org.dynmap.bukkit.helper.v120.BukkitVersionHelperSpigot120"); }