From a9e1a651f91b34d3c0c3075ccd8e117961f63553 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Mon, 13 May 2019 23:40:18 -0500 Subject: [PATCH] Initial 1.14.1 support --- bukkit-helper-114-1/.gitignore | 1 + bukkit-helper-114-1/bin/.gitignore | 1 + bukkit-helper-114-1/build.gradle | 10 + .../BukkitVersionHelperSpigot114_1.java | 193 ++++++++ .../helper/v114_1/MapChunkCache114_1.java | 415 ++++++++++++++++++ settings.gradle | 2 + spigot/build.gradle | 4 + .../main/java/org/dynmap/bukkit/Helper.java | 4 + 8 files changed, 630 insertions(+) create mode 100644 bukkit-helper-114-1/.gitignore create mode 100644 bukkit-helper-114-1/bin/.gitignore create mode 100644 bukkit-helper-114-1/build.gradle create mode 100644 bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/BukkitVersionHelperSpigot114_1.java create mode 100644 bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/MapChunkCache114_1.java diff --git a/bukkit-helper-114-1/.gitignore b/bukkit-helper-114-1/.gitignore new file mode 100644 index 00000000..84c048a7 --- /dev/null +++ b/bukkit-helper-114-1/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/bukkit-helper-114-1/bin/.gitignore b/bukkit-helper-114-1/bin/.gitignore new file mode 100644 index 00000000..ddf9c656 --- /dev/null +++ b/bukkit-helper-114-1/bin/.gitignore @@ -0,0 +1 @@ +/main/ diff --git a/bukkit-helper-114-1/build.gradle b/bukkit-helper-114-1/build.gradle new file mode 100644 index 00000000..4e2e14e8 --- /dev/null +++ b/bukkit-helper-114-1/build.gradle @@ -0,0 +1,10 @@ + +description = 'bukkit-helper-1.14.1' + +dependencies { + compile project(':bukkit-helper') + compile project(':dynmap-api') + compile project(path: ':DynmapCore', configuration: 'shadow') + compile group: 'org.bukkit', name: 'bukkit', version:'1.14.1-R0.1-SNAPSHOT' + compile group: 'org.bukkit', name: 'craftbukkit', version:'1.14.1-R0.1-SNAPSHOT' +} diff --git a/bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/BukkitVersionHelperSpigot114_1.java b/bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/BukkitVersionHelperSpigot114_1.java new file mode 100644 index 00000000..4f6d2c59 --- /dev/null +++ b/bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/BukkitVersionHelperSpigot114_1.java @@ -0,0 +1,193 @@ +package org.dynmap.bukkit.helper.v114_1; + +import java.lang.reflect.Field; + +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import org.bukkit.Chunk; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.WorldBorder; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.bukkit.helper.BukkitVersionHelperCB; +import org.dynmap.bukkit.helper.BukkitWorld; +import org.dynmap.bukkit.helper.v114_1.MapChunkCache114_1; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +import net.minecraft.server.v1_14_R1.BiomeBase; +import net.minecraft.server.v1_14_R1.Block; +import net.minecraft.server.v1_14_R1.BlockFluids; +import net.minecraft.server.v1_14_R1.BlockLogAbstract; +import net.minecraft.server.v1_14_R1.IBlockData; +import net.minecraft.server.v1_14_R1.IRegistry; +import net.minecraft.server.v1_14_R1.Material; + +/** + * Helper for isolation of bukkit version specific issues + */ +public class BukkitVersionHelperSpigot114_1 extends BukkitVersionHelperCB { + + /** CraftChunkSnapshot */ + protected Class datapalettearray; + private Field blockid_field; + + @Override + protected boolean isBlockIdNeeded() { + return false; + } + + @Override + protected boolean isBiomeBaseListNeeded() { + return false; + } + + public BukkitVersionHelperSpigot114_1() { + datapalettearray = getNMSClass("[Lnet.minecraft.server.DataPaletteBlock;"); + blockid_field = getPrivateField(craftchunksnapshot, new String[] { "blockids" }, datapalettearray); + } + + @Override + public Object[] getBlockIDFieldFromSnapshot(ChunkSnapshot css) { + try { + return (Object[]) blockid_field.get(css); + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } + return null; + } + @Override + public void unloadChunkNoSave(World w, Chunk c, int cx, int cz) { + w.unloadChunk(cx, cz, false); + } + + /** + * Get block short name list + */ + @Override + public String[] getBlockNames() { + int cnt = Block.REGISTRY_ID.a(); + String[] names = new String[cnt]; + for (int i = 0; i < cnt; i++) { + IBlockData bd = Block.getByCombinedId(i); + names[i] = IRegistry.BLOCK.getKey(bd.getBlock()).toString(); + Log.info(i + ": blk=" + names[i] + ", bd=" + bd.toString()); + } + return names; + } + + /** + * Get list of defined biomebase objects + */ + @Override + public Object[] getBiomeBaseList() { + if (biomelist == null) { + biomelist = new Object[1024]; + for (int i = 0; i < 1024; i++) { + biomelist[i] = IRegistry.BIOME.fromId(i); + } + } + return biomelist; + } + + /** Get ID from biomebase */ + @Override + public int getBiomeBaseID(Object bb) { + return IRegistry.BIOME.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(); + + int cnt = Block.REGISTRY_ID.a(); + // Loop through block data states + for (int i = 0; i < cnt; i++) { + IBlockData bd = Block.getByCombinedId(i); + String bname = IRegistry.BLOCK.getKey(bd.getBlock()).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); + } + Material mat = bd.getMaterial(); + DynmapBlockState bs = new DynmapBlockState(lastbs, idx, bname, sb, mat.toString()); + if ((!bd.p().isEmpty()) && ((bd.getBlock() instanceof BlockFluids) == false)) { // Test if fluid type for block is not empty + bs.setWaterlogged(); + } + if (mat == Material.AIR) { + bs.setAir(); + } + if (mat == Material.LEAVES) { + bs.setLeaves(); + } + if (bd.getBlock() instanceof BlockLogAbstract) { + bs.setLog(); + } + if (mat.isSolid()) { + bs.setSolid(); + } + dataToState.put(bd, bs); + lastBlockState.put(bname, (lastbs == null) ? bs : lastbs); + Log.verboseinfo(i + ": 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) { + MapChunkCache114_1 c = new MapChunkCache114_1(); + c.setChunks(dw, chunks); + return c; + } + + /** + * Get biome base water multiplier + */ + @Override + public int getBiomeBaseWaterMult(Object bb) { + return ((BiomeBase)bb).n(); + } + + @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; + } + +} diff --git a/bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/MapChunkCache114_1.java b/bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/MapChunkCache114_1.java new file mode 100644 index 00000000..75915931 --- /dev/null +++ b/bukkit-helper-114-1/src/main/java/org/dynmap/bukkit/helper/v114_1/MapChunkCache114_1.java @@ -0,0 +1,415 @@ +package org.dynmap.bukkit.helper.v114_1; + +import org.bukkit.block.Biome; +import org.bukkit.craftbukkit.v1_14_R1.CraftWorld; + +import java.io.IOException; +import java.util.Arrays; + +import org.bukkit.ChunkSnapshot; +import org.bukkit.World; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCore; +import org.dynmap.Log; +import org.dynmap.bukkit.helper.AbstractMapChunkCache; +import org.dynmap.bukkit.helper.SnapshotCache; +import org.dynmap.bukkit.helper.SnapshotCache.SnapshotRec; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.DynIntHashMap; +import org.dynmap.utils.VisibilityLimit; + +import net.minecraft.server.v1_14_R1.ChunkCoordIntPair; +import net.minecraft.server.v1_14_R1.DataBits; +import net.minecraft.server.v1_14_R1.NBTTagCompound; +import net.minecraft.server.v1_14_R1.NBTTagList; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class MapChunkCache114_1 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[] hmap; // Height map + private final int[] biome; + 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 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 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.sectionCnt = worldheight / 16; + /* Allocate arrays indexed by section */ + this.section = new Section[this.sectionCnt]; + + /* 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(NBTTagCompound 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.hasKey("InhabitedTime")) { + this.inhabitedTicks = nbt.getLong("InhabitedTime"); + } + else { + this.inhabitedTicks = 0; + } + /* Allocate arrays indexed by section */ + this.section = new Section[this.sectionCnt]; + /* Fill with empty data */ + for (int i = 0; i < this.sectionCnt; i++) { + this.section[i] = empty_section; + } + /* Get sections */ + NBTTagList sect = nbt.getList("Sections", 10); + for (int i = 0; i < sect.size(); i++) { + NBTTagCompound sec = sect.getCompound(i); + int secnum = sec.getByte("Y"); + if (secnum >= this.sectionCnt) { + Log.info("Section " + (int) secnum + " above world height " + worldheight); + continue; + } + if (secnum < 0) + continue; + //System.out.println("section(" + secnum + ")=" + sec.asString()); + // Create normal section to initialize + StdSection cursect = new StdSection(); + this.section[secnum] = cursect; + DynmapBlockState[] states = cursect.states; + DynmapBlockState[] palette = null; + // If we've got palette and block states list, process non-empty section + if (sec.hasKeyOfType("Palette", 9) && sec.hasKeyOfType("BlockStates", 12)) { + NBTTagList plist = sec.getList("Palette", 10); + long[] statelist = sec.getLongArray("BlockStates"); + palette = new DynmapBlockState[plist.size()]; + for (int pi = 0; pi < plist.size(); pi++) { + NBTTagCompound tc = plist.getCompound(pi); + String pname = tc.getString("Name"); + String statestr = ""; + if (tc.hasKey("Properties")) { + NBTTagCompound prop = tc.getCompound("Properties"); + for (String pid : prop.getKeys()) { + if (statestr.length() > 0) statestr += ","; + statestr += pid + "=" + prop.get(pid).asString(); + } + palette[pi] = DynmapBlockState.getStateByNameAndState(pname, statestr); + } + if (palette[pi] == null) { + palette[pi] = DynmapBlockState.getBaseStateByName(pname); + } + if (palette[pi] == null) { + palette[pi] = DynmapBlockState.AIR; + } + } + int bitsperblock = (statelist.length * 64) / 4096; + DataBits db = new DataBits(bitsperblock, 4096, statelist); + if (bitsperblock > 8) { // Not palette + for (int j = 0; j < 4096; j++) { + states[j] = DynmapBlockState.getStateByGlobalIndex(db.a(j)); + } + } + else { + for (int j = 0; j < 4096; j++) { + int v = db.a(j); + states[j] = (v < palette.length) ? palette[v] : DynmapBlockState.AIR; + } + } + } + cursect.emitlight = sec.getByteArray("BlockLight"); + if (sec.hasKey("SkyLight")) { + cursect.skylight = sec.getByteArray("SkyLight"); + } + } + /* Get biome data */ + this.biome = new int[COLUMNS_PER_CHUNK]; + if (nbt.hasKey("Biomes")) { + byte[] b = nbt.getByteArray("Biomes"); + if (b != null) { + for (int i = 0; i < b.length; i++) { + int bv = 255 & b[i]; + this.biome[i] = (bv == 255) ? 0 : bv; + } + } + else { // Check JEI biomes + int[] bb = nbt.getIntArray("Biomes"); + if (bb != null) { + for (int i = 0; i < bb.length; i++) { + int bv = bb[i]; + this.biome[i] = (bv < 0) ? 0 : bv; + } + } + } + } + } + + public int getX() + { + return x; + } + + public int getZ() + { + return z; + } + + public DynmapBlockState getBlockType(int x, int y, int z) + { + return section[y >> 4].getBlockType(x, y, z); + } + + public int getBlockSkyLight(int x, int y, int z) + { + return section[y >> 4].getBlockSkyLight(x, y, z); + } + + public int getBlockEmittedLight(int x, int y, int z) + { + return section[y >> 4].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) + { + return section[sy].isEmpty(); + } + + public long getInhabitedTicks() { + return inhabitedTicks; + } + + @Override + public Biome getBiome(int x, int z) { + return AbstractMapChunkCache.getBiomeByID(z << 4 | x); + } + + @Override + public Object[] getBiomeBaseFromSnapshot() { + return null; + } + } + + private NBTTagCompound loadChunkNBT(World w, int x, int z) { + CraftWorld cw = (CraftWorld) w; + ChunkCoordIntPair cc = new ChunkCoordIntPair(x, z); + NBTTagCompound nbt = null; + try { + nbt = cw.getHandle().getChunkProvider().playerChunkMap.read(cc); + } catch (IOException iox) { + } + if (nbt != null) { + nbt = nbt.getCompound("Level"); + } + 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; + } + // Load NTB for chunk, if it exists + NBTTagCompound nbt = loadChunkNBT(w, chunk.x, chunk.z); + 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 (ss == EMPTY) + endChunkLoad(startTime, ChunkStats.UNGENERATED_CHUNKS); + else + endChunkLoad(startTime, ChunkStats.UNLOADED_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 79936c4f..c65e3579 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ include ':bukkit-helper-113' include ':bukkit-helper-113-1' include ':bukkit-helper-113-2' include ':bukkit-helper-114' +include ':bukkit-helper-114-1' include ':bukkit-helper' include ':dynmap-api' include ':DynmapCore' @@ -20,6 +21,7 @@ project(':bukkit-helper-113').projectDir = "$rootDir/bukkit-helper-113" as File project(':bukkit-helper-113-1').projectDir = "$rootDir/bukkit-helper-113-1" as File project(':bukkit-helper-113-2').projectDir = "$rootDir/bukkit-helper-113-2" as File project(':bukkit-helper-114').projectDir = "$rootDir/bukkit-helper-114" as File +project(':bukkit-helper-114-1').projectDir = "$rootDir/bukkit-helper-114-1" 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 f37ac54a..121ff590 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -27,6 +27,9 @@ dependencies { implementation(project(':bukkit-helper-114')) { transitive = false } + implementation(project(':bukkit-helper-114-1')) { + transitive = false + } } processResources { @@ -55,6 +58,7 @@ shadowJar { include(dependency(':bukkit-helper-113-1')) include(dependency(':bukkit-helper-113-2')) include(dependency(':bukkit-helper-114')) + include(dependency(':bukkit-helper-114-1')) } 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 033616ff..ce71581f 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/Helper.java +++ b/spigot/src/main/java/org/dynmap/bukkit/Helper.java @@ -9,6 +9,7 @@ import org.dynmap.bukkit.helper.v113.BukkitVersionHelperSpigot113; import org.dynmap.bukkit.helper.v113_1.BukkitVersionHelperSpigot113_1; import org.dynmap.bukkit.helper.v113_2.BukkitVersionHelperSpigot113_2; import org.dynmap.bukkit.helper.v114.BukkitVersionHelperSpigot114; +import org.dynmap.bukkit.helper.v114_1.BukkitVersionHelperSpigot114_1; public class Helper { @@ -34,6 +35,9 @@ public class Helper { Log.info("Loading Glowstone support"); BukkitVersionHelper.helper = new BukkitVersionHelperGlowstone(); } + else if (v.contains("(MC: 1.14.1)")) { + BukkitVersionHelper.helper = new BukkitVersionHelperSpigot114_1(); + } else if (v.contains("(MC: 1.14)")) { BukkitVersionHelper.helper = new BukkitVersionHelperSpigot114(); }