diff --git a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java index 41fcfd67..fba99b8f 100644 --- a/DynmapCore/src/main/java/org/dynmap/DynmapCore.java +++ b/DynmapCore/src/main/java/org/dynmap/DynmapCore.java @@ -135,6 +135,7 @@ public class DynmapCore implements DynmapCommonAPI { private int fullrenderplayerlimit; /* Number of online players that will cause fullrender processing to pause */ private int updateplayerlimit; /* Number of online players that will cause update processing to pause */ private String publicURL; // If set, public HRL for accessing dynmap (declared by administrator) + private String noPermissionMsg; private boolean didfullpause; private boolean didupdatepause; private Map> ids_by_ip = new HashMap>(); @@ -571,6 +572,9 @@ public class DynmapCore implements DynmapCommonAPI { Log.info("EXPERIMENTAL: chunk migration enabled"); publicURL = configuration.getString("publicURL", ""); + + /* Send this message if the player does not have permission to use the command */ + noPermissionMsg = configuration.getString("noPermissionMsg", "You don't have permission to use this command!"); /* Load preupdate/postupdate commands */ ImageIOManager.preUpdateCommand = configuration.getString("custom-commands/image-updates/preupdatecommand", ""); @@ -2097,7 +2101,7 @@ public class DynmapCore implements DynmapCommonAPI { if (!(sender instanceof DynmapPlayer) || sender.isOp()) { return true; } else if (!sender.hasPrivilege(permission.toLowerCase())) { - sender.sendMessage("You don't have permission to use this command!"); + sender.sendMessage(noPermissionMsg); return false; } return true; diff --git a/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java b/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java index 90fa625d..2cfc7ce4 100644 --- a/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java +++ b/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java @@ -1,8 +1,10 @@ package org.dynmap.common.chunk; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.dynmap.DynmapChunk; import org.dynmap.DynmapCore; @@ -37,6 +39,7 @@ public abstract class GenericMapChunkCache extends MapChunkCache { private int snapcnt; private GenericChunk[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */ private boolean[][] isSectionNotEmpty; /* Indexed by snapshot index, then by section index */ + private AtomicInteger loadingChunks = new AtomicInteger(0); //the amount of threads loading chunks at this moment, used by async loading private static final BlockStep unstep[] = { BlockStep.X_MINUS, BlockStep.Y_MINUS, BlockStep.Z_MINUS, BlockStep.X_PLUS, BlockStep.Y_PLUS, BlockStep.Z_PLUS }; @@ -697,6 +700,14 @@ public abstract class GenericMapChunkCache extends MapChunkCache { protected abstract GenericChunk getLoadedChunk(DynmapChunk ch); // Load generic chunk from unloaded chunk protected abstract GenericChunk loadChunk(DynmapChunk ch); + // Load generic chunk from existing and already loaded chunk async + protected Supplier getLoadedChunkAsync(DynmapChunk ch) { + throw new IllegalStateException("Not implemeted"); + } + // Load generic chunks from unloaded chunk async + protected Supplier loadChunkAsync(DynmapChunk ch){ + throw new IllegalStateException("Not implemeted"); + } /** * Read NBT data from loaded chunks - needs to be called from server/world @@ -754,10 +765,82 @@ public abstract class GenericMapChunkCache extends MapChunkCache { return cnt; } + /** + * Read NBT data from loaded chunks - do not needs to be called from server/world + * Will throw {@link IllegalStateException} if not supporting + */ + public void getLoadedChunksAsync() { + class SimplePair { //simple pair of the supplier that finishes read async, and a consumer that also finish his work async + final Supplier supplier; + final BiConsumer consumer; + + SimplePair(Supplier supplier, BiConsumer consumer) { + this.supplier = supplier; + this.consumer = consumer; + } + } + if (!dw.isLoaded()) { + isempty = true; + unloadChunks(); + return; + } + List lastApply = new ArrayList<>(); + for (DynmapChunk dynmapChunk : chunks) { + long startTime = System.nanoTime(); + int chunkIndex = (dynmapChunk.x - x_min) + (dynmapChunk.z - z_min) * x_dim; + if (snaparray[chunkIndex] != null) + continue; // Skip if already processed + + boolean vis = isChunkVisible(dynmapChunk); + + /* Check if cached chunk snapshot found */ + if (tryChunkCache(dynmapChunk, vis)) { + endChunkLoad(startTime, ChunkStats.CACHED_SNAPSHOT_HIT); + } + // If chunk is loaded and not being unloaded, we're grabbing its NBT data + else { + // Get generic chunk from already loaded chunk, if we can + Supplier supplier = getLoadedChunkAsync(dynmapChunk); + long startPause = System.nanoTime(); + BiConsumer consumer = (ss, reloadTime) -> { + if (ss == null) return; + long pause = reloadTime - startPause; + if (vis) { // If visible + prepChunkSnapshot(dynmapChunk, ss); + } else { + if (hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN) { + ss = getStone(); + } else if (hidestyle == HiddenChunkStyle.FILL_OCEAN) { + ss = getOcean(); + } else { + ss = getEmpty(); + } + } + snaparray[chunkIndex] = ss; + endChunkLoad(startTime - pause, ChunkStats.LOADED_CHUNKS); + + }; + lastApply.add(new SimplePair(supplier, consumer)); + } + } + //impact on the main thread should be minimal, so we plan and finish the work after main thread finished it's part + lastApply.forEach(simplePair -> { + long reloadWork = System.nanoTime(); + simplePair.consumer.accept(simplePair.supplier.get(), reloadWork); + }); + } + @Override public int loadChunks(int max_to_load) { return getLoadedChunks() + readChunks(max_to_load); + } + /** + * Prepare the chunks async + */ + public void loadChunksAsync() { + getLoadedChunksAsync(); + readChunksAsync(); } public int readChunks(int max_to_load) { @@ -840,6 +923,96 @@ public abstract class GenericMapChunkCache extends MapChunkCache { return cnt; } + public void readChunksAsync() { + class SimplePair { //pair of the chunk and the data which is readed async + private final Supplier supplier; + private final DynmapChunk chunk; + + SimplePair(DynmapChunk chunk) { + this.chunk = chunk; + this.supplier = loadChunkAsync(chunk); + } + } + if (!dw.isLoaded()) { + isempty = true; + unloadChunks(); + return; + } + + List chunks; + if (iterator == null) { + iterator = Collections.emptyListIterator(); + chunks = new ArrayList<>(this.chunks); + } else { + chunks = new ArrayList<>(); + iterator.forEachRemaining(chunks::add); + } + //if before increent was 0, means that we are the first, so we need to set this + if (loadingChunks.getAndIncrement() == 0) { + DynmapCore.setIgnoreChunkLoads(true); + } + + try { + List cached = new ArrayList<>(); + List notCached = new ArrayList<>(); + + iterator.forEachRemaining(chunks::add); + chunks.stream() + .filter(chunk -> snaparray[(chunk.x - x_min) + (chunk.z - z_min) * x_dim] == null) + .forEach(chunk -> { + if (cache.getSnapshot(dw.getName(), chunk.x, chunk.z) == null) { + notCached.add(new SimplePair(chunk)); + } else { + cached.add(chunk); + } + }); + + cached.forEach(chunk -> { + long startTime = System.nanoTime(); + tryChunkCache(chunk, isChunkVisible(chunk)); + endChunkLoad(startTime, ChunkStats.CACHED_SNAPSHOT_HIT); + }); + notCached.forEach(chunkSupplier -> { + long startTime = System.nanoTime(); + GenericChunk chunk = chunkSupplier.supplier.get(); + DynmapChunk dynmapChunk = chunkSupplier.chunk; + if (chunk != null) { + // If hidden + if (isChunkVisible(dynmapChunk)) { + // Prep snapshot + prepChunkSnapshot(dynmapChunk, chunk); + } else { + if (hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN) { + chunk = getStone(); + } else if (hidestyle == HiddenChunkStyle.FILL_OCEAN) { + chunk = getOcean(); + } else { + chunk = getEmpty(); + } + } + snaparray[(dynmapChunk.x - x_min) + (dynmapChunk.z - z_min) * x_dim] = chunk; + endChunkLoad(startTime, ChunkStats.UNLOADED_CHUNKS); + } else { + endChunkLoad(startTime, ChunkStats.UNGENERATED_CHUNKS); + } + }); + + isempty = true; + /* Fill missing chunks with empty dummy chunk */ + for (int i = 0; i < snaparray.length; i++) { + if (snaparray[i] == null) { + snaparray[i] = getEmpty(); + } else if (!snaparray[i].isEmpty) { + isempty = false; + } + } + } finally { + if (loadingChunks.decrementAndGet() == 0) { + DynmapCore.setIgnoreChunkLoads(false); + } + } + } + /** * Test if done loading */ diff --git a/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java b/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java index 361e445a..533bd1e9 100644 --- a/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java +++ b/DynmapCore/src/main/java/org/dynmap/storage/postgresql/PostgreSQLMapStorage.java @@ -468,7 +468,7 @@ public class PostgreSQLMapStorage extends MapStorage { doUpdate(c, "CREATE TABLE " + tableStandaloneFiles + " (FileName VARCHAR(128) NOT NULL, ServerID BIGINT NOT NULL DEFAULT 0, Content BYTEA, PRIMARY KEY (FileName, ServerID))"); doUpdate(c, "CREATE INDEX " + tableMaps + "_idx ON " + tableMaps + "(WorldID, MapID, Variant, ServerID)"); doUpdate(c, "CREATE TABLE " + tableSchemaVersion + " (level INT PRIMARY KEY NOT NULL)"); - doUpdate(c, "INSERT INTO " + tableSchemaVersion + " (level) VALUES (3)"); + doUpdate(c, "INSERT INTO " + tableSchemaVersion + " (level) VALUES (4)"); version = 4; // initialzed to current schema } catch (SQLException x) { logSQLException("Error creating tables", x); diff --git a/DynmapCore/src/main/resources/extracted/web/index.html b/DynmapCore/src/main/resources/extracted/web/index.html index 38bb3e17..575a0ca4 100644 --- a/DynmapCore/src/main/resources/extracted/web/index.html +++ b/DynmapCore/src/main/resources/extracted/web/index.html @@ -10,7 +10,8 @@ - + + @@ -54,4 +55,4 @@
- \ No newline at end of file + diff --git a/DynmapCore/src/main/resources/extracted/web/js/map.js b/DynmapCore/src/main/resources/extracted/web/js/map.js index 0bbcd9f7..a0ec24b6 100644 --- a/DynmapCore/src/main/resources/extracted/web/js/map.js +++ b/DynmapCore/src/main/resources/extracted/web/js/map.js @@ -74,7 +74,7 @@ DynMap.prototype = { formatUrl: function(name, options) { var url = this.options.url[name]; $.each(options, function(n,v) { - url = url.replace("{" + n + "}", v); + url = url.replace("{" + n + "}", encodeURIComponent(v)); }); return url; }, diff --git a/DynmapCore/src/main/resources/extracted/web/js/markers.js b/DynmapCore/src/main/resources/extracted/web/js/markers.js index 33a7e1dc..42742f1a 100644 --- a/DynmapCore/src/main/resources/extracted/web/js/markers.js +++ b/DynmapCore/src/main/resources/extracted/web/js/markers.js @@ -27,7 +27,7 @@ componentconstructors['markers'] = function(dynmap, configuration) { function loadmarkers(world) { removeAllMarkers(); - var url = concatURL(dynmap.options.url.markers, '_markers_/marker_'+world+'.json'); + var url = concatURL(dynmap.options.url.markers, '_markers_/marker_' + encodeURIComponent(world) + '.json'); $.getJSON(url, function(data) { var ts = data.timestamp; diff --git a/DynmapCore/src/main/resources/models_1.txt b/DynmapCore/src/main/resources/models_1.txt index 3696eea8..77730a93 100644 --- a/DynmapCore/src/main/resources/models_1.txt +++ b/DynmapCore/src/main/resources/models_1.txt @@ -355,15 +355,19 @@ patchblock:id=ladder,data=6,data=7,patch0=VertX0In # Wall sign - facing east [-1.13.2]patchblock:id=wall_sign,data=0-1,patch0=WSignFront,patch1=WSignBack,patch2=WSignTop,patch3=WSignBottom,patch4=WSignLeft,patch5=WSignRight [1.14-]patchblock:id=oak_wall_sign,id=birch_wall_sign,id=spruce_wall_sign,id=acacia_wall_sign,id=jungle_wall_sign,id=dark_oak_wall_sign,data=0-1,patch0=WSignFront,patch1=WSignBack,patch2=WSignTop,patch3=WSignBottom,patch4=WSignLeft,patch5=WSignRight +[1.19-]patchblock:id=mangrove_wall_sign,data=0-1,patch0=WSignFront,patch1=WSignBack,patch2=WSignTop,patch3=WSignBottom,patch4=WSignLeft,patch5=WSignRight # Wall sign - facing west [-1.13.2]patchblock:id=wall_sign,data=2-3,patch0=WSignFront@180,patch1=WSignBack@180,patch2=WSignTop@180,patch3=WSignBottom@180,patch4=WSignLeft@180,patch5=WSignRight@180 [1.14-]patchblock:id=oak_wall_sign,id=birch_wall_sign,id=spruce_wall_sign,id=acacia_wall_sign,id=jungle_wall_sign,id=dark_oak_wall_sign,data=2-3,patch0=WSignFront@180,patch1=WSignBack@180,patch2=WSignTop@180,patch3=WSignBottom@180,patch4=WSignLeft@180,patch5=WSignRight@180 +[1.19-]patchblock:id=mangrove_wall_sign,data=2-3,patch0=WSignFront@180,patch1=WSignBack@180,patch2=WSignTop@180,patch3=WSignBottom@180,patch4=WSignLeft@180,patch5=WSignRight@180 # Wall sign - facing north [-1.13.2]patchblock:id=wall_sign,data=4-5,patch0=WSignFront@270,patch1=WSignBack@270,patch2=WSignTop@270,patch3=WSignBottom@270,patch4=WSignLeft@270,patch5=WSignRight@270 [1.14-]patchblock:id=oak_wall_sign,id=birch_wall_sign,id=spruce_wall_sign,id=acacia_wall_sign,id=jungle_wall_sign,id=dark_oak_wall_sign,data=4-5,patch0=WSignFront@270,patch1=WSignBack@270,patch2=WSignTop@270,patch3=WSignBottom@270,patch4=WSignLeft@270,patch5=WSignRight@270 +[1.19-]patchblock:id=mangrove_wall_sign,data=4-5,patch0=WSignFront@270,patch1=WSignBack@270,patch2=WSignTop@270,patch3=WSignBottom@270,patch4=WSignLeft@270,patch5=WSignRight@270 # Wall sign - facing south [-1.13.2]patchblock:id=wall_sign,data=6-7,patch0=WSignFront@90,patch1=WSignBack@90,patch2=WSignTop@90,patch3=WSignBottom@90,patch4=WSignLeft@90,patch5=WSignRight@90 [1.14-]patchblock:id=oak_wall_sign,id=birch_wall_sign,id=spruce_wall_sign,id=acacia_wall_sign,id=jungle_wall_sign,id=dark_oak_wall_sign,data=6-7,patch0=WSignFront@90,patch1=WSignBack@90,patch2=WSignTop@90,patch3=WSignBottom@90,patch4=WSignLeft@90,patch5=WSignRight@90 +[1.19-]patchblock:id=mangrove_wall_sign,data=6-7,patch0=WSignFront@90,patch1=WSignBack@90,patch2=WSignTop@90,patch3=WSignBottom@90,patch4=WSignLeft@90,patch5=WSignRight@90 # Redstone wire customblock:id=redstone_wire,class=org.dynmap.hdmap.renderer.RedstoneWireStateRenderer @@ -372,51 +376,67 @@ ignore-updates:id=redstone_wire # Signpost - facing west [-1.13.2]patchblock:id=sign,data=0-1,patch0=SignFront@180,patch1=SignBack@180,patch2=SignTop@180,patch3=SignBottom@180,patch4=SignLeft@180,patch5=SignRight@180,patch6=PostFront@180,patch7=PostBack@180,patch8=PostLeft@180,patch9=PostRight@180 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=0-1,patch0=SignFront@180,patch1=SignBack@180,patch2=SignTop@180,patch3=SignBottom@180,patch4=SignLeft@180,patch5=SignRight@180,patch6=PostFront@180,patch7=PostBack@180,patch8=PostLeft@180,patch9=PostRight@180 +[1.19-]patchblock:id=mangrove_sign,data=0-1,patch0=SignFront@180,patch1=SignBack@180,patch2=SignTop@180,patch3=SignBottom@180,patch4=SignLeft@180,patch5=SignRight@180,patch6=PostFront@180,patch7=PostBack@180,patch8=PostLeft@180,patch9=PostRight@180 # Signpost - facing north [-1.13.2]patchblock:id=sign,data=8-9,patch0=SignFront@270,patch1=SignBack@270,patch2=SignTop@270,patch3=SignBottom@270,patch4=SignLeft@270,patch5=SignRight@270,patch6=PostFront@270,patch7=PostBack@270,patch8=PostLeft@270,patch9=PostRight@270 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=8-9,patch0=SignFront@270,patch1=SignBack@270,patch2=SignTop@270,patch3=SignBottom@270,patch4=SignLeft@270,patch5=SignRight@270,patch6=PostFront@270,patch7=PostBack@270,patch8=PostLeft@270,patch9=PostRight@270 +[1.19-]patchblock:id=mangrove_sign,data=8-9,patch0=SignFront@270,patch1=SignBack@270,patch2=SignTop@270,patch3=SignBottom@270,patch4=SignLeft@270,patch5=SignRight@270,patch6=PostFront@270,patch7=PostBack@270,patch8=PostLeft@270,patch9=PostRight@270 # Signpost - facing east [-1.13.2]patchblock:id=sign,data=16-17,patch0=SignFront,patch1=SignBack,patch2=SignTop,patch3=SignBottom,patch4=SignLeft,patch5=SignRight,patch6=PostFront,patch7=PostBack,patch8=PostLeft,patch9=PostRight [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=16-17,patch0=SignFront,patch1=SignBack,patch2=SignTop,patch3=SignBottom,patch4=SignLeft,patch5=SignRight,patch6=PostFront,patch7=PostBack,patch8=PostLeft,patch9=PostRight +[1.19-]patchblock:id=mangrove_sign,data=16-17,patch0=SignFront,patch1=SignBack,patch2=SignTop,patch3=SignBottom,patch4=SignLeft,patch5=SignRight,patch6=PostFront,patch7=PostBack,patch8=PostLeft,patch9=PostRight # Signpost - facing south [-1.13.2]patchblock:id=sign,data=24-25,patch0=SignFront@90,patch1=SignBack@90,patch2=SignTop@90,patch3=SignBottom@90,patch4=SignLeft@90,patch5=SignRight@90,patch6=PostFront@90,patch7=PostBack@90,patch8=PostLeft@90,patch9=PostRight@90 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=24-25,patch0=SignFront@90,patch1=SignBack@90,patch2=SignTop@90,patch3=SignBottom@90,patch4=SignLeft@90,patch5=SignRight@90,patch6=PostFront@90,patch7=PostBack@90,patch8=PostLeft@90,patch9=PostRight@90 +[1.19-]patchblock:id=mangrove_sign,data=24-25,patch0=SignFront@90,patch1=SignBack@90,patch2=SignTop@90,patch3=SignBottom@90,patch4=SignLeft@90,patch5=SignRight@90,patch6=PostFront@90,patch7=PostBack@90,patch8=PostLeft@90,patch9=PostRight@90 # Signpost - facing northwest [-1.13.2]patchblock:id=sign,data=4-5,patch0=SignFront@225,patch1=SignBack@225,patch2=SignTop@225,patch3=SignBottom@225,patch4=SignLeft@225,patch5=SignRight@225,patch6=PostFront@225,patch7=PostBack@225,patch8=PostLeft@225,patch9=PostRight@225 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=4-5,patch0=SignFront@225,patch1=SignBack@225,patch2=SignTop@225,patch3=SignBottom@225,patch4=SignLeft@225,patch5=SignRight@225,patch6=PostFront@225,patch7=PostBack@225,patch8=PostLeft@225,patch9=PostRight@225 +[1.19-]patchblock:id=mangrove_sign,data=4-5,patch0=SignFront@225,patch1=SignBack@225,patch2=SignTop@225,patch3=SignBottom@225,patch4=SignLeft@225,patch5=SignRight@225,patch6=PostFront@225,patch7=PostBack@225,patch8=PostLeft@225,patch9=PostRight@225 # Signpost - facing northeast [-1.13.2]patchblock:id=sign,data=12-13,patch0=SignFront@315,patch1=SignBack@315,patch2=SignTop@315,patch3=SignBottom@315,patch4=SignLeft@315,patch5=SignRight@315,patch6=PostFront@315,patch7=PostBack@315,patch8=PostLeft@315,patch9=PostRight@315 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=12-13,patch0=SignFront@315,patch1=SignBack@315,patch2=SignTop@315,patch3=SignBottom@315,patch4=SignLeft@315,patch5=SignRight@315,patch6=PostFront@315,patch7=PostBack@315,patch8=PostLeft@315,patch9=PostRight@315 +[1.19-]patchblock:id=mangrove_sign,data=12-13,patch0=SignFront@315,patch1=SignBack@315,patch2=SignTop@315,patch3=SignBottom@315,patch4=SignLeft@315,patch5=SignRight@315,patch6=PostFront@315,patch7=PostBack@315,patch8=PostLeft@315,patch9=PostRight@315 # Signpost - facing southeast [-1.13.2]patchblock:id=sign,data=20-21,patch0=SignFront@45,patch1=SignBack@45,patch2=SignTop@45,patch3=SignBottom@45,patch4=SignLeft@45,patch5=SignRight@45,patch6=PostFront@45,patch7=PostBack@45,patch8=PostLeft@45,patch9=PostRight@45 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=20-21,patch0=SignFront@45,patch1=SignBack@45,patch2=SignTop@45,patch3=SignBottom@45,patch4=SignLeft@45,patch5=SignRight@45,patch6=PostFront@45,patch7=PostBack@45,patch8=PostLeft@45,patch9=PostRight@45 +[1.19-]patchblock:id=mangrove_sign,data=20-21,patch0=SignFront@45,patch1=SignBack@45,patch2=SignTop@45,patch3=SignBottom@45,patch4=SignLeft@45,patch5=SignRight@45,patch6=PostFront@45,patch7=PostBack@45,patch8=PostLeft@45,patch9=PostRight@45 # Signpost - facing southwest [-1.13.2]patchblock:id=sign,data=28-29,patch0=SignFront@135,patch1=SignBack@135,patch2=SignTop@135,patch3=SignBottom@135,patch4=SignLeft@135,patch5=SignRight@135,patch6=PostFront@135,patch7=PostBack@135,patch8=PostLeft@135,patch9=PostRight@135 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=28-29,patch0=SignFront@135,patch1=SignBack@135,patch2=SignTop@135,patch3=SignBottom@135,patch4=SignLeft@135,patch5=SignRight@135,patch6=PostFront@135,patch7=PostBack@135,patch8=PostLeft@135,patch9=PostRight@135 +[1.19-]patchblock:id=mangrove_sign,data=28-29,patch0=SignFront@135,patch1=SignBack@135,patch2=SignTop@135,patch3=SignBottom@135,patch4=SignLeft@135,patch5=SignRight@135,patch6=PostFront@135,patch7=PostBack@135,patch8=PostLeft@135,patch9=PostRight@135 # Signpost - facing west-northwest [-1.13.2]patchblock:id=sign,data=2-3,patch0=SignFront@203,patch1=SignBack@203,patch2=SignTop@203,patch3=SignBottom@203,patch4=SignLeft@203,patch5=SignRight@203,patch6=PostFront@203,patch7=PostBack@203,patch8=PostLeft@203,patch9=PostRight@203 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=2-3,patch0=SignFront@203,patch1=SignBack@203,patch2=SignTop@203,patch3=SignBottom@203,patch4=SignLeft@203,patch5=SignRight@203,patch6=PostFront@203,patch7=PostBack@203,patch8=PostLeft@203,patch9=PostRight@203 +[1.19-]patchblock:id=mangrove_sign,data=2-3,patch0=SignFront@203,patch1=SignBack@203,patch2=SignTop@203,patch3=SignBottom@203,patch4=SignLeft@203,patch5=SignRight@203,patch6=PostFront@203,patch7=PostBack@203,patch8=PostLeft@203,patch9=PostRight@203 # Signpost - facing north-northeast [-1.13.2]patchblock:id=sign,data=10-11,patch0=SignFront@293,patch1=SignBack@293,patch2=SignTop@293,patch3=SignBottom@293,patch4=SignLeft@293,patch5=SignRight@293,patch6=PostFront@293,patch7=PostBack@293,patch8=PostLeft@293,patch9=PostRight@293 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=10-11,patch0=SignFront@293,patch1=SignBack@293,patch2=SignTop@293,patch3=SignBottom@293,patch4=SignLeft@293,patch5=SignRight@293,patch6=PostFront@293,patch7=PostBack@293,patch8=PostLeft@293,patch9=PostRight@293 +[1.19-]patchblock:id=mangrove_sign,data=10-11,patch0=SignFront@293,patch1=SignBack@293,patch2=SignTop@293,patch3=SignBottom@293,patch4=SignLeft@293,patch5=SignRight@293,patch6=PostFront@293,patch7=PostBack@293,patch8=PostLeft@293,patch9=PostRight@293 # Signpost - facing north-northeast [-1.13.2]patchblock:id=sign,data=18-19,patch0=SignFront@23,patch1=SignBack@23,patch2=SignTop@23,patch3=SignBottom@23,patch4=SignLeft@23,patch5=SignRight@23,patch6=PostFront@23,patch7=PostBack@23,patch8=PostLeft@23,patch9=PostRight@23 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=18-19,patch0=SignFront@23,patch1=SignBack@23,patch2=SignTop@23,patch3=SignBottom@23,patch4=SignLeft@23,patch5=SignRight@23,patch6=PostFront@23,patch7=PostBack@23,patch8=PostLeft@23,patch9=PostRight@23 +[1.19-]patchblock:id=mangrove_sign,data=18-19,patch0=SignFront@23,patch1=SignBack@23,patch2=SignTop@23,patch3=SignBottom@23,patch4=SignLeft@23,patch5=SignRight@23,patch6=PostFront@23,patch7=PostBack@23,patch8=PostLeft@23,patch9=PostRight@23 # Signpost - facing north-northeast [-1.13.2]patchblock:id=sign,data=26-27,patch0=SignFront@113,patch1=SignBack@113,patch2=SignTop@113,patch3=SignBottom@113,patch4=SignLeft@113,patch5=SignRight@113,patch6=PostFront@113,patch7=PostBack@113,patch8=PostLeft@113,patch9=PostRight@113 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=26-27,patch0=SignFront@113,patch1=SignBack@113,patch2=SignTop@113,patch3=SignBottom@113,patch4=SignLeft@113,patch5=SignRight@113,patch6=PostFront@113,patch7=PostBack@113,patch8=PostLeft@113,patch9=PostRight@113 +[1.19-]patchblock:id=mangrove_sign,data=26-27,patch0=SignFront@113,patch1=SignBack@113,patch2=SignTop@113,patch3=SignBottom@113,patch4=SignLeft@113,patch5=SignRight@113,patch6=PostFront@113,patch7=PostBack@113,patch8=PostLeft@113,patch9=PostRight@113 # Signpost - facing west-southwest [-1.13.2]patchblock:id=sign,data=30-31,patch0=SignFront@157,patch1=SignBack@157,patch2=SignTop@157,patch3=SignBottom@157,patch4=SignLeft@157,patch5=SignRight@157,patch6=PostFront@157,patch7=PostBack@157,patch8=PostLeft@157,patch9=PostRight@157 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=30-31,patch0=SignFront@157,patch1=SignBack@157,patch2=SignTop@157,patch3=SignBottom@157,patch4=SignLeft@157,patch5=SignRight@157,patch6=PostFront@157,patch7=PostBack@157,patch8=PostLeft@157,patch9=PostRight@157 +[1.19-]patchblock:id=mangrove_sign,data=30-31,patch0=SignFront@157,patch1=SignBack@157,patch2=SignTop@157,patch3=SignBottom@157,patch4=SignLeft@157,patch5=SignRight@157,patch6=PostFront@157,patch7=PostBack@157,patch8=PostLeft@157,patch9=PostRight@157 # Signpost - facing north-northwest [-1.13.2]patchblock:id=sign,data=6-7,patch0=SignFront@247,patch1=SignBack@247,patch2=SignTop@247,patch3=SignBottom@247,patch4=SignLeft@247,patch5=SignRight@247,patch6=PostFront@247,patch7=PostBack@247,patch8=PostLeft@247,patch9=PostRight@247 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=6-7,patch0=SignFront@247,patch1=SignBack@247,patch2=SignTop@247,patch3=SignBottom@247,patch4=SignLeft@247,patch5=SignRight@247,patch6=PostFront@247,patch7=PostBack@247,patch8=PostLeft@247,patch9=PostRight@247 +[1.19-]patchblock:id=mangrove_sign,data=6-7,patch0=SignFront@247,patch1=SignBack@247,patch2=SignTop@247,patch3=SignBottom@247,patch4=SignLeft@247,patch5=SignRight@247,patch6=PostFront@247,patch7=PostBack@247,patch8=PostLeft@247,patch9=PostRight@247 # Signpost - facing east-northeast [-1.13.2]patchblock:id=sign,data=14-15,patch0=SignFront@337,patch1=SignBack@337,patch2=SignTop@337,patch3=SignBottom@337,patch4=SignLeft@337,patch5=SignRight@337,patch6=PostFront@337,patch7=PostBack@337,patch8=PostLeft@337,patch9=PostRight@337 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=14-15,patch0=SignFront@337,patch1=SignBack@337,patch2=SignTop@337,patch3=SignBottom@337,patch4=SignLeft@337,patch5=SignRight@337,patch6=PostFront@337,patch7=PostBack@337,patch8=PostLeft@337,patch9=PostRight@337 +[1.19-]patchblock:id=mangrove_sign,data=14-15,patch0=SignFront@337,patch1=SignBack@337,patch2=SignTop@337,patch3=SignBottom@337,patch4=SignLeft@337,patch5=SignRight@337,patch6=PostFront@337,patch7=PostBack@337,patch8=PostLeft@337,patch9=PostRight@337 # Signpost - facing south-southeast [-1.13.2]patchblock:id=sign,data=22-23,patch0=SignFront@67,patch1=SignBack@67,patch2=SignTop@67,patch3=SignBottom@67,patch4=SignLeft@67,patch5=SignRight@67,patch6=PostFront@67,patch7=PostBack@67,patch8=PostLeft@67,patch9=PostRight@67 [1.14-]patchblock:id=oak_sign,id=spruce_sign,id=birch_sign,id=acacia_sign,id=jungle_sign,id=dark_oak_sign,data=22-23,patch0=SignFront@67,patch1=SignBack@67,patch2=SignTop@67,patch3=SignBottom@67,patch4=SignLeft@67,patch5=SignRight@67,patch6=PostFront@67,patch7=PostBack@67,patch8=PostLeft@67,patch9=PostRight@67 +[1.19-]patchblock:id=mangrove_sign,data=22-23,patch0=SignFront@67,patch1=SignBack@67,patch2=SignTop@67,patch3=SignBottom@67,patch4=SignLeft@67,patch5=SignRight@67,patch6=PostFront@67,patch7=PostBack@67,patch8=PostLeft@67,patch9=PostRight@67 # Fire modellist:id=fire,box=0/0/8.8/false:16/16/8.8/-22.5/0/0:s/0/0/0/16/16,box=0/0/7.2/false:16/16/7.2/22.5/0/0:n/0/0/0/16/16,box=8.8/0/0/false:8.8/16/16/0/0/-22/5:w/0/0/0/16/16,box=7.2/0/0/false:7.2/16/16/0/0/22.5:e/0/0/0/16/16,box=0/0/0.01/false:16/16/0.01:s/0/0/0/16/16:n/0/0/0/16/16,box=0/0/0.01/false:16/16/0.01/0/90/0:s/0/0/0/16/16:n/0/0/0/16/16,box=0/0/0.01/false:16/16/0.01/0/180/0:s/0/0/0/16/16:n/0/0/0/16/16,box=0/0/0.01/false:16/16/0.01/0/270/0:s/0/0/0/16/16:n/0/0/0/16/16 @@ -2243,3 +2263,468 @@ modellist:id=%dropper,state=facing:south,box=0.000000/0.000000/0.000000:16.00000 modellist:id=%dropper,state=facing:west,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:d/2:w/1:e/1:n/0:u/2:s/1:R/0/270/0 modellist:id=%dropper,state=facing:down,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:d/0:w/0:e/0:n/0:u/1:s/0:R/180/0/0 + +[1.19-]modellist:id=%mangrove_propagule,state=hanging:true/age:0,box=7.000000/13.611040/10.071930:9.000000/13.611040/12.071930/22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u180/0/8.000000/3.000000/10.000000/5.000000,box=10.071930/13.611040/7.000000:12.071930/13.611040/9.000000/0.000000/0.000000/-22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d90/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u90/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.611040/3.928070:9.000000/13.611040/5.928070/-22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d180/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u/0/8.000000/3.000000/10.000000/5.000000,box=3.928070/13.611040/7.000000:5.928070/13.611040/9.000000/0.000000/0.000000/22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d270/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u270/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.000000/7.000000:9.000000/14.000000/9.000000:n/0/0.000000/2.000000/2.000000/3.000000:d/0/0.000000/3.000000/2.000000/5.000000:w/0/0.000000/2.000000/2.000000/3.000000:e/0/0.000000/2.000000/2.000000/3.000000:s/0/0.000000/2.000000/2.000000/3.000000:u/0/0.000000/0.000000/2.000000/2.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:false/age:0,box=4.500000/9.000000/8.000000:11.500000/15.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/4.000000/1.000000/11.000000/7.000000:s/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/9.000000/4.500000:8.000000/15.000000/11.500000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/4.000000/1.000000/11.000000/7.000000:e/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/0.000000/7.000000:8.000000/9.000000/9.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/7.000000/7.000000/9.000000/16.000000:e/0/7.000000/7.000000/9.000000/16.000000,box=7.000000/0.000000/8.000000:9.000000/9.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/7.000000/7.000000/9.000000/16.000000:s/0/7.000000/7.000000/9.000000/16.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:true/age:1,box=7.000000/10.000000/7.000000:9.000000/13.000000/9.000000:n/0/0.000000/7.000000/2.000000/10.000000:d/0/0.000000/5.000000/2.000000/7.000000:w/0/0.000000/7.000000/2.000000/10.000000:e/0/0.000000/7.000000/2.000000/10.000000:s/0/0.000000/7.000000/2.000000/10.000000:u/0/0.000000/5.000000/2.000000/7.000000,box=7.000000/13.611040/10.071930:9.000000/13.611040/12.071930/22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u180/0/8.000000/3.000000/10.000000/5.000000,box=10.071930/13.611040/7.000000:12.071930/13.611040/9.000000/0.000000/0.000000/-22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d90/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u90/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.611040/3.928070:9.000000/13.611040/5.928070/-22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d180/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u/0/8.000000/3.000000/10.000000/5.000000,box=3.928070/13.611040/7.000000:5.928070/13.611040/9.000000/0.000000/0.000000/22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d270/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u270/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.000000/7.000000:9.000000/14.000000/9.000000:n/0/0.000000/2.000000/2.000000/3.000000:d/0/0.000000/3.000000/2.000000/5.000000:w/0/0.000000/2.000000/2.000000/3.000000:e/0/0.000000/2.000000/2.000000/3.000000:s/0/0.000000/2.000000/2.000000/3.000000:u/0/0.000000/0.000000/2.000000/2.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:false/age:1,box=4.500000/9.000000/8.000000:11.500000/15.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/4.000000/1.000000/11.000000/7.000000:s/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/9.000000/4.500000:8.000000/15.000000/11.500000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/4.000000/1.000000/11.000000/7.000000:e/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/0.000000/7.000000:8.000000/9.000000/9.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/7.000000/7.000000/9.000000/16.000000:e/0/7.000000/7.000000/9.000000/16.000000,box=7.000000/0.000000/8.000000:9.000000/9.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/7.000000/7.000000/9.000000/16.000000:s/0/7.000000/7.000000/9.000000/16.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:true/age:2,box=7.000000/10.000000/7.000000:9.000000/13.000000/9.000000:n/0/0.000000/7.000000/2.000000/10.000000:d/0/0.000000/10.000000/2.000000/12.000000:w/0/0.000000/7.000000/2.000000/10.000000:e/0/0.000000/7.000000/2.000000/10.000000:s/0/0.000000/7.000000/2.000000/10.000000:u/0/0.000000/5.000000/2.000000/7.000000,box=7.000000/13.611040/10.071930:9.000000/13.611040/12.071930/22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u180/0/8.000000/3.000000/10.000000/5.000000,box=10.071930/13.611040/7.000000:12.071930/13.611040/9.000000/0.000000/0.000000/-22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d90/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u90/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.611040/3.928070:9.000000/13.611040/5.928070/-22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d180/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u/0/8.000000/3.000000/10.000000/5.000000,box=3.928070/13.611040/7.000000:5.928070/13.611040/9.000000/0.000000/0.000000/22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d270/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u270/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.000000/7.000000:9.000000/14.000000/9.000000:n/0/0.000000/2.000000/2.000000/3.000000:d/0/0.000000/3.000000/2.000000/5.000000:w/0/0.000000/2.000000/2.000000/3.000000:e/0/0.000000/2.000000/2.000000/3.000000:s/0/0.000000/2.000000/2.000000/3.000000:u/0/0.000000/0.000000/2.000000/2.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/7.000000/8.000000:9.000000/10.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/3.000000/7.000000/5.000000/10.000000:d/0/11.000000/10.000000/13.000000/10.000000:w/0/11.000000/0.000000/11.000000/10.000000:e/0/13.000000/0.000000/13.000000/10.000000:s/0/3.000000/7.000000/5.000000/10.000000:u/0/11.000000/0.000000/13.000000/0.000000,box=7.000000/7.000000/8.000000:9.000000/10.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/3.000000/7.000000/5.000000/10.000000:d180/0/11.000000/10.000000/13.000000/10.000000:w/0/13.000000/0.000000/13.000000/10.000000:e/0/11.000000/0.000000/11.000000/10.000000:s/0/3.000000/7.000000/5.000000/10.000000:u180/0/11.000000/0.000000/13.000000/0.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:false/age:2,box=4.500000/9.000000/8.000000:11.500000/15.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/4.000000/1.000000/11.000000/7.000000:s/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/9.000000/4.500000:8.000000/15.000000/11.500000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/4.000000/1.000000/11.000000/7.000000:e/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/0.000000/7.000000:8.000000/9.000000/9.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/7.000000/7.000000/9.000000/16.000000:e/0/7.000000/7.000000/9.000000/16.000000,box=7.000000/0.000000/8.000000:9.000000/9.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/7.000000/7.000000/9.000000/16.000000:s/0/7.000000/7.000000/9.000000/16.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:true/age:3,box=7.000000/10.000000/7.000000:9.000000/13.000000/9.000000:n/0/0.000000/7.000000/2.000000/10.000000:d/0/0.000000/10.000000/2.000000/12.000000:w/0/0.000000/7.000000/2.000000/10.000000:e/0/0.000000/7.000000/2.000000/10.000000:s/0/0.000000/7.000000/2.000000/10.000000:u/0/0.000000/5.000000/2.000000/7.000000,box=7.000000/13.611040/10.071930:9.000000/13.611040/12.071930/22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u180/0/8.000000/3.000000/10.000000/5.000000,box=10.071930/13.611040/7.000000:12.071930/13.611040/9.000000/0.000000/0.000000/-22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d90/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u90/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.611040/3.928070:9.000000/13.611040/5.928070/-22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d180/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u/0/8.000000/3.000000/10.000000/5.000000,box=3.928070/13.611040/7.000000:5.928070/13.611040/9.000000/0.000000/0.000000/22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d270/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u270/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.000000/7.000000:9.000000/14.000000/9.000000:n/0/0.000000/2.000000/2.000000/3.000000:d/0/0.000000/3.000000/2.000000/5.000000:w/0/0.000000/2.000000/2.000000/3.000000:e/0/0.000000/2.000000/2.000000/3.000000:s/0/0.000000/2.000000/2.000000/3.000000:u/0/0.000000/0.000000/2.000000/2.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/3.000000/8.000000:9.000000/10.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/3.000000/3.000000/5.000000/10.000000:d/0/11.000000/10.000000/13.000000/10.000000:w/0/11.000000/0.000000/11.000000/10.000000:e/0/13.000000/0.000000/13.000000/10.000000:s/0/3.000000/3.000000/5.000000/10.000000:u/0/11.000000/0.000000/13.000000/0.000000,box=7.000000/3.000000/8.000000:9.000000/10.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/3.000000/3.000000/5.000000/10.000000:d180/0/11.000000/10.000000/13.000000/10.000000:w/0/13.000000/0.000000/13.000000/10.000000:e/0/11.000000/0.000000/11.000000/10.000000:s/0/3.000000/3.000000/5.000000/10.000000:u180/0/11.000000/0.000000/13.000000/0.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:false/age:3,box=4.500000/9.000000/8.000000:11.500000/15.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/4.000000/1.000000/11.000000/7.000000:s/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/9.000000/4.500000:8.000000/15.000000/11.500000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/4.000000/1.000000/11.000000/7.000000:e/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/0.000000/7.000000:8.000000/9.000000/9.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/7.000000/7.000000/9.000000/16.000000:e/0/7.000000/7.000000/9.000000/16.000000,box=7.000000/0.000000/8.000000:9.000000/9.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/7.000000/7.000000/9.000000/16.000000:s/0/7.000000/7.000000/9.000000/16.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:true/age:4,box=7.000000/10.000000/7.000000:9.000000/13.000000/9.000000:n/0/0.000000/7.000000/2.000000/10.000000:d/0/0.000000/10.000000/2.000000/12.000000:w/0/0.000000/7.000000/2.000000/10.000000:e/0/0.000000/7.000000/2.000000/10.000000:s/0/0.000000/7.000000/2.000000/10.000000:u/0/0.000000/5.000000/2.000000/7.000000,box=7.000000/13.611040/10.071930:9.000000/13.611040/12.071930/22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u180/0/8.000000/3.000000/10.000000/5.000000,box=10.071930/13.611040/7.000000:12.071930/13.611040/9.000000/0.000000/0.000000/-22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d90/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u90/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.611040/3.928070:9.000000/13.611040/5.928070/-22.500000/0.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d180/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u/0/8.000000/3.000000/10.000000/5.000000,box=3.928070/13.611040/7.000000:5.928070/13.611040/9.000000/0.000000/0.000000/22.500000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/0.000000:d270/0/6.000000/3.000000/8.000000/5.000000:w/0/0.000000/0.000000/2.000000/0.000000:e/0/0.000000/0.000000/2.000000/0.000000:s/0/0.000000/0.000000/2.000000/0.000000:u270/0/8.000000/3.000000/10.000000/5.000000,box=7.000000/13.000000/7.000000:9.000000/14.000000/9.000000:n/0/0.000000/2.000000/2.000000/3.000000:d/0/0.000000/3.000000/2.000000/5.000000:w/0/0.000000/2.000000/2.000000/3.000000:e/0/0.000000/2.000000/2.000000/3.000000:s/0/0.000000/2.000000/2.000000/3.000000:u/0/0.000000/0.000000/2.000000/2.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/14.000000/8.000000:9.000000/16.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/0.000000/0.000000/2.000000/2.000000:d/0/0.000000/0.000000/2.000000/0.000000:w/0/0.000000/0.000000/0.000000/2.000000:e/0/0.000000/0.000000/0.000000/2.000000:s/0/0.000000/0.000000/2.000000/2.000000:u/0/0.000000/0.000000/2.000000/0.000000,box=7.000000/0.000000/8.000000:9.000000/10.000000/8.000000/0.000000/45.000000/0.000000/8.000000/16.000000/8.000000:n/0/3.000000/0.000000/5.000000/10.000000:d/0/11.000000/10.000000/13.000000/10.000000:w/0/11.000000/0.000000/11.000000/10.000000:e/0/13.000000/0.000000/13.000000/10.000000:s/0/3.000000/0.000000/5.000000/10.000000:u/0/11.000000/0.000000/13.000000/0.000000,box=7.000000/0.000000/8.000000:9.000000/10.000000/8.000000/0.000000/-45.000000/0.000000/8.000000/16.000000/8.000000:n/0/3.000000/0.000000/5.000000/10.000000:d180/0/11.000000/10.000000/13.000000/10.000000:w/0/13.000000/0.000000/13.000000/10.000000:e/0/11.000000/0.000000/11.000000/10.000000:s/0/3.000000/0.000000/5.000000/10.000000:u180/0/11.000000/0.000000/13.000000/0.000000 +[1.19-]modellist:id=%mangrove_propagule,state=hanging:false/age:4,box=4.500000/9.000000/8.000000:11.500000/15.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/4.000000/1.000000/11.000000/7.000000:s/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/9.000000/4.500000:8.000000/15.000000/11.500000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/4.000000/1.000000/11.000000/7.000000:e/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/0.000000/7.000000:8.000000/9.000000/9.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/7.000000/7.000000/9.000000/16.000000:e/0/7.000000/7.000000/9.000000/16.000000,box=7.000000/0.000000/8.000000:9.000000/9.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/7.000000/7.000000/9.000000/16.000000:s/0/7.000000/7.000000/9.000000/16.000000 +[1.19-]modellist:id=%mangrove_log,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/90/0 +[1.19-]modellist:id=%mangrove_log,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/0/0 +[1.19-]modellist:id=%mangrove_roots,box=0.000000/0.000000/8.000000:16.000000/16.000000/8.000000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/0.000000/0.000000:8.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/15.998000/0.000000:16.000000/16.000000/16.000000:d/1/0.000000/16.000000/16.000000/0.000000:u/1/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.000000:16.000000/0.002000/16.000000:d/1/0.000000/0.000000/16.000000/16.000000:u/1/0.000000/16.000000/16.000000/0.000000,box=0.000000/0.000000/0.000000:16.000000/16.000000/0.002000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/16.000000/0.000000/0.000000/16.000000,box=0.000000/0.000000/15.998000:16.000000/16.000000/16.000000:n/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.000000:0.002000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000,box=15.998000/0.000000/0.000000:16.000000/16.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%muddy_mangrove_roots,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u/1:R/90/90/0 +[1.19-]modellist:id=%muddy_mangrove_roots,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u/1:R/90/0/0 +[1.19-]modellist:id=%stripped_mangrove_log,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/90/0 +[1.19-]modellist:id=%stripped_mangrove_log,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/0/0 +[1.19-]modellist:id=%mangrove_wood,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/0:w/0:e/0:s/0:u/0:R/90/90/0 +[1.19-]modellist:id=%mangrove_wood,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/0:w/0:e/0:s/0:u/0:R/90/0/0 +[1.19-]modellist:id=%stripped_mangrove_wood,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/0:w/0:e/0:s/0:u/0:R/90/90/0 +[1.19-]modellist:id=%stripped_mangrove_wood,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/0:w/0:e/0:s/0:u/0:R/90/0/0 +[1.19-]modellist:id=%mangrove_pressure_plate,state=powered:true,box=1.000000/0.000000/1.000000:15.000000/0.500000/15.000000:n/0/1.000000/15.000000/15.000000/15.500000:d/0/1.000000/1.000000/15.000000/15.000000:w/0/1.000000/15.000000/15.000000/15.500000:e/0/1.000000/15.000000/15.000000/15.500000:s/0/1.000000/15.000000/15.000000/15.500000:u/0/1.000000/1.000000/15.000000/15.000000 +[1.19-]modellist:id=%mangrove_pressure_plate,state=powered:false,box=1.000000/0.000000/1.000000:15.000000/1.000000/15.000000:n/0/1.000000/15.000000/15.000000/16.000000:d/0/1.000000/1.000000/15.000000/15.000000:w/0/1.000000/15.000000/15.000000/16.000000:e/0/1.000000/15.000000/15.000000/16.000000:s/0/1.000000/15.000000/15.000000/16.000000:u/0/1.000000/1.000000/15.000000/15.000000 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:north/half:top/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:north/half:top/open:false,box=0.000000/13.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:north/half:bottom/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:north/half:bottom/open:false,box=0.000000/0.000000/0.000000:16.000000/3.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:south/half:top/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:south/half:top/open:false,box=0.000000/13.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:south/half:bottom/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:south/half:bottom/open:false,box=0.000000/0.000000/0.000000:16.000000/3.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:west/half:top/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:west/half:top/open:false,box=0.000000/13.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:west/half:bottom/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:west/half:bottom/open:false,box=0.000000/0.000000/0.000000:16.000000/3.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:east/half:top/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:east/half:top/open:false,box=0.000000/13.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:east/half:bottom/open:true,box=0.000000/0.000000/13.000000:16.000000/16.000000/16.000000:n/0/0.000000/16.000000/16.000000/0.000000:d/0/0.000000/0.000000/16.000000/3.000000:w90/0/0.000000/0.000000/16.000000/3.000000:e90/0/0.000000/3.000000/16.000000/0.000000:s/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/3.000000/16.000000/0.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_trapdoor,state=facing:east/half:bottom/open:false,box=0.000000/0.000000/0.000000:16.000000/3.000000/16.000000:n/0/0.000000/0.000000/16.000000/3.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/3.000000:e/0/0.000000/0.000000/16.000000/3.000000:s/0/0.000000/0.000000/16.000000/3.000000:u/0/0.000000/16.000000/16.000000/0.000000:R/0/90/0 +[1.19-]modellist:id=%potted_mangrove_propagule,box=4.500000/9.000000/8.000000:11.500000/15.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/11.000000/1.000000/4.000000/7.000000:s/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/9.000000/4.500000:8.000000/15.000000/11.500000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/4.000000/1.000000/11.000000/7.000000:e/0/11.000000/1.000000/4.000000/7.000000,box=8.000000/0.000000/7.000000:8.000000/9.000000/9.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/7.000000/7.000000/9.000000/16.000000:e/0/7.000000/7.000000/9.000000/16.000000,box=7.000000/0.000000/8.000000:9.000000/9.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/7.000000/7.000000/9.000000/16.000000:s/0/7.000000/7.000000/9.000000/16.000000,box=5.000000/0.000000/5.000000:6.000000/6.000000/11.000000:n/1/10.000000/10.000000/11.000000/16.000000:d/1/5.000000/5.000000/6.000000/11.000000:w/1/5.000000/10.000000/11.000000/16.000000:e/1/5.000000/10.000000/11.000000/16.000000:s/1/5.000000/10.000000/6.000000/16.000000:u/1/5.000000/5.000000/6.000000/11.000000,box=10.000000/0.000000/5.000000:11.000000/6.000000/11.000000:n/1/5.000000/10.000000/6.000000/16.000000:d/1/10.000000/5.000000/11.000000/11.000000:w/1/5.000000/10.000000/11.000000/16.000000:e/1/5.000000/10.000000/11.000000/16.000000:s/1/10.000000/10.000000/11.000000/16.000000:u/1/10.000000/5.000000/11.000000/11.000000,box=6.000000/0.000000/5.000000:10.000000/6.000000/6.000000:n/1/6.000000/10.000000/10.000000/16.000000:d/1/6.000000/10.000000/10.000000/11.000000:s/1/6.000000/10.000000/10.000000/16.000000:u/1/6.000000/5.000000/10.000000/6.000000,box=6.000000/0.000000/10.000000:10.000000/6.000000/11.000000:n/1/6.000000/10.000000/10.000000/16.000000:d/1/6.000000/5.000000/10.000000/6.000000:s/1/6.000000/10.000000/10.000000/16.000000:u/1/6.000000/10.000000/10.000000/11.000000,box=6.000000/0.000000/6.000000:10.000000/4.000000/10.000000:d/1/6.000000/12.000000/10.000000/16.000000:u/2/6.000000/6.000000/10.000000/10.000000 +[1.19-]modellist:id=%mangrove_button,state=facing:north/face:floor/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000 +[1.19-]modellist:id=%mangrove_button,state=facing:north/face:floor/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000 +[1.19-]modellist:id=%mangrove_button,state=facing:south/face:floor/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_button,state=facing:south/face:floor/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_button,state=facing:west/face:floor/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_button,state=facing:west/face:floor/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_button,state=facing:east/face:floor/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_button,state=facing:east/face:floor/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_button,state=facing:north/face:wall/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/0/0 +[1.19-]modellist:id=%mangrove_button,state=facing:north/face:wall/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/0/0 +[1.19-]modellist:id=%mangrove_button,state=facing:south/face:wall/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/180/0 +[1.19-]modellist:id=%mangrove_button,state=facing:south/face:wall/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/180/0 +[1.19-]modellist:id=%mangrove_button,state=facing:west/face:wall/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/270/0 +[1.19-]modellist:id=%mangrove_button,state=facing:west/face:wall/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/270/0 +[1.19-]modellist:id=%mangrove_button,state=facing:east/face:wall/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/90/0 +[1.19-]modellist:id=%mangrove_button,state=facing:east/face:wall/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/90/90/0 +[1.19-]modellist:id=%mangrove_button,state=facing:north/face:ceiling/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_button,state=facing:north/face:ceiling/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_button,state=facing:south/face:ceiling/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_button,state=facing:south/face:ceiling/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_button,state=facing:west/face:ceiling/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_button,state=facing:west/face:ceiling/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_button,state=facing:east/face:ceiling/powered:true,box=5.000000/0.000000/6.000000:11.000000/1.000000/10.000000:n/0/5.000000/14.000000/11.000000/15.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/15.000000:e/0/6.000000/14.000000/10.000000/15.000000:s/0/5.000000/14.000000/11.000000/15.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_button,state=facing:east/face:ceiling/powered:false,box=5.000000/0.000000/6.000000:11.000000/2.000000/10.000000:n/0/5.000000/14.000000/11.000000/16.000000:d/0/5.000000/6.000000/11.000000/10.000000:w/0/6.000000/14.000000/10.000000/16.000000:e/0/6.000000/14.000000/10.000000/16.000000:s/0/5.000000/14.000000/11.000000/16.000000:u/0/5.000000/10.000000/11.000000/6.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/0/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:north/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000 +[1.19-]modellist:id=%mangrove_stairs,state=facing:south/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:west/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/0/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_stairs,state=facing:east/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000 +[1.19-]modellist:id=%mangrove_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%mangrove_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:north/in_wall:true/open:true,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/180/0,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/180/0,box=0.000000/3.000000/13.000000:2.000000/12.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000:R/0/180/0,box=14.000000/3.000000/13.000000:16.000000/12.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000:R/0/180/0,box=0.000000/3.000000/9.000000:2.000000/6.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/180/0,box=0.000000/9.000000/9.000000:2.000000/12.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/180/0,box=14.000000/3.000000/9.000000:16.000000/6.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/180/0,box=14.000000/9.000000/9.000000:16.000000/12.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:north/in_wall:true/open:false,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/180/0,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/180/0,box=6.000000/3.000000/7.000000:8.000000/12.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000:R/0/180/0,box=8.000000/3.000000/7.000000:10.000000/12.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000:R/0/180/0,box=2.000000/3.000000/7.000000:6.000000/6.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/180/0,box=2.000000/9.000000/7.000000:6.000000/12.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/180/0,box=10.000000/3.000000/7.000000:14.000000/6.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/180/0,box=10.000000/9.000000/7.000000:14.000000/12.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:north/in_wall:false/open:true,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/180/0,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/180/0,box=0.000000/6.000000/13.000000:2.000000/15.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000:R/0/180/0,box=14.000000/6.000000/13.000000:16.000000/15.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000:R/0/180/0,box=0.000000/6.000000/9.000000:2.000000/9.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/180/0,box=0.000000/12.000000/9.000000:2.000000/15.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/180/0,box=14.000000/6.000000/9.000000:16.000000/9.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/180/0,box=14.000000/12.000000/9.000000:16.000000/15.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:north/in_wall:false/open:false,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/180/0,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/180/0,box=6.000000/6.000000/7.000000:8.000000/15.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000:R/0/180/0,box=8.000000/6.000000/7.000000:10.000000/15.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000:R/0/180/0,box=2.000000/6.000000/7.000000:6.000000/9.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/180/0,box=2.000000/12.000000/7.000000:6.000000/15.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/180/0,box=10.000000/6.000000/7.000000:14.000000/9.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/180/0,box=10.000000/12.000000/7.000000:14.000000/15.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:south/in_wall:true/open:true,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000,box=0.000000/3.000000/13.000000:2.000000/12.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000,box=14.000000/3.000000/13.000000:16.000000/12.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000,box=0.000000/3.000000/9.000000:2.000000/6.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000,box=0.000000/9.000000/9.000000:2.000000/12.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000,box=14.000000/3.000000/9.000000:16.000000/6.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000,box=14.000000/9.000000/9.000000:16.000000/12.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:south/in_wall:true/open:false,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000,box=6.000000/3.000000/7.000000:8.000000/12.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000,box=8.000000/3.000000/7.000000:10.000000/12.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000,box=2.000000/3.000000/7.000000:6.000000/6.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000,box=2.000000/9.000000/7.000000:6.000000/12.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000,box=10.000000/3.000000/7.000000:14.000000/6.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000,box=10.000000/9.000000/7.000000:14.000000/12.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:south/in_wall:false/open:true,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000,box=0.000000/6.000000/13.000000:2.000000/15.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000,box=14.000000/6.000000/13.000000:16.000000/15.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000,box=0.000000/6.000000/9.000000:2.000000/9.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000,box=0.000000/12.000000/9.000000:2.000000/15.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000,box=14.000000/6.000000/9.000000:16.000000/9.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000,box=14.000000/12.000000/9.000000:16.000000/15.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:south/in_wall:false/open:false,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000,box=6.000000/6.000000/7.000000:8.000000/15.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000,box=8.000000/6.000000/7.000000:10.000000/15.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000,box=2.000000/6.000000/7.000000:6.000000/9.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000,box=2.000000/12.000000/7.000000:6.000000/15.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000,box=10.000000/6.000000/7.000000:14.000000/9.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000,box=10.000000/12.000000/7.000000:14.000000/15.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:west/in_wall:true/open:true,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/90/0,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/90/0,box=0.000000/3.000000/13.000000:2.000000/12.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000:R/0/90/0,box=14.000000/3.000000/13.000000:16.000000/12.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000:R/0/90/0,box=0.000000/3.000000/9.000000:2.000000/6.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/90/0,box=0.000000/9.000000/9.000000:2.000000/12.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/90/0,box=14.000000/3.000000/9.000000:16.000000/6.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/90/0,box=14.000000/9.000000/9.000000:16.000000/12.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:west/in_wall:true/open:false,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/90/0,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/90/0,box=6.000000/3.000000/7.000000:8.000000/12.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000:R/0/90/0,box=8.000000/3.000000/7.000000:10.000000/12.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000:R/0/90/0,box=2.000000/3.000000/7.000000:6.000000/6.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/90/0,box=2.000000/9.000000/7.000000:6.000000/12.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/90/0,box=10.000000/3.000000/7.000000:14.000000/6.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/90/0,box=10.000000/9.000000/7.000000:14.000000/12.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:west/in_wall:false/open:true,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/90/0,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/90/0,box=0.000000/6.000000/13.000000:2.000000/15.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000:R/0/90/0,box=14.000000/6.000000/13.000000:16.000000/15.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000:R/0/90/0,box=0.000000/6.000000/9.000000:2.000000/9.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/90/0,box=0.000000/12.000000/9.000000:2.000000/15.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/90/0,box=14.000000/6.000000/9.000000:16.000000/9.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/90/0,box=14.000000/12.000000/9.000000:16.000000/15.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:west/in_wall:false/open:false,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/90/0,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/90/0,box=6.000000/6.000000/7.000000:8.000000/15.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000:R/0/90/0,box=8.000000/6.000000/7.000000:10.000000/15.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000:R/0/90/0,box=2.000000/6.000000/7.000000:6.000000/9.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/90/0,box=2.000000/12.000000/7.000000:6.000000/15.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/90/0,box=10.000000/6.000000/7.000000:14.000000/9.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/90/0,box=10.000000/12.000000/7.000000:14.000000/15.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:east/in_wall:true/open:true,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/270/0,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/270/0,box=0.000000/3.000000/13.000000:2.000000/12.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000:R/0/270/0,box=14.000000/3.000000/13.000000:16.000000/12.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000:R/0/270/0,box=0.000000/3.000000/9.000000:2.000000/6.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/270/0,box=0.000000/9.000000/9.000000:2.000000/12.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/270/0,box=14.000000/3.000000/9.000000:16.000000/6.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/270/0,box=14.000000/9.000000/9.000000:16.000000/12.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:east/in_wall:true/open:false,box=0.000000/2.000000/7.000000:2.000000/13.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/270/0,box=14.000000/2.000000/7.000000:16.000000/13.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/270/0,box=6.000000/3.000000/7.000000:8.000000/12.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000:R/0/270/0,box=8.000000/3.000000/7.000000:10.000000/12.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000:R/0/270/0,box=2.000000/3.000000/7.000000:6.000000/6.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/270/0,box=2.000000/9.000000/7.000000:6.000000/12.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/270/0,box=10.000000/3.000000/7.000000:14.000000/6.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/270/0,box=10.000000/9.000000/7.000000:14.000000/12.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:east/in_wall:false/open:true,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/270/0,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/270/0,box=0.000000/6.000000/13.000000:2.000000/15.000000/15.000000:n/0/0.000000/1.000000/2.000000/10.000000:d/0/0.000000/13.000000/2.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/0.000000/1.000000/2.000000/10.000000:u/0/0.000000/13.000000/2.000000/15.000000:R/0/270/0,box=14.000000/6.000000/13.000000:16.000000/15.000000/15.000000:n/0/14.000000/1.000000/16.000000/10.000000:d/0/14.000000/13.000000/16.000000/15.000000:w/0/13.000000/1.000000/15.000000/10.000000:e/0/13.000000/1.000000/15.000000/10.000000:s/0/14.000000/1.000000/16.000000/10.000000:u/0/14.000000/13.000000/16.000000/15.000000:R/0/270/0,box=0.000000/6.000000/9.000000:2.000000/9.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/270/0,box=0.000000/12.000000/9.000000:2.000000/15.000000/13.000000:d/0/0.000000/9.000000/2.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/0.000000/9.000000/2.000000/13.000000:R/0/270/0,box=14.000000/6.000000/9.000000:16.000000/9.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/7.000000/15.000000/10.000000:e/0/13.000000/7.000000/15.000000/10.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/270/0,box=14.000000/12.000000/9.000000:16.000000/15.000000/13.000000:d/0/14.000000/9.000000/16.000000/13.000000:w/0/13.000000/1.000000/15.000000/4.000000:e/0/13.000000/1.000000/15.000000/4.000000:u/0/14.000000/9.000000/16.000000/13.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence_gate,state=facing:east/in_wall:false/open:false,box=0.000000/5.000000/7.000000:2.000000/16.000000/9.000000:n/0/0.000000/0.000000/2.000000/11.000000:d/0/0.000000/7.000000/2.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/0.000000/0.000000/2.000000/11.000000:u/0/0.000000/7.000000/2.000000/9.000000:R/0/270/0,box=14.000000/5.000000/7.000000:16.000000/16.000000/9.000000:n/0/14.000000/0.000000/16.000000/11.000000:d/0/14.000000/7.000000/16.000000/9.000000:w/0/7.000000/0.000000/9.000000/11.000000:e/0/7.000000/0.000000/9.000000/11.000000:s/0/14.000000/0.000000/16.000000/11.000000:u/0/14.000000/7.000000/16.000000/9.000000:R/0/270/0,box=6.000000/6.000000/7.000000:8.000000/15.000000/9.000000:n/0/6.000000/1.000000/8.000000/10.000000:d/0/6.000000/7.000000/8.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/6.000000/1.000000/8.000000/10.000000:u/0/6.000000/7.000000/8.000000/9.000000:R/0/270/0,box=8.000000/6.000000/7.000000:10.000000/15.000000/9.000000:n/0/8.000000/1.000000/10.000000/10.000000:d/0/8.000000/7.000000/10.000000/9.000000:w/0/7.000000/1.000000/9.000000/10.000000:e/0/7.000000/1.000000/9.000000/10.000000:s/0/8.000000/1.000000/10.000000/10.000000:u/0/8.000000/7.000000/10.000000/9.000000:R/0/270/0,box=2.000000/6.000000/7.000000:6.000000/9.000000/9.000000:n/0/2.000000/7.000000/6.000000/10.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/7.000000/6.000000/10.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/270/0,box=2.000000/12.000000/7.000000:6.000000/15.000000/9.000000:n/0/2.000000/1.000000/6.000000/4.000000:d/0/2.000000/7.000000/6.000000/9.000000:s/0/2.000000/1.000000/6.000000/4.000000:u/0/2.000000/7.000000/6.000000/9.000000:R/0/270/0,box=10.000000/6.000000/7.000000:14.000000/9.000000/9.000000:n/0/10.000000/7.000000/14.000000/10.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/7.000000/14.000000/10.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/270/0,box=10.000000/12.000000/7.000000:14.000000/15.000000/9.000000:n/0/10.000000/1.000000/14.000000/4.000000:d/0/10.000000/7.000000/14.000000/9.000000:s/0/10.000000/1.000000/14.000000/4.000000:u/0/10.000000/7.000000/14.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:true/south:true/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:true/south:true/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:true/south:false/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:true/south:false/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:true/south:true/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:true/south:true/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:true/south:false/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:true/south:false/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:false/south:true/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:false/south:true/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:false/south:false/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:false/south:false/north:true,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:false/south:true/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:false/south:true/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_fence,state=west:true/east:false/south:false/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000,box=7.000000/12.000000/0.000000:9.000000/15.000000/9.000000:n/0/7.000000/1.000000/9.000000/4.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/1.000000/9.000000/4.000000:e/0/0.000000/1.000000/9.000000/4.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0,box=7.000000/6.000000/0.000000:9.000000/9.000000/9.000000:n/0/7.000000/7.000000/9.000000/10.000000:d/0/7.000000/0.000000/9.000000/9.000000:w/0/0.000000/7.000000/9.000000/10.000000:e/0/0.000000/7.000000/9.000000/10.000000:u/0/7.000000/0.000000/9.000000/9.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_fence,state=west:false/east:false/south:false/north:false,box=6.000000/0.000000/6.000000:10.000000/16.000000/10.000000:n/0/6.000000/0.000000/10.000000/16.000000:d/0/6.000000/6.000000/10.000000/10.000000:w/0/6.000000/0.000000/10.000000/16.000000:e/0/6.000000/0.000000/10.000000/16.000000:s/0/6.000000/0.000000/10.000000/16.000000:u/0/6.000000/6.000000/10.000000/10.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:left/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/3.000000/16.000000/0.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:left/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u90/0/0.000000/3.000000/16.000000/0.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:right/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000:u90/0/0.000000/0.000000/16.000000/3.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:right/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/0.000000/16.000000/3.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:left/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:d90/0/0.000000/16.000000/16.000000/13.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:left/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/13.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:right/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/16.000000/0.000000/13.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:north/hinge:right/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/0.000000/13.000000/16.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:left/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/3.000000/16.000000/0.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:left/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u90/0/0.000000/3.000000/16.000000/0.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:right/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000:u90/0/0.000000/0.000000/16.000000/3.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:right/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/0.000000/16.000000/3.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:left/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:d90/0/0.000000/16.000000/16.000000/13.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:left/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/13.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:right/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/16.000000/0.000000/13.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:south/hinge:right/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/0.000000/13.000000/16.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:left/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/3.000000/16.000000/0.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:left/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u90/0/0.000000/3.000000/16.000000/0.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:right/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000:u90/0/0.000000/0.000000/16.000000/3.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:right/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/0.000000/16.000000/3.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:left/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:d90/0/0.000000/16.000000/16.000000/13.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:left/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/13.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:right/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/16.000000/0.000000/13.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:west/hinge:right/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/0.000000/13.000000/16.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:left/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/3.000000/16.000000/0.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:left/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u90/0/0.000000/3.000000/16.000000/0.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:right/half:upper/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000:u90/0/0.000000/0.000000/16.000000/3.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:right/half:upper/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:u270/0/0.000000/0.000000/16.000000/3.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:left/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/0.000000/0.000000/3.000000/16.000000:d90/0/0.000000/16.000000/16.000000/13.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:left/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/13.000000/0.000000/16.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:right/half:lower/open:true,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/16.000000/16.000000/0.000000/13.000000:w/0/0.000000/0.000000/16.000000/16.000000:e/0/16.000000/0.000000/0.000000/16.000000:s/0/3.000000/0.000000/0.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mangrove_door,state=facing:east/hinge:right/half:lower/open:false,box=0.000000/0.000000/0.000000:3.000000/16.000000/16.000000:n/0/3.000000/0.000000/0.000000/16.000000:d90/0/0.000000/13.000000/16.000000/16.000000:w/0/16.000000/0.000000/0.000000/16.000000:e/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/3.000000/16.000000 +[1.19-]modellist:id=%stripped_mangrove_wood,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/0:w/0:e/0:s/0:u/0:R/90/90/0 +[1.19-]modellist:id=%stripped_mangrove_wood,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/0:w/0:e/0:s/0:u/0:R/90/0/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/0/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:north/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:south/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:west/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:top/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:top/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/0/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:top/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/180/90/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:top/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/0/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:top/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/180/90/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/180/90/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:bottom/shape:straight,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:bottom/shape:inner_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:bottom/shape:inner_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/0.000000/16.000000/16.000000,box=0.000000/8.000000/8.000000:8.000000/16.000000/16.000000:n/0/8.000000/0.000000/16.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/8.000000/8.000000:u/0/0.000000/8.000000/8.000000/16.000000 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:bottom/shape:outer_left,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%mud_brick_stairs,state=facing:east/half:bottom/shape:outer_right,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000,box=8.000000/8.000000/8.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/8.000000/8.000000:w/0/8.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/8.000000/8.000000:s/0/8.000000/0.000000/16.000000/8.000000:u/0/8.000000/8.000000/16.000000/16.000000 +[1.19-]modellist:id=%mud_brick_slab,state=type:top,box=0.000000/8.000000/0.000000:16.000000/16.000000/16.000000:n/0/0.000000/0.000000/16.000000/8.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/0.000000/16.000000/8.000000:e/0/0.000000/0.000000/16.000000/8.000000:s/0/0.000000/0.000000/16.000000/8.000000:u/0/0.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%mud_brick_slab,state=type:bottom,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/0/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/0/0.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:none/north:none +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:none/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:none/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:none/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:low/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:low/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:low/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:none/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:none/north:none,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:low/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:low/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:tall/north:none,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:tall/north:none,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:none/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:none/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:low/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:low/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:tall/north:low,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:tall/north:low,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:none/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:none/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:low/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:low/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:true/south:tall/north:tall,box=4.000000/0.000000/4.000000:12.000000/16.000000/12.000000:n/0:d/0:w/0:e/0:s/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:none/east:tall/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:low/east:tall/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/14.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0 +[1.19-]modellist:id=%mud_brick_wall,state=west:tall/east:tall/up:false/south:tall/north:tall,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/90/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/180/0,box=5.000000/0.000000/0.000000:11.000000/16.000000/8.000000:n/0:d/0:w/0:e/0:u/0:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:true/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:false/up:true/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:true/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:false/up:false/down:true,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:true/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:true/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:true/north:false/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:true/south:false/north:false/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:true/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:true/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:true/north:false/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:true/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:false/up:true/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:true/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0 +[1.19-]modellist:id=%sculk_vein,state=east:false/south:false/north:false/west:false/up:false/down:false,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/90/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/180/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/0/270/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/270/0/0,box=0.000000/0.000000/0.100000:16.000000/16.000000/0.100000:n/0/0.000000/0.000000/16.000000/16.000000:s/0/0.000000/0.000000/16.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%sculk_shrieker,state=can_summon:true,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/2/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/1/0.000000/0.000000/16.000000/16.000000,box=1.000000/8.000000/1.000000:15.000000/15.000000/15.000000:n/0/1.000000/1.000000/15.000000/8.000000:w/0/1.000000/1.000000/15.000000/8.000000:e/0/1.000000/1.000000/15.000000/8.000000:s/0/1.000000/1.000000/15.000000/8.000000:u/3/1.000000/1.000000/15.000000/15.000000,box=1.000000/14.980000/1.000000:15.000000/14.980000/15.000000:d/3/1.000000/1.000000/15.000000/15.000000,box=1.000000/8.000000/14.980000:15.000000/15.000000/14.980000:n/0/1.000000/1.000000/15.000000/8.000000,box=1.000000/8.000000/1.020000:15.000000/15.000000/1.020000:s/0/1.000000/1.000000/15.000000/8.000000,box=14.980000/8.000000/1.000000:14.980000/15.000000/15.000000:w/0/1.000000/1.000000/15.000000/8.000000,box=1.020000/8.000000/1.000000:1.020000/15.000000/15.000000:e/0/1.000000/1.000000/15.000000/8.000000 +[1.19-]modellist:id=%sculk_shrieker,state=can_summon:false,box=0.000000/0.000000/0.000000:16.000000/8.000000/16.000000:n/0/0.000000/8.000000/16.000000/16.000000:d/2/0.000000/0.000000/16.000000/16.000000:w/0/0.000000/8.000000/16.000000/16.000000:e/0/0.000000/8.000000/16.000000/16.000000:s/0/0.000000/8.000000/16.000000/16.000000:u/1/0.000000/0.000000/16.000000/16.000000,box=1.000000/8.000000/1.000000:15.000000/15.000000/15.000000:n/0/1.000000/1.000000/15.000000/8.000000:w/0/1.000000/1.000000/15.000000/8.000000:e/0/1.000000/1.000000/15.000000/8.000000:s/0/1.000000/1.000000/15.000000/8.000000:u/3/1.000000/1.000000/15.000000/15.000000,box=1.000000/14.980000/1.000000:15.000000/14.980000/15.000000:d/3/1.000000/1.000000/15.000000/15.000000,box=1.000000/8.000000/14.980000:15.000000/15.000000/14.980000:n/0/1.000000/1.000000/15.000000/8.000000,box=1.000000/8.000000/1.020000:15.000000/15.000000/1.020000:s/0/1.000000/1.000000/15.000000/8.000000,box=14.980000/8.000000/1.000000:14.980000/15.000000/15.000000:w/0/1.000000/1.000000/15.000000/8.000000,box=1.020000/8.000000/1.000000:1.020000/15.000000/15.000000:e/0/1.000000/1.000000/15.000000/8.000000 +[1.19-]modellist:id=%ochre_froglight,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/90/0 +[1.19-]modellist:id=%ochre_froglight,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/0/0 +[1.19-]modellist:id=%verdant_froglight,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/90/0 +[1.19-]modellist:id=%verdant_froglight,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/0/0 +[1.19-]modellist:id=%pearlescent_froglight,state=axis:x,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/90/0 +[1.19-]modellist:id=%pearlescent_froglight,state=axis:z,box=0.000000/0.000000/0.000000:16.000000/16.000000/16.000000:n/0:d/1:w/0:e/0:s/0:u180/1:R/90/0/0 +[1.19-]modellist:id=%frogspawn,box=0.000000/0.250000/0.000000:16.000000/0.250000/16.000000:d/0/0.000000/16.000000/16.000000/0.000000:u/0/0.000000/0.000000/16.000000/16.000000 +[1.19-]modellist:id=%potted_mangrove_propagule,box=4.500000/9.000000/8.000000:11.500000/15.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/11.000000/1.000000/4.000000/7.000000:s/0/4.000000/1.000000/11.000000/7.000000,box=8.000000/9.000000/4.500000:8.000000/15.000000/11.500000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/4.000000/1.000000/11.000000/7.000000:e/0/11.000000/1.000000/4.000000/7.000000,box=8.000000/0.000000/7.000000:8.000000/9.000000/9.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:w/0/7.000000/7.000000/9.000000/16.000000:e/0/7.000000/7.000000/9.000000/16.000000,box=7.000000/0.000000/8.000000:9.000000/9.000000/8.000000/0.000000/45.000000/0.000000/8.000000/0.000000/8.000000:n/0/7.000000/7.000000/9.000000/16.000000:s/0/7.000000/7.000000/9.000000/16.000000,box=5.000000/0.000000/5.000000:6.000000/6.000000/11.000000:n/1/10.000000/10.000000/11.000000/16.000000:d/1/5.000000/5.000000/6.000000/11.000000:w/1/5.000000/10.000000/11.000000/16.000000:e/1/5.000000/10.000000/11.000000/16.000000:s/1/5.000000/10.000000/6.000000/16.000000:u/1/5.000000/5.000000/6.000000/11.000000,box=10.000000/0.000000/5.000000:11.000000/6.000000/11.000000:n/1/5.000000/10.000000/6.000000/16.000000:d/1/10.000000/5.000000/11.000000/11.000000:w/1/5.000000/10.000000/11.000000/16.000000:e/1/5.000000/10.000000/11.000000/16.000000:s/1/10.000000/10.000000/11.000000/16.000000:u/1/10.000000/5.000000/11.000000/11.000000,box=6.000000/0.000000/5.000000:10.000000/6.000000/6.000000:n/1/6.000000/10.000000/10.000000/16.000000:d/1/6.000000/10.000000/10.000000/11.000000:s/1/6.000000/10.000000/10.000000/16.000000:u/1/6.000000/5.000000/10.000000/6.000000,box=6.000000/0.000000/10.000000:10.000000/6.000000/11.000000:n/1/6.000000/10.000000/10.000000/16.000000:d/1/6.000000/5.000000/10.000000/6.000000:s/1/6.000000/10.000000/10.000000/16.000000:u/1/6.000000/10.000000/10.000000/11.000000,box=6.000000/0.000000/6.000000:10.000000/4.000000/10.000000:d/1/6.000000/12.000000/10.000000/16.000000:u/2/6.000000/6.000000/10.000000/10.000000 +[1.19-]modellist:id=%potted_azalea_bush,box=5.000000/0.000000/5.000000:6.000000/6.000000/11.000000:n/0/10.000000/10.000000/11.000000/16.000000:d/0/5.000000/5.000000/6.000000/11.000000:w/0/5.000000/10.000000/11.000000/16.000000:e/0/5.000000/10.000000/11.000000/16.000000:s/0/5.000000/10.000000/6.000000/16.000000:u/0/5.000000/5.000000/6.000000/11.000000,box=10.000000/0.000000/5.000000:11.000000/6.000000/11.000000:n/0/5.000000/10.000000/6.000000/16.000000:d/0/10.000000/5.000000/11.000000/11.000000:w/0/5.000000/10.000000/11.000000/16.000000:e/0/5.000000/10.000000/11.000000/16.000000:s/0/10.000000/10.000000/11.000000/16.000000:u/0/10.000000/5.000000/11.000000/11.000000,box=6.000000/0.000000/5.000000:10.000000/6.000000/6.000000:n/0/6.000000/10.000000/10.000000/16.000000:d/0/6.000000/10.000000/10.000000/11.000000:s/0/6.000000/10.000000/10.000000/16.000000:u/0/6.000000/5.000000/10.000000/6.000000,box=6.000000/0.000000/10.000000:10.000000/6.000000/11.000000:n/0/6.000000/10.000000/10.000000/16.000000:d/0/6.000000/5.000000/10.000000/6.000000:s/0/6.000000/10.000000/10.000000/16.000000:u/0/6.000000/10.000000/10.000000/11.000000,box=6.000000/0.000000/6.000000:10.000000/4.000000/10.000000:d/0/6.000000/12.000000/10.000000/16.000000:u/1/6.000000/6.000000/10.000000/10.000000,box=4.000000/15.900000/4.000000:12.000000/16.000000/12.000000:d/2/4.000000/12.000000/12.000000/4.000000:u/2/4.000000/4.000000/12.000000/12.000000,box=4.000000/8.000000/4.000000:12.000000/16.000000/4.000000:n/3/4.000000/5.000000/12.000000/13.000000:s/3/12.000000/5.000000/4.000000/13.000000,box=4.000000/8.000000/12.000000:12.000000/16.000000/12.000000:n/3/12.000000/5.000000/4.000000/13.000000:s/3/4.000000/5.000000/12.000000/13.000000,box=4.000000/8.000000/4.000000:4.000000/16.000000/12.000000:w/3/4.000000/5.000000/12.000000/13.000000:e/3/12.000000/5.000000/4.000000/13.000000,box=12.000000/8.000000/4.000000:12.000000/16.000000/12.000000:w/3/12.000000/5.000000/4.000000/13.000000:e/3/4.000000/5.000000/12.000000/13.000000,box=2.600000/4.000000/8.000000:13.400000/16.000000/8.000000/0.000000/45.000000/0.000000/8.000000/8.000000/8.000000:n/4/0.000000/4.000000/16.000000/16.000000:s/4/0.000000/4.000000/16.000000/16.000000,box=8.000000/4.000000/2.600000:8.000000/16.000000/13.400000/0.000000/45.000000/0.000000/8.000000/8.000000/8.000000:w/4/0.000000/4.000000/16.000000/16.000000:e/4/0.000000/4.000000/16.000000/16.000000 +[1.19-]modellist:id=%potted_flowering_azalea_bush,box=5.000000/0.000000/5.000000:6.000000/6.000000/11.000000:n/0/10.000000/10.000000/11.000000/16.000000:d/0/5.000000/5.000000/6.000000/11.000000:w/0/5.000000/10.000000/11.000000/16.000000:e/0/5.000000/10.000000/11.000000/16.000000:s/0/5.000000/10.000000/6.000000/16.000000:u/0/5.000000/5.000000/6.000000/11.000000,box=10.000000/0.000000/5.000000:11.000000/6.000000/11.000000:n/0/5.000000/10.000000/6.000000/16.000000:d/0/10.000000/5.000000/11.000000/11.000000:w/0/5.000000/10.000000/11.000000/16.000000:e/0/5.000000/10.000000/11.000000/16.000000:s/0/10.000000/10.000000/11.000000/16.000000:u/0/10.000000/5.000000/11.000000/11.000000,box=6.000000/0.000000/5.000000:10.000000/6.000000/6.000000:n/0/6.000000/10.000000/10.000000/16.000000:d/0/6.000000/10.000000/10.000000/11.000000:s/0/6.000000/10.000000/10.000000/16.000000:u/0/6.000000/5.000000/10.000000/6.000000,box=6.000000/0.000000/10.000000:10.000000/6.000000/11.000000:n/0/6.000000/10.000000/10.000000/16.000000:d/0/6.000000/5.000000/10.000000/6.000000:s/0/6.000000/10.000000/10.000000/16.000000:u/0/6.000000/10.000000/10.000000/11.000000,box=6.000000/0.000000/6.000000:10.000000/4.000000/10.000000:d/0/6.000000/12.000000/10.000000/16.000000:u/1/6.000000/6.000000/10.000000/10.000000,box=4.000000/15.900000/4.000000:12.000000/16.000000/12.000000:d/2/4.000000/12.000000/12.000000/4.000000:u/2/4.000000/4.000000/12.000000/12.000000,box=4.000000/8.000000/4.000000:12.000000/16.000000/4.000000:n/3/4.000000/5.000000/12.000000/13.000000:s/3/12.000000/5.000000/4.000000/13.000000,box=4.000000/8.000000/12.000000:12.000000/16.000000/12.000000:n/3/12.000000/5.000000/4.000000/13.000000:s/3/4.000000/5.000000/12.000000/13.000000,box=4.000000/8.000000/4.000000:4.000000/16.000000/12.000000:w/3/4.000000/5.000000/12.000000/13.000000:e/3/12.000000/5.000000/4.000000/13.000000,box=12.000000/8.000000/4.000000:12.000000/16.000000/12.000000:w/3/12.000000/5.000000/4.000000/13.000000:e/3/4.000000/5.000000/12.000000/13.000000,box=2.600000/4.000000/8.000000:13.400000/16.000000/8.000000/0.000000/45.000000/0.000000/8.000000/8.000000/8.000000:n/4/0.000000/4.000000/16.000000/16.000000:s/4/0.000000/4.000000/16.000000/16.000000,box=8.000000/4.000000/2.600000:8.000000/16.000000/13.400000/0.000000/45.000000/0.000000/8.000000/8.000000/8.000000:w/4/0.000000/4.000000/16.000000/16.000000:e/4/0.000000/4.000000/16.000000/16.000000 +[1.19-]modellist:id=%lightning_rod,state=powered:true/facing:north,box=6.000000/12.000000/6.000000/false:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/0/0,box=7.000000/0.000000/7.000000/false:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/16.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%lightning_rod,state=powered:false/facing:north,box=6.000000/12.000000/6.000000:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/0/0,box=7.000000/0.000000/7.000000:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/6.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/0/0 +[1.19-]modellist:id=%lightning_rod,state=powered:true/facing:east,box=6.000000/12.000000/6.000000/false:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/90/0,box=7.000000/0.000000/7.000000/false:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/16.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/90/0 +[1.19-]modellist:id=%lightning_rod,state=powered:false/facing:east,box=6.000000/12.000000/6.000000:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/90/0,box=7.000000/0.000000/7.000000:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/6.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/90/0 +[1.19-]modellist:id=%lightning_rod,state=powered:true/facing:south,box=6.000000/12.000000/6.000000/false:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/180/0,box=7.000000/0.000000/7.000000/false:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/16.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/180/0 +[1.19-]modellist:id=%lightning_rod,state=powered:false/facing:south,box=6.000000/12.000000/6.000000:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/180/0,box=7.000000/0.000000/7.000000:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/6.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/180/0 +[1.19-]modellist:id=%lightning_rod,state=powered:true/facing:west,box=6.000000/12.000000/6.000000/false:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/270/0,box=7.000000/0.000000/7.000000/false:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/16.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/270/0 +[1.19-]modellist:id=%lightning_rod,state=powered:false/facing:west,box=6.000000/12.000000/6.000000:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/90/270/0,box=7.000000/0.000000/7.000000:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/6.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/90/270/0 +[1.19-]modellist:id=%lightning_rod,state=powered:true/facing:up,box=6.000000/12.000000/6.000000/false:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000,box=7.000000/0.000000/7.000000/false:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/16.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000 +[1.19-]modellist:id=%lightning_rod,state=powered:false/facing:up,box=6.000000/12.000000/6.000000:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000,box=7.000000/0.000000/7.000000:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/6.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000 +[1.19-]modellist:id=%lightning_rod,state=powered:true/facing:down,box=6.000000/12.000000/6.000000/false:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/180/0/0,box=7.000000/0.000000/7.000000/false:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/16.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/180/0/0 +[1.19-]modellist:id=%lightning_rod,state=powered:false/facing:down,box=6.000000/12.000000/6.000000:10.000000/16.000000/10.000000:n/0/0.000000/0.000000/4.000000/4.000000:d/0/0.000000/0.000000/4.000000/4.000000:w/0/0.000000/0.000000/4.000000/4.000000:e/0/0.000000/0.000000/4.000000/4.000000:s/0/0.000000/0.000000/4.000000/4.000000:u/0/4.000000/4.000000/0.000000/0.000000:R/180/0/0,box=7.000000/0.000000/7.000000:9.000000/12.000000/9.000000:n/0/0.000000/4.000000/2.000000/16.000000:d/0/0.000000/4.000000/2.000000/6.000000:w/0/0.000000/4.000000/2.000000/16.000000:e/0/0.000000/4.000000/2.000000/16.000000:s/0/0.000000/4.000000/2.000000/16.000000:R/180/0/0 diff --git a/DynmapCore/src/main/resources/texture_1.txt b/DynmapCore/src/main/resources/texture_1.txt index 534da2eb..7e092c41 100644 --- a/DynmapCore/src/main/resources/texture_1.txt +++ b/DynmapCore/src/main/resources/texture_1.txt @@ -3201,8 +3201,6 @@ block:id=%attached_melon_stem,patch0=0:melon_stem,patch1=0:attached_melon_stem,b block:id=%pumpkin_stem,patch0=0:pumpkin_stem,blockcolor=foliagebiome,transparency=TRANSPARENT,stdrot=true # Melon stem block:id=%melon_stem,patch0=0:melon_stem,blockcolor=foliagebiome,transparency=TRANSPARENT,stdrot=true -<<<<<<< HEAD -======= [1.19-]texture:id=mangrove_planks,filename=assets/minecraft/textures/block/mangrove_planks.png,xcount=1,ycount=1 [1.19-]texture:id=mangrove_propagule_hanging,filename=assets/minecraft/textures/block/mangrove_propagule_hanging.png,xcount=1,ycount=1 @@ -3735,4 +3733,3 @@ block:id=%melon_stem,patch0=0:melon_stem,blockcolor=foliagebiome,transparency=TR [1.19-]block:id=%lightning_rod,state=powered:false/facing:up,patch0=0:lightning_rod,transparency=SEMITRANSPARENT,stdrot=true [1.19-]block:id=%lightning_rod,state=powered:true/facing:down,patch0=0:lightning_rod_on,transparency=SEMITRANSPARENT,stdrot=true [1.19-]block:id=%lightning_rod,state=powered:false/facing:down,patch0=0:lightning_rod,transparency=SEMITRANSPARENT,stdrot=true ->>>>>>> aae2146b (fixed mud_bricks sides so it is no longer transparent. thanks BlargCraft on Reddit.) diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/frogspawn.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/frogspawn.png new file mode 100644 index 00000000..22890540 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/frogspawn.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_door_bottom.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_door_bottom.png new file mode 100644 index 00000000..d8ab5639 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_door_bottom.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_door_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_door_top.png new file mode 100644 index 00000000..900efe16 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_door_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_leaves.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_leaves.png new file mode 100644 index 00000000..ec40483b Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_leaves.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_log.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_log.png new file mode 100644 index 00000000..149d8339 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_log.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_log_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_log_top.png new file mode 100644 index 00000000..f2c4d64f Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_log_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_planks.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_planks.png new file mode 100644 index 00000000..a18b4db6 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_planks.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_propagule.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_propagule.png new file mode 100644 index 00000000..6226526a Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_propagule.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_propagule_hanging.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_propagule_hanging.png new file mode 100644 index 00000000..1ebc3639 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_propagule_hanging.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_roots_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_roots_side.png new file mode 100644 index 00000000..658754e4 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_roots_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_roots_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_roots_top.png new file mode 100644 index 00000000..5e2eb107 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_roots_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_trapdoor.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_trapdoor.png new file mode 100644 index 00000000..4bd2fb78 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mangrove_trapdoor.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mud.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mud.png new file mode 100644 index 00000000..104d291a Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mud.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mud_bricks.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mud_bricks.png new file mode 100644 index 00000000..f138a525 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/mud_bricks.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/muddy_mangrove_roots_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/muddy_mangrove_roots_side.png new file mode 100644 index 00000000..94367143 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/muddy_mangrove_roots_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/muddy_mangrove_roots_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/muddy_mangrove_roots_top.png new file mode 100644 index 00000000..dbbe6b0e Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/muddy_mangrove_roots_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/ochre_froglight_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/ochre_froglight_side.png new file mode 100644 index 00000000..c914bb83 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/ochre_froglight_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/ochre_froglight_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/ochre_froglight_top.png new file mode 100644 index 00000000..68cfb425 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/ochre_froglight_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/packed_mud.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/packed_mud.png new file mode 100644 index 00000000..c1539af5 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/packed_mud.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/pearlescent_froglight_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/pearlescent_froglight_side.png new file mode 100644 index 00000000..8f15ce6a Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/pearlescent_froglight_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/pearlescent_froglight_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/pearlescent_froglight_top.png new file mode 100644 index 00000000..c8cb0511 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/pearlescent_froglight_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_bottom.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_bottom.png new file mode 100644 index 00000000..81984069 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_bottom.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_side.png new file mode 100644 index 00000000..03edd003 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_top.png new file mode 100644 index 00000000..bee3fab0 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/reinforced_deepslate_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk.png new file mode 100644 index 00000000..fef9d976 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk.png.mcmeta b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk.png.mcmeta new file mode 100644 index 00000000..c248f23f --- /dev/null +++ b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "frametime": 20, + "interpolate": true + } +} diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_bottom.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_bottom.png new file mode 100644 index 00000000..3db8e1f1 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_bottom.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side.png new file mode 100644 index 00000000..1225e965 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side_bloom.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side_bloom.png new file mode 100644 index 00000000..dd9dda20 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side_bloom.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side_bloom.png.mcmeta b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side_bloom.png.mcmeta new file mode 100644 index 00000000..d1cd0799 --- /dev/null +++ b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_side_bloom.png.mcmeta @@ -0,0 +1,5 @@ +{ + "animation": { + "frametime": 1 + } +} diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top.png new file mode 100644 index 00000000..00ee9fad Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top_bloom.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top_bloom.png new file mode 100644 index 00000000..d59da0e2 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top_bloom.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top_bloom.png.mcmeta b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top_bloom.png.mcmeta new file mode 100644 index 00000000..d1cd0799 --- /dev/null +++ b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_catalyst_top_bloom.png.mcmeta @@ -0,0 +1,5 @@ +{ + "animation": { + "frametime": 1 + } +} diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_bottom.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_bottom.png new file mode 100644 index 00000000..a3bf18fe Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_bottom.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_can_summon_inner_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_can_summon_inner_top.png new file mode 100644 index 00000000..7109feb5 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_can_summon_inner_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_can_summon_inner_top.png.mcmeta b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_can_summon_inner_top.png.mcmeta new file mode 100644 index 00000000..8d50e3a0 --- /dev/null +++ b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_can_summon_inner_top.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "frametime": 3, + "interpolate": true + } +} diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_inner_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_inner_top.png new file mode 100644 index 00000000..5c597e48 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_inner_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_inner_top.png.mcmeta b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_inner_top.png.mcmeta new file mode 100644 index 00000000..304ede5f --- /dev/null +++ b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_inner_top.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "frametime": 6, + "interpolate": true + } +} diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_side.png new file mode 100644 index 00000000..f6848107 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_top.png new file mode 100644 index 00000000..9532e850 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_shrieker_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_vein.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_vein.png new file mode 100644 index 00000000..a3eec529 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_vein.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_vein.png.mcmeta b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_vein.png.mcmeta new file mode 100644 index 00000000..c248f23f --- /dev/null +++ b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/sculk_vein.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "frametime": 20, + "interpolate": true + } +} diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/stripped_mangrove_log.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/stripped_mangrove_log.png new file mode 100644 index 00000000..7ff56699 Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/stripped_mangrove_log.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/stripped_mangrove_log_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/stripped_mangrove_log_top.png new file mode 100644 index 00000000..9d05e81c Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/stripped_mangrove_log_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/verdant_froglight_side.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/verdant_froglight_side.png new file mode 100644 index 00000000..5f18747c Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/verdant_froglight_side.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/verdant_froglight_top.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/verdant_froglight_top.png new file mode 100644 index 00000000..964bfb7b Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/block/verdant_froglight_top.png differ diff --git a/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/entity/signs/mangrove.png b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/entity/signs/mangrove.png new file mode 100644 index 00000000..c18ac85b Binary files /dev/null and b/DynmapCore/src/main/resources/texturepacks/standard/assets/minecraft/textures/entity/signs/mangrove.png differ diff --git a/build.gradle b/build.gradle index f8877613..f486652e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,16 @@ +// Workaround for Shadow not supporting Java 19 classes. +// Remove this once Shadow updates. +// See: https://github.com/johnrengelman/shadow/pull/770 +// See: https://discord.com/channels/722722769950998560/793019909055578113/978939925061857315 +buildscript { + configurations.all { + resolutionStrategy { + force("org.ow2.asm:asm:9.3") + force("org.ow2.asm:asm-commons:9.3") + } + } +} + plugins { id "com.github.johnrengelman.shadow" version "7.1.0" id 'java' @@ -25,7 +38,7 @@ allprojects { apply plugin: 'java' group = 'us.dynmap' - version = '3.4-SNAPSHOT' + version = '3.4-beta-4' } diff --git a/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java b/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java index e493ec2d..bd00dd00 100644 --- a/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java +++ b/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java @@ -37,11 +37,13 @@ import net.minecraft.server.v1_16_R2.MinecraftServer; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116_2 extends BukkitVersionHelperGeneric { + private final boolean unsafeAsync; private Field watercolorfield; public BukkitVersionHelperSpigot116_2() { Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); + this.unsafeAsync = true; } /** @@ -69,6 +71,12 @@ public class BukkitVersionHelperSpigot116_2 extends BukkitVersionHelperGeneric { } private Object[] biomelist; + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + /** * Get list of defined biomebase objects */ diff --git a/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java b/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java index ae63946c..531503e5 100644 --- a/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java +++ b/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java @@ -37,11 +37,13 @@ import net.minecraft.server.v1_16_R2.BlockPosition; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116_3 extends BukkitVersionHelperGeneric { + private final boolean unsafeAsync; private Field watercolorfield; public BukkitVersionHelperSpigot116_3() { - Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); - watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); + Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); + watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); + this.unsafeAsync = true; } /** @@ -69,6 +71,12 @@ public class BukkitVersionHelperSpigot116_3 extends BukkitVersionHelperGeneric { } private Object[] biomelist; + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + /** * Get list of defined biomebase objects */ diff --git a/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java b/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java index 32ae9698..26b87b2f 100644 --- a/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java +++ b/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java @@ -26,11 +26,13 @@ import java.util.List; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116_4 extends BukkitVersionHelperGeneric { + private final boolean unsafeAsync; private Field watercolorfield; public BukkitVersionHelperSpigot116_4() { Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); + this.unsafeAsync = true; } /** @@ -58,7 +60,13 @@ public class BukkitVersionHelperSpigot116_4 extends BukkitVersionHelperGeneric { } private Object[] biomelist; - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get list of defined biomebase objects */ @Override diff --git a/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java b/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java index c522d77d..fb9f2a10 100644 --- a/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java +++ b/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java @@ -36,11 +36,13 @@ import net.minecraft.server.v1_16_R1.BlockPosition; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116 extends BukkitVersionHelperGeneric { + private final boolean unsafeAsync; private Field watercolorfield; public BukkitVersionHelperSpigot116() { Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); + this.unsafeAsync = true; } /** @@ -59,7 +61,13 @@ public class BukkitVersionHelperSpigot116 extends BukkitVersionHelperGeneric { } private Object[] biomelist; - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get list of defined biomebase objects */ @Override diff --git a/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java b/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java index 96116e88..f5a9b076 100644 --- a/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java +++ b/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java @@ -58,11 +58,19 @@ import java.util.Map; /** * Helper for isolation of bukkit version specific issues */ -public class BukkitVersionHelperSpigot117 extends BukkitVersionHelper { +public class BukkitVersionHelperSpigot117 extends BukkitVersionHelper { + private final boolean unsafeAsync; + public BukkitVersionHelperSpigot117() { + this.unsafeAsync = true; } - - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get block short name list */ @Override diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java new file mode 100644 index 00000000..9c21562d --- /dev/null +++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java @@ -0,0 +1,60 @@ +package org.dynmap.bukkit.helper.v118_2; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.level.WorldServer; + +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.function.Consumer; +import java.util.function.Predicate; + +/** + * The provider used to work with paper libs + * Because paper libs need java 17 we can't interact with them directly + */ +public class AsyncChunkProvider118_2 { + private final Thread ioThread; + private final Method getChunk; + private final Predicate ifFailed; + AsyncChunkProvider118_2 () { + try { + Predicate ifFailed1 = null; + Method getChunk1 = null; + Thread ioThread1 = null; + try { + Class threadClass = Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + Class[] classes = threadClass.getClasses(); + Class holder = Arrays.stream(classes).filter(aClass -> aClass.getSimpleName().equals("Holder")).findAny().orElseThrow(RuntimeException::new); + ioThread1 = (Thread) holder.getField("INSTANCE").get(null); + getChunk1 = threadClass.getMethod("loadChunkDataAsync", WorldServer.class, int.class, int.class, int.class, Consumer.class, boolean.class, boolean.class, boolean.class); + NBTTagCompound failure = (NBTTagCompound) threadClass.getField("FAILURE_VALUE").get(null); + ifFailed1 = nbtTagCompound -> nbtTagCompound == failure; + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + e.printStackTrace(); + } + ifFailed = Objects.requireNonNull(ifFailed1); + getChunk = Objects.requireNonNull(getChunk1); + ioThread = Objects.requireNonNull(ioThread1); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + public CompletableFuture getChunk(WorldServer world, int x, int y) throws InvocationTargetException, IllegalAccessException { + CompletableFuture future = new CompletableFuture<>(); + getChunk.invoke(ioThread,world,x,y,5,(Consumer) future::complete, false, true, true); + return future.thenApply((resultFuture) -> { + if (resultFuture == null) return null; + try { + NBTTagCompound compound = (NBTTagCompound) resultFuture.getClass().getField("chunkData").get(resultFuture); + return ifFailed.test(compound) ? null : compound; + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } + return null; + }); + } +} diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java index 83c80870..fed30017 100644 --- a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java +++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java @@ -64,12 +64,25 @@ import java.util.Set; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot118_2 extends BukkitVersionHelper { - + private final boolean unsafeAsync; + public BukkitVersionHelperSpigot118_2() { - + boolean unsafeAsync1; + try { + Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + unsafeAsync1 = false; + } catch (ClassNotFoundException e) { + unsafeAsync1 = true; + } + this.unsafeAsync = unsafeAsync1; } - - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get block short name list */ @Override diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java index 2e8b1972..3971de10 100644 --- a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java +++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java @@ -1,8 +1,11 @@ package org.dynmap.bukkit.helper.v118_2; +import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.craftbukkit.v1_18_R2.CraftServer; import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; import org.dynmap.DynmapChunk; +import org.dynmap.bukkit.helper.BukkitVersionHelper; import org.dynmap.bukkit.helper.BukkitWorld; import org.dynmap.common.chunk.GenericChunk; import org.dynmap.common.chunk.GenericChunkCache; @@ -14,38 +17,68 @@ import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; import net.minecraft.world.level.chunk.Chunk; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; /** * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread */ public class MapChunkCache118_2 extends GenericMapChunkCache { - private World w; - /** - * Construct empty cache - */ - public MapChunkCache118_2(GenericChunkCache cc) { - super(cc); - } + private static final AsyncChunkProvider118_2 provider = BukkitVersionHelper.helper.isUnsafeAsync() ? null : new AsyncChunkProvider118_2(); + private World w; + /** + * Construct empty cache + */ + public MapChunkCache118_2(GenericChunkCache cc) { + super(cc); + } - // Load generic chunk from existing and already loaded chunk - protected GenericChunk getLoadedChunk(DynmapChunk chunk) { - CraftWorld cw = (CraftWorld) w; - NBTTagCompound nbt = null; - GenericChunk gc = null; - if (cw.isChunkLoaded(chunk.x, chunk.z)) { - Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); - if ((c != null) && c.o) { // c.loaded - nbt = ChunkRegionLoader.a(cw.getHandle(), c); - } - if (nbt != null) { - gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); - } + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + return getLoadedChunk(chunk, false).get(); + } + @Override + protected Supplier getLoadedChunkAsync(DynmapChunk ch) { + return getLoadedChunk(ch, true); + } + + @Override + protected Supplier loadChunkAsync(DynmapChunk chunk){ + try { + CompletableFuture nbt = provider.getChunk(((CraftWorld) w).getHandle(), chunk.x, chunk.z); + return () -> { + NBTTagCompound compound = nbt.join(); + return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound)); + }; + } catch (InvocationTargetException | IllegalAccessException ignored) { + return () -> null; } - return gc; - } - // Load generic chunk from unloaded chunk - protected GenericChunk loadChunk(DynmapChunk chunk) { + } + + private Supplier getLoadedChunk(DynmapChunk chunk, boolean async) { + CraftWorld cw = (CraftWorld) w; + if (!cw.isChunkLoaded(chunk.x, chunk.z)) return () -> null; + Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); //already safe async on vanilla + if ((c == null) || c.o) return () -> null; // c.loaded + if (async) { //the data of the chunk may change while we write, better to write it sync + CompletableFuture nbt = CompletableFuture.supplyAsync(() -> ChunkRegionLoader.a(cw.getHandle(), c), ((CraftServer) Bukkit.getServer()).getServer()); + return () -> { + NBTTagCompound compound = nbt.join(); + return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound)); + }; + } else { + NBTTagCompound nbt = ChunkRegionLoader.a(cw.getHandle(), c); + GenericChunk genericChunk; + if (nbt != null) genericChunk = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + else genericChunk = null; + return () -> genericChunk; + } + + } + // Load generic chunk from unloaded chunk + protected GenericChunk loadChunk(DynmapChunk chunk) { CraftWorld cw = (CraftWorld) w; NBTTagCompound nbt = null; ChunkCoordIntPair cc = new ChunkCoordIntPair(chunk.x, chunk.z); @@ -55,13 +88,13 @@ public class MapChunkCache118_2 extends GenericMapChunkCache { } catch (IOException iox) { } if (nbt != null) { - gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); } - return gc; - } + return gc; + } - public void setChunks(BukkitWorld dw, List chunks) { - this.w = dw.getWorld(); - super.setChunks(dw, chunks); - } + public void setChunks(BukkitWorld dw, List chunks) { + this.w = dw.getWorld(); + super.setChunks(dw, chunks); + } } 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 index d5381c60..fbacfa6d 100644 --- 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 @@ -63,12 +63,18 @@ import java.util.Set; /** * Helper for isolation of bukkit version specific issues */ -public class BukkitVersionHelperSpigot118 extends BukkitVersionHelper { - +public class BukkitVersionHelperSpigot118 extends BukkitVersionHelper { + private final boolean unsafeAsync; + public BukkitVersionHelperSpigot118() { - + this.unsafeAsync = true; } - + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + /** * Get block short name list */ diff --git a/bukkit-helper-119/.gitignore b/bukkit-helper-119/.gitignore new file mode 100644 index 00000000..84c048a7 --- /dev/null +++ b/bukkit-helper-119/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/bukkit-helper-119/bin/.gitignore b/bukkit-helper-119/bin/.gitignore new file mode 100644 index 00000000..ddf9c656 --- /dev/null +++ b/bukkit-helper-119/bin/.gitignore @@ -0,0 +1 @@ +/main/ diff --git a/bukkit-helper-119/build.gradle b/bukkit-helper-119/build.gradle new file mode 100644 index 00000000..74aa29e4 --- /dev/null +++ b/bukkit-helper-119/build.gradle @@ -0,0 +1,15 @@ +eclipse { + project { + name = "Dynmap(Spigot-1.19)" + } +} + +description = 'bukkit-helper-1.19' + +dependencies { + implementation project(':bukkit-helper') + implementation project(':dynmap-api') + implementation project(path: ':DynmapCore', configuration: 'shadow') + implementation group: 'org.spigotmc', name: 'spigot-api', version:'1.19-R0.1-SNAPSHOT' + implementation group: 'org.spigotmc', name: 'spigot', version:'1.19-R0.1-SNAPSHOT' +} diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java new file mode 100644 index 00000000..7438575c --- /dev/null +++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java @@ -0,0 +1,60 @@ +package org.dynmap.bukkit.helper.v119; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.level.WorldServer; + +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.function.Consumer; +import java.util.function.Predicate; + +/** + * The provider used to work with paper libs + * Because paper libs need java 17 we can't interact with them directly + */ +public class AsyncChunkProvider119 { + private final Thread ioThread; + private final Method getChunk; + private final Predicate ifFailed; + AsyncChunkProvider119 () { + try { + Predicate ifFailed1 = null; + Method getChunk1 = null; + Thread ioThread1 = null; + try { + Class threadClass = Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + Class[] classes = threadClass.getClasses(); + Class holder = Arrays.stream(classes).filter(aClass -> aClass.getSimpleName().equals("Holder")).findAny().orElseThrow(RuntimeException::new); + ioThread1 = (Thread) holder.getField("INSTANCE").get(null); + getChunk1 = threadClass.getMethod("loadChunkDataAsync", WorldServer.class, int.class, int.class, int.class, Consumer.class, boolean.class, boolean.class, boolean.class); + NBTTagCompound failure = (NBTTagCompound) threadClass.getField("FAILURE_VALUE").get(null); + ifFailed1 = nbtTagCompound -> nbtTagCompound == failure; + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + e.printStackTrace(); + } + ifFailed = Objects.requireNonNull(ifFailed1); + getChunk = Objects.requireNonNull(getChunk1); + ioThread = Objects.requireNonNull(ioThread1); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + public CompletableFuture getChunk(WorldServer world, int x, int y) throws InvocationTargetException, IllegalAccessException { + CompletableFuture future = new CompletableFuture<>(); + getChunk.invoke(ioThread,world,x,y,5,(Consumer) future::complete, false, true, true); + return future.thenApply((resultFuture) -> { + if (resultFuture == null) return null; + try { + NBTTagCompound compound = (NBTTagCompound) resultFuture.getClass().getField("chunkData").get(resultFuture); + return ifFailed.test(compound) ? null : compound; + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } + return null; + }); + } +} diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/BukkitVersionHelperSpigot119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/BukkitVersionHelperSpigot119.java new file mode 100644 index 00000000..d4d5daf9 --- /dev/null +++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/BukkitVersionHelperSpigot119.java @@ -0,0 +1,456 @@ +package org.dynmap.bukkit.helper.v119; + +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_19_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_19_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.RegistryBlocks; +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.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 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; +import java.util.Set; + + +/** + * Helper for isolation of bukkit version specific issues + */ +public class BukkitVersionHelperSpigot119 extends BukkitVersionHelper { + private final boolean unsafeAsync; + + public BukkitVersionHelperSpigot119() { + boolean unsafeAsync1; + try { + Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + 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.o; + 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 = RegistryBlocks.V.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().aX().d(IRegistry.aR); + } + 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.o; + 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 = RegistryBlocks.V.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); + } + net.minecraft.world.level.material.Material mat = bd.d(); + + int lightAtten = b.g(bd, BlockAccessAir.a, BlockPosition.b); // getLightBlock + //Log.info("statename=" + bname + "[" + sb + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(lastbs).setStateIndex(idx).setBlockName(bname).setStateName(sb).setMaterial(mat.toString()).setAttenuatesLight(lightAtten); + if (mat.b()) { bld.setSolid(); } + if (mat == net.minecraft.world.level.material.Material.a) { bld.setAir(); } + if (mat == net.minecraft.world.level.material.Material.z) { bld.setLog(); } + if (mat == net.minecraft.world.level.material.Material.F) { bld.setLeaves(); } + if ((!bd.p().c()) && ((bd.b() instanceof BlockFluids) == false)) { // Test if fluid type for block is not empty + bld.setWaterlogged(); + } + 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) { + MapChunkCache119 c = new MapChunkCache119(gencache); + c.setChunks(dw, chunks); + return c; + } + + /** + * Get biome base water multiplier + */ + @Override + public int getBiomeBaseWaterMult(Object bb) { + BiomeBase biome = (BiomeBase) bb; + return biome.k(); // waterColor + } + + /** Get temperature from biomebase */ + @Override + public float getBiomeBaseTemperature(Object bb) { + return ((BiomeBase)bb).i(); + } + + /** Get humidity from biomebase */ + @Override + public float getBiomeBaseHumidity(Object bb) { + return ((BiomeBase)bb).h(); + } + + @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().u(); + } + + @Override + public Map getTileEntitiesForChunk(Chunk c) { + return ((CraftChunk)c).getHandle().i; + } + + @Override + public int getTileEntityX(Object te) { + TileEntity tileent = (TileEntity) te; + return tileent.p().u(); + } + + @Override + public int getTileEntityY(Object te) { + TileEntity tileent = (TileEntity) te; + return tileent.p().v(); + } + + @Override + public int getTileEntityZ(Object te) { + TileEntity tileent = (TileEntity) te; + return tileent.p().w(); + } + + @Override + public Object readTileEntityNBT(Object te) { + TileEntity tileent = (TileEntity) te; + NBTTagCompound nbt = tileent.n(); + 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).e_(); + } + 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.getValue(); + 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-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java new file mode 100644 index 00000000..4743e94f --- /dev/null +++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java @@ -0,0 +1,103 @@ +package org.dynmap.bukkit.helper.v119; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R1.CraftServer; +import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; +import org.dynmap.DynmapChunk; +import org.dynmap.bukkit.helper.BukkitVersionHelper; +import org.dynmap.bukkit.helper.BukkitWorld; +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.io.IOException; +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.function.Supplier; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class MapChunkCache119 extends GenericMapChunkCache { + private static final AsyncChunkProvider119 provider = BukkitVersionHelper.helper.isUnsafeAsync() ? null : new AsyncChunkProvider119(); + private World w; + /** + * Construct empty cache + */ + public MapChunkCache119(GenericChunkCache cc) { + super(cc); + } + + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + return getLoadedChunk(chunk, false).get(); + } + @Override + protected Supplier getLoadedChunkAsync(DynmapChunk ch) { + return getLoadedChunk(ch, true); + } + + @Override + protected Supplier loadChunkAsync(DynmapChunk chunk){ + try { + CompletableFuture nbt = provider.getChunk(((CraftWorld) w).getHandle(), chunk.x, chunk.z); + return () -> { + NBTTagCompound compound = nbt.join(); + return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound)); + }; + } catch (InvocationTargetException | IllegalAccessException ignored) { + return () -> null; + } + } + + private Supplier getLoadedChunk(DynmapChunk chunk, boolean async) { + CraftWorld cw = (CraftWorld) w; + if (!cw.isChunkLoaded(chunk.x, chunk.z)) return () -> null; + Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); //already safe async on vanilla + if ((c == null) || c.o) return () -> null; // c.loaded + if (async) { //the data of the chunk may change while we write, better to write it sync + CompletableFuture nbt = CompletableFuture.supplyAsync(() -> ChunkRegionLoader.a(cw.getHandle(), c), ((CraftServer) Bukkit.getServer()).getServer()); + return () -> { + NBTTagCompound compound = nbt.join(); + return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound)); + }; + } else { + NBTTagCompound nbt = ChunkRegionLoader.a(cw.getHandle(), c); + GenericChunk genericChunk; + if (nbt != null) genericChunk = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + else genericChunk = null; + return () -> genericChunk; + } + + } + // Load generic chunk from unloaded chunk + 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().k().a.f(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); + } +} diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/NBT.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/NBT.java new file mode 100644 index 00000000..14503faf --- /dev/null +++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/NBT.java @@ -0,0 +1,126 @@ +package org.dynmap.bukkit.helper.v119; + +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.d(); + } + @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).e_(); + } + @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/bukkit-helper/.settings/org.eclipse.jdt.core.prefs b/bukkit-helper/.settings/org.eclipse.jdt.core.prefs index 34340ad4..ed0a92f7 100644 --- a/bukkit-helper/.settings/org.eclipse.jdt.core.prefs +++ b/bukkit-helper/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Tue Mar 29 22:17:34 CDT 2022 +#Tue Jun 07 20:25:33 CDT 2022 org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 diff --git a/bukkit-helper/build.gradle b/bukkit-helper/build.gradle index b8b14737..83ece802 100644 --- a/bukkit-helper/build.gradle +++ b/bukkit-helper/build.gradle @@ -12,5 +12,5 @@ dependencies { implementation project(':dynmap-api') implementation project(path: ':DynmapCore', configuration: 'shadow') implementation group: 'org.bukkit', name: 'bukkit', version:'1.7.10-R0.1-SNAPSHOT' - implementation group: 'com.google.code.gson', name: 'gson', version:'2.8.2' + implementation group: 'com.google.code.gson', name: 'gson', version:'2.8.9' } diff --git a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java index 8724eac8..58cdb90d 100644 --- a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java +++ b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java @@ -34,6 +34,10 @@ public abstract class BukkitVersionHelper { protected BukkitVersionHelper() { } + /** + * Get if it's unsafe to load chunks async + */ + public abstract boolean isUnsafeAsync(); /** * Get list of defined biomebase objects */ diff --git a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java index 05da8fbe..d9ddd264 100644 --- a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java +++ b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java @@ -54,6 +54,12 @@ public class BukkitVersionHelperCB extends BukkitVersionHelperGeneric { isBadUnload = HDBlockModels.checkVersionRange(mcver, "1.9-"); Log.verboseinfo("MCVER=" + mcver + ", isBadUnload=" + isBadUnload); } + + @Override + public boolean isUnsafeAsync() { + return true; + } + @Override protected String getNMSPackage() { Server srv = Bukkit.getServer(); diff --git a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java index 2629abc0..92629236 100644 --- a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java +++ b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java @@ -32,7 +32,12 @@ public class BukkitVersionHelperGlowstone extends BukkitVersionHelper { throw new IllegalArgumentException("Error initializing dynmap - Glowstone version incompatible!"); } } - + + @Override + public boolean isUnsafeAsync() { + return true; + } + @Override public Object[] getBiomeBaseList() { return new Object[0]; diff --git a/fabric-1.14.4/src/main/resources/configuration.txt b/fabric-1.14.4/src/main/resources/configuration.txt index 9889eaa3..cd925d90 100644 --- a/fabric-1.14.4/src/main/resources/configuration.txt +++ b/fabric-1.14.4/src/main/resources/configuration.txt @@ -470,7 +470,10 @@ soft-ref-cache: true # Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/fabric-1.15.2/src/main/resources/configuration.txt b/fabric-1.15.2/src/main/resources/configuration.txt index fbf2a220..cd925d90 100644 --- a/fabric-1.15.2/src/main/resources/configuration.txt +++ b/fabric-1.15.2/src/main/resources/configuration.txt @@ -471,6 +471,9 @@ soft-ref-cache: true # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/FabricServer.java b/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/FabricServer.java index 9c0844f3..1a4e1ef3 100644 --- a/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/FabricServer.java +++ b/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/FabricServer.java @@ -27,6 +27,7 @@ import org.dynmap.common.DynmapListenerManager; import org.dynmap.common.DynmapPlayer; import org.dynmap.common.DynmapServerInterface; import org.dynmap.fabric_1_16_4.event.ServerChatEvents; +import org.dynmap.fabric_1_16_4.event.BlockEvents; import org.dynmap.utils.MapChunkCache; import org.dynmap.utils.VisibilityLimit; @@ -283,6 +284,10 @@ public class FabricServer extends DynmapServerInterface { } }, DynmapPlugin.this); */ + BlockEvents.SIGN_CHANGE_EVENT.register((world, pos, lines, material, player) -> { + plugin.core.processSignChange("fabric", FabricWorld.getWorldName(plugin, world), + pos.getX(), pos.getY(), pos.getZ(), lines, player.getName().asString()); + }); break; default: diff --git a/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/event/BlockEvents.java b/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/event/BlockEvents.java index 564f2587..b937f894 100644 --- a/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/event/BlockEvents.java +++ b/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/event/BlockEvents.java @@ -5,6 +5,11 @@ import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.block.Material; +import net.minecraft.server.network.ServerPlayerEntity; + public class BlockEvents { private BlockEvents() { } @@ -17,7 +22,19 @@ public class BlockEvents { } ); + public static Event SIGN_CHANGE_EVENT = EventFactory.createArrayBacked(SignChangeCallback.class, + (listeners) -> (world, pos, lines, material, player) -> { + for (SignChangeCallback callback : listeners) { + callback.onSignChange(world, pos, lines, material, player); + } + } + ); + public interface BlockCallback { void onBlockEvent(World world, BlockPos pos); } + + public interface SignChangeCallback { + void onSignChange(ServerWorld world, BlockPos pos, String[] lines, Material material, ServerPlayerEntity player); + } } diff --git a/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/mixin/ServerPlayNetworkHandlerMixin.java b/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/mixin/ServerPlayNetworkHandlerMixin.java index d103962b..d838d9b7 100644 --- a/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/mixin/ServerPlayNetworkHandlerMixin.java +++ b/fabric-1.16.4/src/main/java/org/dynmap/fabric_1_16_4/mixin/ServerPlayNetworkHandlerMixin.java @@ -2,12 +2,23 @@ package org.dynmap.fabric_1_16_4.mixin; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import net.minecraft.text.LiteralText; import org.dynmap.fabric_1_16_4.event.ServerChatEvents; +import org.dynmap.fabric_1_16_4.event.BlockEvents; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.List; @Mixin(ServerPlayNetworkHandler.class) public abstract class ServerPlayNetworkHandlerMixin { @@ -25,4 +36,29 @@ public abstract class ServerPlayNetworkHandlerMixin { public void onGameMessage(String string, CallbackInfo info) { ServerChatEvents.EVENT.invoker().onChatMessage(player, string); } + + @Inject( + method = "method_31282", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/block/entity/SignBlockEntity;markDirty()V", + shift = At.Shift.BEFORE + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + public void onSignUpdate(UpdateSignC2SPacket packet, List signText, CallbackInfo info, + ServerWorld serverWorld, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity, SignBlockEntity signBlockEntity) + { + // Pull the raw text from the input. + String[] rawTexts = new String[4]; + for (int i=0; i iter = bsids.iterator(); - DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); while (iter.hasNext()) { BlockState bs = iter.next(); int idx = bsids.getRawId(bs); if (idx >= stateByID.length) { int plen = stateByID.length; - stateByID = Arrays.copyOf(stateByID, idx*11/10); // grow array by 10% + stateByID = Arrays.copyOf(stateByID, idx * 11 / 10); // grow array by 10% Arrays.fill(stateByID, plen, stateByID.length, DynmapBlockState.AIR); } Block b = bs.getBlock(); @@ -173,16 +172,26 @@ public class DynmapPlugin { //Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); // Fill in base attributes bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename).setMaterial(mat.toString()).setLegacyBlockID(idx).setAttenuatesLight(lightAtten); - if (mat.isSolid()) { bld.setSolid(); } - if (mat == Material.AIR) { bld.setAir(); } - if (mat == Material.WOOD) { bld.setLog(); } - if (mat == Material.LEAVES) { bld.setLeaves(); } - if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof FluidBlock)) { - bld.setWaterlogged(); - } + if (mat.isSolid()) { + bld.setSolid(); + } + if (mat == Material.AIR) { + bld.setAir(); + } + if (mat == Material.WOOD) { + bld.setLog(); + } + if (mat == Material.LEAVES) { + bld.setLeaves(); + } + if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof FluidBlock)) { + bld.setWaterlogged(); + } DynmapBlockState dbs = bld.build(); // Build state stateByID[idx] = dbs; - if (basebs == null) { basebs = dbs; } + if (basebs == null) { + basebs = dbs; + } } } // for (int gidx = 0; gidx < DynmapBlockState.getGlobalIndexMax(); gidx++) { @@ -348,18 +357,16 @@ public class DynmapPlugin { Log.verboseinfo("biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); BiomeMap bmap = BiomeMap.NULL; - if (rl != null) { // If resource location, lookup by this - bmap = BiomeMap.byBiomeResourceLocation(rl); - } - else { - bmap = BiomeMap.byBiomeID(i); + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } else { + bmap = BiomeMap.byBiomeID(i); } if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); Log.verboseinfo("Add custom biome [" + bmap.toString() + "] (" + i + ")"); cnt++; - } - else { + } else { bmap.setTemperature(tmp); bmap.setRainfall(hum); } @@ -395,11 +402,21 @@ public class DynmapPlugin { /* Set up player login/quit event handler */ registerPlayerLoginListener(); - /* Initialize permissions handler */ - permissions = FilePermissions.create(); - if (permissions == null) { - permissions = new OpPermissions(new String[]{"webchat", "marker.icons", "marker.list", "webregister", "stats", "hide.self", "show.self"}); + if (FabricLoader.getInstance().isModLoaded("luckperms")) { + Log.info("Using luckperms for access control"); + permissions = new LuckPermissions(); } + else if (FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0")) { + Log.info("Using fabric-permissions-api for access control"); + permissions = new FabricPermissions(); + } else { + /* Initialize permissions handler */ + permissions = FilePermissions.create(); + if (permissions == null) { + permissions = new OpPermissions(new String[]{"webchat", "marker.icons", "marker.list", "webregister", "stats", "hide.self", "show.self"}); + } + } + /* Get and initialize data folder */ File dataDirectory = new File("dynmap"); @@ -628,14 +645,14 @@ public class DynmapPlugin { FabricWorld fw = getWorld(world, false); ChunkPos chunkPos = chunk.getPos(); - int ymax = Integer.MIN_VALUE; - int ymin = Integer.MAX_VALUE; + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; ChunkSection[] sections = chunk.getSectionArray(); for (int i = 0; i < sections.length; i++) { if ((sections[i] != null) && (!sections[i].isEmpty())) { - int sy = sections[i].getYOffset(); - if (sy < ymin) ymin = sy; - if ((sy+16) > ymax) ymax = sy + 16; + int sy = sections[i].getYOffset(); + if (sy < ymin) ymin = sy; + if ((sy + 16) > ymax) ymax = sy + 16; } } if (ymax != Integer.MIN_VALUE) { diff --git a/fabric-1.18.2/src/main/java/org/dynmap/fabric_1_18_2/permissions/FabricPermissions.java b/fabric-1.18.2/src/main/java/org/dynmap/fabric_1_18_2/permissions/FabricPermissions.java new file mode 100644 index 00000000..c572acc4 --- /dev/null +++ b/fabric-1.18.2/src/main/java/org/dynmap/fabric_1_18_2/permissions/FabricPermissions.java @@ -0,0 +1,47 @@ +package org.dynmap.fabric_1_18_2.permissions; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.entity.player.PlayerEntity; +import org.dynmap.Log; +import org.dynmap.fabric_1_18_2.DynmapPlugin; +import org.dynmap.json.simple.parser.JSONParser; + +import java.util.Set; +import java.util.stream.Collectors; + +public class FabricPermissions implements PermissionProvider { + + private String permissionKey(String perm) { + return "dynmap." + perm; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + return perms.stream() + .filter(perm -> hasOfflinePermission(player, perm)) + .collect(Collectors.toSet()); + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player.toLowerCase()); + } + + @Override + public boolean has(PlayerEntity player, String permission) { + if (player == null) return false; + String name = player.getName().getString().toLowerCase(); + if (DynmapPlugin.plugin.isOp(name)) return true; + return Permissions.check(player, permissionKey(permission)); + } + + @Override + public boolean hasPermissionNode(PlayerEntity player, String permission) { + if (player != null) { + String name = player.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(name); + } + return false; + } + +} diff --git a/fabric-1.18.2/src/main/java/org/dynmap/fabric_1_18_2/permissions/LuckPermissions.java b/fabric-1.18.2/src/main/java/org/dynmap/fabric_1_18_2/permissions/LuckPermissions.java new file mode 100644 index 00000000..829c8a80 --- /dev/null +++ b/fabric-1.18.2/src/main/java/org/dynmap/fabric_1_18_2/permissions/LuckPermissions.java @@ -0,0 +1,102 @@ +package org.dynmap.fabric_1_18_2.permissions; + +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.cacheddata.CachedPermissionData; +import net.luckperms.api.model.user.User; +import net.luckperms.api.util.Tristate; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import org.dynmap.Log; +import org.dynmap.fabric_1_18_2.DynmapPlugin; +import org.dynmap.json.simple.JSONArray; +import org.dynmap.json.simple.JSONObject; +import org.dynmap.json.simple.parser.JSONParser; +import org.dynmap.json.simple.parser.ParseException; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public class LuckPermissions implements PermissionProvider { + + private final JSONParser parser = new JSONParser(); + private LuckPerms api = null; + + private Optional getApi() { + if (api != null) return Optional.of(api); + try { + api = LuckPermsProvider.get(); + return Optional.of(api); + } catch (Exception ex) { + Log.warning("Trying to access LuckPerms before it has loaded"); + return Optional.empty(); + } + } + + private Optional cachedUUID(String username) { + try { + BufferedReader reader = new BufferedReader(new FileReader(MinecraftServer.USER_CACHE_FILE)); + JSONArray cache = (JSONArray) parser.parse(reader); + for (Object it : cache) { + JSONObject user = (JSONObject) it; + if (user.get("name").toString().equalsIgnoreCase(username)) { + String uuid = user.get("uuid").toString(); + return Optional.of(UUID.fromString(uuid)); + } + } + + reader.close(); + } catch (IOException | ParseException ex) { + Log.warning("Unable to read usercache.json"); + } + + return Optional.empty(); + } + + private String permissionKey(String perm) { + return "dynmap." + perm; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + return perms.stream() + .filter(perm -> hasOfflinePermission(player, perm)) + .collect(Collectors.toSet()); + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + if (DynmapPlugin.plugin.isOp(player.toLowerCase())) return true; + Optional api = getApi(); + Optional uuid = cachedUUID(player); + if (!uuid.isPresent() || !api.isPresent()) return false; + User user = api.get().getUserManager().loadUser(uuid.get()).join(); + CachedPermissionData permissions = user.getCachedData().getPermissionData(); + Tristate state = permissions.checkPermission(permissionKey(perm)); + return state.asBoolean(); + } + + @Override + public boolean has(PlayerEntity player, String permission) { + if (player == null) return false; + String name = player.getName().getString().toLowerCase(); + if (DynmapPlugin.plugin.isOp(name)) return true; + return Permissions.check(player, permissionKey(permission)); + } + + @Override + public boolean hasPermissionNode(PlayerEntity player, String permission) { + if (player != null) { + String name = player.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(name); + } + return false; + } + +} diff --git a/fabric-1.18.2/src/main/resources/configuration.txt b/fabric-1.18.2/src/main/resources/configuration.txt index c0379e35..2e123c79 100644 --- a/fabric-1.18.2/src/main/resources/configuration.txt +++ b/fabric-1.18.2/src/main/resources/configuration.txt @@ -477,6 +477,9 @@ soft-ref-cache: true # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/fabric-1.18/src/main/resources/configuration.txt b/fabric-1.18/src/main/resources/configuration.txt index c0379e35..2e123c79 100644 --- a/fabric-1.18/src/main/resources/configuration.txt +++ b/fabric-1.18/src/main/resources/configuration.txt @@ -477,6 +477,9 @@ soft-ref-cache: true # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/fabric-1.19/.gitignore b/fabric-1.19/.gitignore new file mode 100644 index 00000000..8b87af68 --- /dev/null +++ b/fabric-1.19/.gitignore @@ -0,0 +1,32 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# eclipse + +*.launch + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# fabric + +run/ + +# other +*.log diff --git a/fabric-1.19/build.gradle b/fabric-1.19/build.gradle new file mode 100644 index 00000000..5d6eea4a --- /dev/null +++ b/fabric-1.19/build.gradle @@ -0,0 +1,60 @@ +plugins { + // TODO: switch to a stable Loom release once 1.19 lands + id 'fabric-loom' version '0.12-SNAPSHOT' +} + +archivesBaseName = "Dynmap" +version = parent.version +group = parent.group + +eclipse { + project { + name = "Dynmap(Fabric-1.19)" + } +} + +configurations { + shadow + implementation.extendsFrom(shadow) +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + compileOnly group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' + + shadow project(path: ':DynmapCore', configuration: 'shadow') +} + +processResources { + filesMatching('fabric.mod.json') { + expand "version": project.version + } +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +jar { + from "LICENSE" + from { + configurations.shadow.collect { it.toString().contains("guava") ? null : it.isDirectory() ? it : zipTree(it) } + } +} + +remapJar { + archiveFileName = "${archivesBaseName}-${project.version}-fabric-${project.minecraft_version}.jar" + destinationDirectory = file '../target' +} + +remapJar.doLast { + task -> + ant.checksum file: task.archivePath +} diff --git a/fabric-1.19/gradle.properties b/fabric-1.19/gradle.properties new file mode 100644 index 00000000..20c5066a --- /dev/null +++ b/fabric-1.19/gradle.properties @@ -0,0 +1,4 @@ +minecraft_version=1.19 +yarn_mappings=1.19+build.1 +loader_version=0.14.6 +fabric_version=0.55.2+1.19 diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/DynmapMod.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/DynmapMod.java new file mode 100644 index 00000000..3bcf5f32 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/DynmapMod.java @@ -0,0 +1,50 @@ +package org.dynmap.fabric_1_19; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class DynmapMod implements ModInitializer { + private static final String MODID = "dynmap"; + private static final ModContainer MOD_CONTAINER = FabricLoader.getInstance().getModContainer(MODID) + .orElseThrow(() -> new RuntimeException("Failed to get mod container: " + MODID)); + // The instance of your mod that Fabric uses. + public static DynmapMod instance; + + public static DynmapPlugin plugin; + public static File jarfile; + public static String ver; + public static boolean useforcedchunks; + + @Override + public void onInitialize() { + instance = this; + + Path path = MOD_CONTAINER.getRootPath(); + try { + jarfile = new File(DynmapCore.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + } catch (URISyntaxException e) { + Log.severe("Unable to get DynmapCore jar path", e); + } + + if (path.getFileSystem().provider().getScheme().equals("jar")) { + path = Paths.get(path.getFileSystem().toString()); + jarfile = path.toFile(); + } + + ver = MOD_CONTAINER.getMetadata().getVersion().getFriendlyString(); + + Log.setLogger(new FabricLogger()); + org.dynmap.modsupport.ModSupportImpl.init(); + + // Initialize the plugin, we will enable it fully when the server starts. + plugin = new DynmapPlugin(); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/DynmapPlugin.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/DynmapPlugin.java new file mode 100644 index 00000000..7c265749 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/DynmapPlugin.java @@ -0,0 +1,794 @@ +package org.dynmap.fabric_1_19; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.FluidBlock; +import net.minecraft.block.Material; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.network.ClientConnection; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.IdList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.EmptyBlockView; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkSection; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.WorldChunk; + +import org.dynmap.*; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapCommandSender; +import org.dynmap.common.DynmapListenerManager; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.fabric_1_19.command.DmapCommand; +import org.dynmap.fabric_1_19.command.DmarkerCommand; +import org.dynmap.fabric_1_19.command.DynmapCommand; +import org.dynmap.fabric_1_19.command.DynmapExpCommand; +import org.dynmap.fabric_1_19.event.BlockEvents; +import org.dynmap.fabric_1_19.event.CustomServerChunkEvents; +import org.dynmap.fabric_1_19.event.CustomServerLifecycleEvents; +import org.dynmap.fabric_1_19.event.PlayerEvents; +import org.dynmap.fabric_1_19.mixin.BiomeEffectsAccessor; +import org.dynmap.fabric_1_19.permissions.FilePermissions; +import org.dynmap.fabric_1_19.permissions.OpPermissions; +import org.dynmap.fabric_1_19.permissions.PermissionProvider; +import org.dynmap.permissions.PermissionsHandler; +import org.dynmap.renderer.DynmapBlockState; + +import java.io.File; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.regex.Pattern; + +public class DynmapPlugin { + // FIXME: Fix package-private fields after splitting is done + DynmapCore core; + private PermissionProvider permissions; + private boolean core_enabled; + public GenericChunkCache sscache; + public PlayerList playerList; + MapManager mapManager; + /** + * Server is set when running and unset at shutdown. + */ + private net.minecraft.server.MinecraftServer server; + public static DynmapPlugin plugin; + ChatHandler chathandler; + private HashMap sortWeights = new HashMap(); + private HashMap worlds = new HashMap(); + private WorldAccess last_world; + private FabricWorld last_fworld; + private Map players = new HashMap(); + private FabricServer fserver; + private boolean tickregistered = false; + // TPS calculator + double tps; + long lasttick; + long avgticklen; + // Per tick limit, in nsec + long perTickLimit = (50000000); // 50 ms + private boolean useSaveFolder = true; + + private static final String[] TRIGGER_DEFAULTS = {"blockupdate", "chunkpopulate", "chunkgenerate"}; + + static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); + + DynmapPlugin() { + plugin = this; + // Fabric events persist between server instances + ServerLifecycleEvents.SERVER_STARTING.register(this::serverStart); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> registerCommands(dispatcher)); + CustomServerLifecycleEvents.SERVER_STARTED_PRE_WORLD_LOAD.register(this::serverStarted); + ServerLifecycleEvents.SERVER_STOPPING.register(this::serverStop); + } + + int getSortWeight(String name) { + return sortWeights.getOrDefault(name, 0); + } + + void setSortWeight(String name, int wt) { + sortWeights.put(name, wt); + } + + void dropSortWeight(String name) { + sortWeights.remove(name); + } + + public static class BlockUpdateRec { + WorldAccess w; + String wid; + int x, y, z; + } + + ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); + + public static DynmapBlockState[] stateByID; + + /** + * Initialize block states (org.dynmap.blockstate.DynmapBlockState) + */ + public void initializeBlockStates() { + stateByID = new DynmapBlockState[512 * 32]; // Simple map - scale as needed + Arrays.fill(stateByID, DynmapBlockState.AIR); // Default to air + + IdList bsids = Block.STATE_IDS; + + DynmapBlockState basebs = null; + Block baseb = null; + int baseidx = 0; + + Iterator iter = bsids.iterator(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + int idx = bsids.getRawId(bs); + if (idx >= stateByID.length) { + int plen = stateByID.length; + stateByID = Arrays.copyOf(stateByID, idx*11/10); // grow array by 10% + Arrays.fill(stateByID, plen, stateByID.length, DynmapBlockState.AIR); + } + Block b = bs.getBlock(); + // If this is new block vs last, it's the base block state + if (b != baseb) { + basebs = null; + baseidx = idx; + baseb = b; + } + + Identifier ui = Registry.BLOCK.getId(b); + if (ui == null) { + continue; + } + String bn = ui.getNamespace() + ":" + ui.getPath(); + // Only do defined names, and not "air" + if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { + Material mat = bs.getMaterial(); + String statename = ""; + for (net.minecraft.state.property.Property p : bs.getProperties()) { + if (statename.length() > 0) { + statename += ","; + } + statename += p.getName() + "=" + bs.get(p).toString(); + } + int lightAtten = bs.isOpaqueFullCube(EmptyBlockView.INSTANCE, BlockPos.ORIGIN) ? 15 : (bs.isTranslucent(EmptyBlockView.INSTANCE, BlockPos.ORIGIN) ? 0 : 1); + //Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename).setMaterial(mat.toString()).setLegacyBlockID(idx).setAttenuatesLight(lightAtten); + if (mat.isSolid()) { bld.setSolid(); } + if (mat == Material.AIR) { bld.setAir(); } + if (mat == Material.WOOD) { bld.setLog(); } + if (mat == Material.LEAVES) { bld.setLeaves(); } + if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof FluidBlock)) { + bld.setWaterlogged(); + } + DynmapBlockState dbs = bld.build(); // Build state + stateByID[idx] = dbs; + if (basebs == null) { basebs = dbs; } + } + } +// for (int gidx = 0; gidx < DynmapBlockState.getGlobalIndexMax(); gidx++) { +// DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(gidx); +// Log.info(gidx + ":" + bs.toString() + ", gidx=" + bs.globalStateIndex + ", sidx=" + bs.stateIndex); +// } + } + + public static final Item getItemByID(int id) { + return Item.byRawId(id); + } + + public static final ClientConnection getNetworkManager(ServerPlayNetworkHandler nh) { + return nh.connection; + } + + FabricPlayer getOrAddPlayer(ServerPlayerEntity player) { + String name = player.getName().getString(); + FabricPlayer fp = players.get(name); + if (fp != null) { + fp.player = player; + } else { + fp = new FabricPlayer(this, player); + players.put(name, fp); + } + return fp; + } + + static class ChatMessage { + String message; + ServerPlayerEntity sender; + } + + ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + + public static class ChatHandler { + private final DynmapPlugin plugin; + + ChatHandler(DynmapPlugin plugin) { + this.plugin = plugin; + } + + public void handleChat(ServerPlayerEntity player, String message) { + if (!message.startsWith("/")) { + ChatMessage cm = new ChatMessage(); + cm.message = message; + cm.sender = player; + plugin.msgqueue.add(cm); + } + } + } + + public FabricServer getFabricServer() { + return fserver; + } + + private void serverStart(MinecraftServer server) { + // Set the server so we don't NPE during setup + this.server = server; + this.fserver = new FabricServer(this, server); + this.onEnable(); + } + + private void serverStarted(MinecraftServer server) { + this.onStart(); + if (core != null) { + core.serverStarted(); + } + } + + private void serverStop(MinecraftServer server) { + this.onDisable(); + this.server = null; + } + + public boolean isOp(String player) { + String[] ops = server.getPlayerManager().getOpList().getNames(); + + for (String op : ops) { + if (op.equalsIgnoreCase(player)) { + return true; + } + } + + // TODO: Consider whether cheats are enabled for integrated server + return server.isSingleplayer() && server.isHost(server.getPlayerManager().getPlayer(player).getGameProfile()); + } + + boolean hasPerm(PlayerEntity psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((ph != null) && (psender != null) && ph.hasPermission(psender.getName().getString(), permission)) { + return true; + } + return permissions.has(psender, permission); + } + + boolean hasPermNode(PlayerEntity psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((ph != null) && (psender != null) && ph.hasPermissionNode(psender.getName().getString(), permission)) { + return true; + } + return permissions.hasPermissionNode(psender, permission); + } + + Set hasOfflinePermissions(String player, Set perms) { + Set rslt = null; + PermissionsHandler ph = PermissionsHandler.getHandler(); + if (ph != null) { + rslt = ph.hasOfflinePermissions(player, perms); + } + Set rslt2 = hasOfflinePermissions(player, perms); + if ((rslt != null) && (rslt2 != null)) { + Set newrslt = new HashSet(rslt); + newrslt.addAll(rslt2); + rslt = newrslt; + } else if (rslt2 != null) { + rslt = rslt2; + } + return rslt; + } + + boolean hasOfflinePermission(String player, String perm) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if (ph != null) { + if (ph.hasOfflinePermission(player, perm)) { + return true; + } + } + return permissions.hasOfflinePermission(player, perm); + } + + void setChatHandler(ChatHandler chatHandler) { + plugin.chathandler = chatHandler; + } + + public class TexturesPayload { + public long timestamp; + public String profileId; + public String profileName; + public boolean isPublic; + public Map textures; + + } + + public class ProfileTexture { + public String url; + } + + public void loadExtraBiomes(String mcver) { + int cnt = 0; + BiomeMap.loadWellKnownByVersion(mcver); + + Registry biomeRegistry = getFabricServer().getBiomeRegistry(); + Biome[] list = getFabricServer().getBiomeList(biomeRegistry); + + for (int i = 0; i < list.length; i++) { + Biome bb = list[i]; + if (bb != null) { + String id = biomeRegistry.getId(bb).getPath(); + String rl = biomeRegistry.getId(bb).toString(); + float tmp = bb.getTemperature(), hum = bb.getDownfall(); + int watermult = ((BiomeEffectsAccessor) bb.getEffects()).getWaterColor(); + Log.verboseinfo("biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); + + BiomeMap bmap = BiomeMap.NULL; + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } + else { + bmap = BiomeMap.byBiomeID(i); + } + if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { + bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); + Log.verboseinfo("Add custom biome [" + bmap.toString() + "] (" + i + ")"); + cnt++; + } + else { + bmap.setTemperature(tmp); + bmap.setRainfall(hum); + } + if (watermult != -1) { + bmap.setWaterColorMultiplier(watermult); + Log.verboseinfo("Set watercolormult for " + bmap.toString() + " (" + i + ") to " + Integer.toHexString(watermult)); + } + } + } + if (cnt > 0) + Log.info("Added " + cnt + " custom biome mappings"); + } + + private String[] getBiomeNames() { + Registry biomeRegistry = getFabricServer().getBiomeRegistry(); + Biome[] list = getFabricServer().getBiomeList(biomeRegistry); + String[] lst = new String[list.length]; + for (int i = 0; i < list.length; i++) { + Biome bb = list[i]; + if (bb != null) { + lst[i] = biomeRegistry.getId(bb).getPath(); + } + } + return lst; + } + + public void onEnable() { + /* Get MC version */ + String mcver = server.getVersion(); + + /* Load extra biomes */ + loadExtraBiomes(mcver); + /* Set up player login/quit event handler */ + registerPlayerLoginListener(); + + /* Initialize permissions handler */ + permissions = FilePermissions.create(); + if (permissions == null) { + permissions = new OpPermissions(new String[]{"webchat", "marker.icons", "marker.list", "webregister", "stats", "hide.self", "show.self"}); + } + /* Get and initialize data folder */ + File dataDirectory = new File("dynmap"); + + if (!dataDirectory.exists()) { + dataDirectory.mkdirs(); + } + + /* Instantiate core */ + if (core == null) { + core = new DynmapCore(); + } + + /* Inject dependencies */ + core.setPluginJarFile(DynmapMod.jarfile); + core.setPluginVersion(DynmapMod.ver); + core.setMinecraftVersion(mcver); + core.setDataFolder(dataDirectory); + core.setServer(fserver); + core.setTriggerDefault(TRIGGER_DEFAULTS); + core.setBiomeNames(getBiomeNames()); + + if (!core.initConfiguration(null)) { + return; + } + // Extract default permission example, if needed + File filepermexample = new File(core.getDataFolder(), "permissions.yml.example"); + core.createDefaultFileFromResource("/permissions.yml.example", filepermexample); + + DynmapCommonAPIListener.apiInitialized(core); + } + + private DynmapCommand dynmapCmd; + private DmapCommand dmapCmd; + private DmarkerCommand dmarkerCmd; + private DynmapExpCommand dynmapexpCmd; + + public void registerCommands(CommandDispatcher cd) { + dynmapCmd = new DynmapCommand(this); + dmapCmd = new DmapCommand(this); + dmarkerCmd = new DmarkerCommand(this); + dynmapexpCmd = new DynmapExpCommand(this); + dynmapCmd.register(cd); + dmapCmd.register(cd); + dmarkerCmd.register(cd); + dynmapexpCmd.register(cd); + + Log.info("Register commands"); + } + + public void onStart() { + initializeBlockStates(); + /* Enable core */ + if (!core.enableCore(null)) { + return; + } + core_enabled = true; + VersionCheck.runCheck(core); + // Get per tick time limit + perTickLimit = core.getMaxTickUseMS() * 1000000; + // Prep TPS + lasttick = System.nanoTime(); + tps = 20.0; + + /* Register tick handler */ + if (!tickregistered) { + ServerTickEvents.END_SERVER_TICK.register(server -> fserver.tickEvent(server)); + tickregistered = true; + } + + playerList = core.playerList; + sscache = new GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); + /* Get map manager from core */ + mapManager = core.getMapManager(); + + /* Load saved world definitions */ + loadWorlds(); + + for (FabricWorld w : worlds.values()) { + if (core.processWorldLoad(w)) { /* Have core process load first - fire event listeners if good load after */ + if (w.isLoaded()) { + core.listenerManager.processWorldEvent(DynmapListenerManager.EventType.WORLD_LOAD, w); + } + } + } + core.updateConfigHashcode(); + + /* Register our update trigger events */ + registerEvents(); + Log.info("Register events"); + + //DynmapCommonAPIListener.apiInitialized(core); + + Log.info("Enabled"); + } + + public void onDisable() { + DynmapCommonAPIListener.apiTerminated(); + + //if (metrics != null) { + // metrics.stop(); + // metrics = null; + //} + /* Save worlds */ + saveWorlds(); + + /* Purge tick queue */ + fserver.clearTaskQueue(); + + /* Disable core */ + core.disableCore(); + core_enabled = false; + + if (sscache != null) { + sscache.cleanup(); + sscache = null; + } + + Log.info("Disabled"); + } + + // TODO: Clean a bit + public void handleCommand(ServerCommandSource commandSource, String cmd, String[] args) throws CommandSyntaxException { + DynmapCommandSender dsender; + ServerPlayerEntity psender = null; + + // getPlayer throws a CommandSyntaxException, so getEntity and instanceof for safety + if (commandSource.getEntity() instanceof ServerPlayerEntity) { + psender = commandSource.getPlayerOrThrow(); + } + + if (psender != null) { + // FIXME: New Player? Why not query the current player list. + dsender = new FabricPlayer(this, psender); + } else { + dsender = new FabricCommandSender(commandSource); + } + + core.processCommand(dsender, cmd, cmd, args); + } + + public class PlayerTracker { + public void onPlayerLogin(ServerPlayerEntity player) { + if (!core_enabled) return; + final DynmapPlayer dp = getOrAddPlayer(player); + /* This event can be called from off server thread, so push processing there */ + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processPlayerEvent(DynmapListenerManager.EventType.PLAYER_JOIN, dp); + } + }, 2); + } + + public void onPlayerLogout(ServerPlayerEntity player) { + if (!core_enabled) return; + final DynmapPlayer dp = getOrAddPlayer(player); + final String name = player.getName().getString(); + /* This event can be called from off server thread, so push processing there */ + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processPlayerEvent(DynmapListenerManager.EventType.PLAYER_QUIT, dp); + players.remove(name); + } + }, 0); + } + + public void onPlayerChangedDimension(ServerPlayerEntity player) { + if (!core_enabled) return; + getOrAddPlayer(player); // Freshen player object reference + } + + public void onPlayerRespawn(ServerPlayerEntity player) { + if (!core_enabled) return; + getOrAddPlayer(player); // Freshen player object reference + } + } + + private PlayerTracker playerTracker = null; + + private void registerPlayerLoginListener() { + if (playerTracker == null) { + playerTracker = new PlayerTracker(); + PlayerEvents.PLAYER_LOGGED_IN.register(player -> playerTracker.onPlayerLogin(player)); + PlayerEvents.PLAYER_LOGGED_OUT.register(player -> playerTracker.onPlayerLogout(player)); + PlayerEvents.PLAYER_CHANGED_DIMENSION.register(player -> playerTracker.onPlayerChangedDimension(player)); + PlayerEvents.PLAYER_RESPAWN.register(player -> playerTracker.onPlayerRespawn(player)); + } + } + + public class WorldTracker { + public void handleWorldLoad(MinecraftServer server, ServerWorld world) { + if (!core_enabled) return; + + final FabricWorld fw = getWorld(world); + // This event can be called from off server thread, so push processing there + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + if (core.processWorldLoad(fw)) // Have core process load first - fire event listeners if good load after + core.listenerManager.processWorldEvent(DynmapListenerManager.EventType.WORLD_LOAD, fw); + } + }, 0); + } + + public void handleWorldUnload(MinecraftServer server, ServerWorld world) { + if (!core_enabled) return; + + final FabricWorld fw = getWorld(world); + if (fw != null) { + // This event can be called from off server thread, so push processing there + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processWorldEvent(DynmapListenerManager.EventType.WORLD_UNLOAD, fw); + core.processWorldUnload(fw); + } + }, 0); + // Set world unloaded (needs to be immediate, since it may be invalid after event) + fw.setWorldUnloaded(); + // Clean up tracker + //WorldUpdateTracker wut = updateTrackers.remove(fw.getName()); + //if(wut != null) wut.world = null; + } + } + + public void handleChunkGenerate(ServerWorld world, Chunk chunk) { + if (!onchunkgenerate) return; + + FabricWorld fw = getWorld(world, false); + ChunkPos chunkPos = chunk.getPos(); + + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + ChunkSection[] sections = chunk.getSectionArray(); + for (int i = 0; i < sections.length; i++) { + if ((sections[i] != null) && (!sections[i].isEmpty())) { + int sy = sections[i].getYOffset(); + if (sy < ymin) ymin = sy; + if ((sy+16) > ymax) ymax = sy + 16; + } + } + if (ymax != Integer.MIN_VALUE) { + mapManager.touchVolume(fw.getName(), + chunkPos.getStartX(), ymin, chunkPos.getStartZ(), + chunkPos.getEndX(), ymax, chunkPos.getEndZ(), + "chunkgenerate"); + //Log.info("New generated chunk detected at %s[%s]".formatted(fw.getName(), chunkPos.getStartPos())); + } + } + + public void handleBlockEvent(World world, BlockPos pos) { + if (!core_enabled) return; + if (!onblockchange) return; + if (!(world instanceof ServerWorld)) return; + + BlockUpdateRec r = new BlockUpdateRec(); + r.w = world; + FabricWorld fw = getWorld(world, false); + if (fw == null) return; + r.wid = fw.getName(); + r.x = pos.getX(); + r.y = pos.getY(); + r.z = pos.getZ(); + blockupdatequeue.add(r); + } + } + + private WorldTracker worldTracker = null; + private boolean onblockchange = false; + private boolean onchunkpopulate = false; + private boolean onchunkgenerate = false; + boolean onblockchange_with_id = false; + + private void registerEvents() { + // To trigger rendering. + onblockchange = core.isTrigger("blockupdate"); + onchunkpopulate = core.isTrigger("chunkpopulate"); + onchunkgenerate = core.isTrigger("chunkgenerate"); + onblockchange_with_id = core.isTrigger("blockupdate-with-id"); + if (onblockchange_with_id) + onblockchange = true; + if (worldTracker == null) + worldTracker = new WorldTracker(); + if (onchunkpopulate || onchunkgenerate) { + CustomServerChunkEvents.CHUNK_GENERATE.register((world, chunk) -> worldTracker.handleChunkGenerate(world, chunk)); + } + if (onblockchange) { + BlockEvents.BLOCK_EVENT.register((world, pos) -> worldTracker.handleBlockEvent(world, pos)); + } + + ServerWorldEvents.LOAD.register((server, world) -> worldTracker.handleWorldLoad(server, world)); + ServerWorldEvents.UNLOAD.register((server, world) -> worldTracker.handleWorldUnload(server, world)); + } + + FabricWorld getWorldByName(String name) { + return worlds.get(name); + } + + FabricWorld getWorld(World w) { + return getWorld(w, true); + } + + private FabricWorld getWorld(World w, boolean add_if_not_found) { + if (last_world == w) { + return last_fworld; + } + String wname = FabricWorld.getWorldName(this, w); + + for (FabricWorld fw : worlds.values()) { + if (fw.getRawName().equals(wname)) { + last_world = w; + last_fworld = fw; + if (!fw.isLoaded()) { + fw.setWorldLoaded(w); + } + fw.updateWorld(w); + return fw; + } + } + FabricWorld fw = null; + if (add_if_not_found) { + /* Add to list if not found */ + fw = new FabricWorld(this, w); + worlds.put(fw.getName(), fw); + } + last_world = w; + last_fworld = fw; + return fw; + } + + private void saveWorlds() { + File f = new File(core.getDataFolder(), FabricWorld.SAVED_WORLDS_FILE); + ConfigurationNode cn = new ConfigurationNode(f); + ArrayList> lst = new ArrayList>(); + for (DynmapWorld fw : core.mapManager.getWorlds()) { + HashMap vals = new HashMap(); + vals.put("name", fw.getRawName()); + vals.put("height", fw.worldheight); + vals.put("miny", fw.minY); + vals.put("sealevel", fw.sealevel); + vals.put("nether", fw.isNether()); + vals.put("the_end", ((FabricWorld) fw).isTheEnd()); + vals.put("title", fw.getTitle()); + lst.add(vals); + } + cn.put("worlds", lst); + cn.put("useSaveFolderAsName", useSaveFolder); + cn.put("maxWorldHeight", FabricWorld.getMaxWorldHeight()); + + cn.save(); + } + + private void loadWorlds() { + File f = new File(core.getDataFolder(), FabricWorld.SAVED_WORLDS_FILE); + if (f.canRead() == false) { + useSaveFolder = true; + return; + } + ConfigurationNode cn = new ConfigurationNode(f); + cn.load(); + // If defined, use maxWorldHeight + FabricWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); + + // If setting defined, use it + if (cn.containsKey("useSaveFolderAsName")) { + useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); + } + List> lst = cn.getMapList("worlds"); + if (lst == null) { + Log.warning(String.format("Discarding bad %s", FabricWorld.SAVED_WORLDS_FILE)); + return; + } + + for (Map world : lst) { + try { + String name = (String) world.get("name"); + int height = (Integer) world.get("height"); + Integer miny = (Integer) world.get("miny"); + int sealevel = (Integer) world.get("sealevel"); + boolean nether = (Boolean) world.get("nether"); + boolean theend = (Boolean) world.get("the_end"); + String title = (String) world.get("title"); + if (name != null) { + FabricWorld fw = new FabricWorld(this, name, height, sealevel, nether, theend, title, (miny != null) ? miny : 0); + fw.setWorldUnloaded(); + core.processWorldLoad(fw); + worlds.put(fw.getName(), fw); + } + } catch (Exception x) { + Log.warning(String.format("Unable to load saved worlds from %s", FabricWorld.SAVED_WORLDS_FILE)); + return; + } + } + } +} \ No newline at end of file diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricAdapter.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricAdapter.java new file mode 100644 index 00000000..d10f7060 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricAdapter.java @@ -0,0 +1,13 @@ +package org.dynmap.fabric_1_19; + +import net.minecraft.server.world.ServerWorld; +import org.dynmap.DynmapLocation; + +public final class FabricAdapter { + public static DynmapLocation toDynmapLocation(DynmapPlugin plugin, ServerWorld world, double x, double y, double z) { + return new DynmapLocation(plugin.getWorld(world).getName(), x, y, z); + } + + private FabricAdapter() { + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricCommandSender.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricCommandSender.java new file mode 100644 index 00000000..b2bf4e49 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricCommandSender.java @@ -0,0 +1,47 @@ +package org.dynmap.fabric_1_19; + +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.LiteralTextContent; +import net.minecraft.text.Text; +import org.dynmap.common.DynmapCommandSender; + +/* Handler for generic console command sender */ +public class FabricCommandSender implements DynmapCommandSender { + private ServerCommandSource sender; + + protected FabricCommandSender() { + sender = null; + } + + public FabricCommandSender(ServerCommandSource send) { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) { + return true; + } + + @Override + public void sendMessage(String msg) { + if (sender != null) { + Text ichatcomponent = Text.literal(msg); + sender.sendFeedback(ichatcomponent, false); + } + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public boolean hasPermissionNode(String node) { + return true; + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricLogger.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricLogger.java new file mode 100644 index 00000000..0aeebc45 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricLogger.java @@ -0,0 +1,49 @@ +package org.dynmap.fabric_1_19; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dynmap.utils.DynmapLogger; + +public class FabricLogger implements DynmapLogger { + Logger log; + public static final String DM = "[Dynmap] "; + + FabricLogger() { + log = LogManager.getLogger("Dynmap"); + } + + @Override + public void info(String s) { + log.info(DM + s); + } + + @Override + public void severe(Throwable t) { + log.fatal(t); + } + + @Override + public void severe(String s) { + log.fatal(DM + s); + } + + @Override + public void severe(String s, Throwable t) { + log.fatal(DM + s, t); + } + + @Override + public void verboseinfo(String s) { + log.info(DM + s); + } + + @Override + public void warning(String s) { + log.warn(DM + s); + } + + @Override + public void warning(String s, Throwable t) { + log.warn(DM + s, t); + } +} \ No newline at end of file diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricMapChunkCache.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricMapChunkCache.java new file mode 100644 index 00000000..4e63d359 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricMapChunkCache.java @@ -0,0 +1,104 @@ +package org.dynmap.fabric_1_19; + +import net.minecraft.nbt.*; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import net.minecraft.util.collection.PackedIntegerArray; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.WordPackedArray; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.ChunkSerializer; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.chunk.ChunkManager; +import net.minecraft.world.chunk.ChunkStatus; + +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCore; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkSection; +import org.dynmap.common.chunk.GenericMapChunkCache; +import org.dynmap.hdmap.HDBlockModels; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.renderer.RenderPatchFactory; +import org.dynmap.utils.*; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class FabricMapChunkCache extends GenericMapChunkCache { + private World w; + private ServerChunkManager cps; + + /** + * Construct empty cache + */ + public FabricMapChunkCache(DynmapPlugin plugin) { + super(plugin.sscache); + } + + public void setChunks(FabricWorld dw, List chunks) { + this.w = dw.getWorld(); + if (dw.isLoaded()) { + /* Check if world's provider is ServerChunkManager */ + ChunkManager cp = this.w.getChunkManager(); + + if (cp instanceof ServerChunkManager) { + cps = (ServerChunkManager) cp; + } else { + Log.severe("Error: world " + dw.getName() + " has unsupported chunk provider"); + } + } + super.setChunks(dw, chunks); + } + + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + GenericChunk gc = null; + if (cps.isChunkLoaded(chunk.x, chunk.z)) { + NbtCompound nbt = null; + try { + nbt = ChunkSerializer.serialize((ServerWorld) w, cps.getWorldChunk(chunk.x, chunk.z, false)); + } catch (NullPointerException e) { + // TODO: find out why this is happening and why it only seems to happen since 1.16.2 + Log.severe("ChunkSerializer.serialize threw a NullPointerException", e); + } + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + } + return gc; + } + + private NbtCompound readChunk(int x, int z) { + try { + ThreadedAnvilChunkStorage acl = cps.threadedAnvilChunkStorage; + + ChunkPos coord = new ChunkPos(x, z); + // Async chunk reading is synchronized here. Perhaps we can do async and improve performance? + return acl.getNbt(coord).join().orElse(null); + } catch (Exception exc) { + Log.severe(String.format("Error reading chunk: %s,%d,%d", dw.getName(), x, z), exc); + return null; + } + } + + // Load generic chunk from unloaded chunk + protected GenericChunk loadChunk(DynmapChunk chunk) { + GenericChunk gc = null; + NbtCompound nbt = readChunk(chunk.x, chunk.z); + // If read was good + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricPlayer.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricPlayer.java new file mode 100644 index 00000000..83bdf97b --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricPlayer.java @@ -0,0 +1,252 @@ +package org.dynmap.fabric_1_19; + +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 net.minecraft.network.packet.s2c.play.SubtitleS2CPacket; +import net.minecraft.network.packet.s2c.play.TitleFadeS2CPacket; +import net.minecraft.network.packet.s2c.play.TitleS2CPacket; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralTextContent; +import net.minecraft.text.Text; +import net.minecraft.util.Util; +import net.minecraft.util.math.Vec3d; +import org.dynmap.DynmapLocation; +import org.dynmap.common.DynmapPlayer; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; + +/** + * Player access abstraction class + */ +public class FabricPlayer extends FabricCommandSender implements DynmapPlayer { + private static final Gson GSON = new GsonBuilder().create(); + private final DynmapPlugin plugin; + // FIXME: Proper setter + ServerPlayerEntity player; + private final String skinurl; + private final UUID uuid; + + public FabricPlayer(DynmapPlugin plugin, ServerPlayerEntity player) { + this.plugin = plugin; + this.player = player; + String url = null; + if (this.player != null) { + uuid = this.player.getUuid(); + GameProfile prof = this.player.getGameProfile(); + if (prof != null) { + Property textureProperty = Iterables.getFirst(prof.getProperties().get("textures"), null); + + if (textureProperty != null) { + DynmapPlugin.TexturesPayload result = null; + try { + String json = new String(Base64.getDecoder().decode(textureProperty.getValue()), StandardCharsets.UTF_8); + result = GSON.fromJson(json, DynmapPlugin.TexturesPayload.class); + } catch (JsonParseException e) { + } + if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { + url = result.textures.get("SKIN").url; + } + } + } + } else { + uuid = null; + } + skinurl = url; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public String getName() { + if (player != null) { + String n = player.getName().getString(); + ; + return n; + } else + return "[Server]"; + } + + @Override + public String getDisplayName() { + if (player != null) { + String n = player.getDisplayName().getString(); + return n; + } else + return "[Server]"; + } + + @Override + public boolean isOnline() { + return true; + } + + @Override + public DynmapLocation getLocation() { + if (player == null) { + return null; + } + + Vec3d pos = player.getPos(); + return FabricAdapter.toDynmapLocation(plugin, player.getWorld(), pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public String getWorld() { + if (player == null) { + return null; + } + + if (player.world != null) { + return plugin.getWorld(player.world).getName(); + } + + return null; + } + + @Override + public InetSocketAddress getAddress() { + if (player != null) { + ServerPlayNetworkHandler networkHandler = player.networkHandler; + if ((networkHandler != null) && (networkHandler.getConnection() != null)) { + SocketAddress sa = networkHandler.getConnection().getAddress(); + if (sa instanceof InetSocketAddress) { + return (InetSocketAddress) sa; + } + } + } + return null; + } + + @Override + public boolean isSneaking() { + if (player != null) { + return player.isSneaking(); + } + + return false; + } + + @Override + public double getHealth() { + if (player != null) { + double h = player.getHealth(); + if (h > 20) h = 20; + return h; // Scale to 20 range + } else { + return 0; + } + } + + @Override + public int getArmorPoints() { + if (player != null) { + return player.getArmor(); + } else { + return 0; + } + } + + @Override + public DynmapLocation getBedSpawnLocation() { + return null; + } + + @Override + public long getLastLoginTime() { + return 0; + } + + @Override + public long getFirstLoginTime() { + return 0; + } + + @Override + public boolean hasPrivilege(String privid) { + if (player != null) + return plugin.hasPerm(player, privid); + return false; + } + + @Override + public boolean isOp() { + return plugin.isOp(player.getName().getString()); + } + + @Override + public void sendMessage(String msg) { + Text ichatcomponent = Text.literal(msg); + player.sendMessage(ichatcomponent); + } + + @Override + public boolean isInvisible() { + if (player != null) { + return player.isInvisible(); + } + return false; + } + + @Override + public int getSortWeight() { + return plugin.getSortWeight(getName()); + } + + @Override + public void setSortWeight(int wt) { + if (wt == 0) { + plugin.dropSortWeight(getName()); + } else { + plugin.setSortWeight(getName(), wt); + } + } + + @Override + public boolean hasPermissionNode(String node) { + return player != null && plugin.hasPermNode(player, node); + } + + @Override + public String getSkinURL() { + return skinurl; + } + + @Override + public UUID getUUID() { + return uuid; + } + + /** + * Send title and subtitle text (called from server thread) + */ + @Override + public void sendTitleText(String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { + if (player != null) { + ServerPlayerEntity player = this.player; + TitleFadeS2CPacket times = new TitleFadeS2CPacket(fadeInTicks, stayTicks, fadeOutTicks); + player.networkHandler.sendPacket(times); + if (title != null) { + TitleS2CPacket titlepkt = new TitleS2CPacket(Text.literal(title)); + player.networkHandler.sendPacket(titlepkt); + } + + if (subtitle != null) { + SubtitleS2CPacket subtitlepkt = new SubtitleS2CPacket(Text.literal(subtitle)); + player.networkHandler.sendPacket(subtitlepkt); + } + } + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricServer.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricServer.java new file mode 100644 index 00000000..645dd540 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricServer.java @@ -0,0 +1,609 @@ +package org.dynmap.fabric_1_19; + +import com.mojang.authlib.GameProfile; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.block.AbstractSignBlock; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.network.message.MessageType; +import net.minecraft.server.BannedIpList; +import net.minecraft.server.BannedPlayerList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.LiteralTextContent; +import net.minecraft.text.Text; +import net.minecraft.util.UserCache; +import net.minecraft.util.Util; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapListenerManager; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.fabric_1_19.event.BlockEvents; +import org.dynmap.fabric_1_19.event.ServerChatEvents; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.VisibilityLimit; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +/** + * Server access abstraction class + */ +public class FabricServer extends DynmapServerInterface { + /* Server thread scheduler */ + private final Object schedlock = new Object(); + private final DynmapPlugin plugin; + private final MinecraftServer server; + private final Registry biomeRegistry; + private long cur_tick; + private long next_id; + private long cur_tick_starttime; + private PriorityQueue runqueue = new PriorityQueue(); + + public FabricServer(DynmapPlugin plugin, MinecraftServer server) { + this.plugin = plugin; + this.server = server; + this.biomeRegistry = server.getRegistryManager().get(Registry.BIOME_KEY); + } + + private Optional getProfileByName(String player) { + UserCache cache = server.getUserCache(); + return cache.findByName(player); + } + + public final Registry getBiomeRegistry() { + return biomeRegistry; + } + + private Biome[] biomelist = null; + + public final Biome[] getBiomeList(Registry biomeRegistry) { + if (biomelist == null) { + biomelist = new Biome[256]; + Iterator iter = biomeRegistry.iterator(); + while (iter.hasNext()) { + Biome b = iter.next(); + int bidx = biomeRegistry.getRawId(b); + if (bidx >= biomelist.length) { + biomelist = Arrays.copyOf(biomelist, bidx + biomelist.length); + } + biomelist[bidx] = b; + } + } + return biomelist; + } + + @Override + public int getBlockIDAt(String wname, int x, int y, int z) { + return -1; + } + + @SuppressWarnings("deprecation") /* Not much I can do... fix this if it breaks. */ + @Override + public int isSignAt(String wname, int x, int y, int z) { + World world = plugin.getWorldByName(wname).getWorld(); + + BlockPos pos = new BlockPos(x, y, z); + if (!world.isChunkLoaded(pos)) + return -1; + + Block block = world.getBlockState(pos).getBlock(); + return (block instanceof AbstractSignBlock ? 1 : 0); + } + + @Override + public void scheduleServerTask(Runnable run, long delay) { + /* Add task record to queue */ + synchronized (schedlock) { + TaskRecord tr = new TaskRecord(cur_tick + delay, next_id++, new FutureTask(run, null)); + runqueue.add(tr); + } + } + + @Override + public DynmapPlayer[] getOnlinePlayers() { + if (server.getPlayerManager() == null) return new DynmapPlayer[0]; + + List players = server.getPlayerManager().getPlayerList(); + int playerCount = players.size(); + DynmapPlayer[] dplay = new DynmapPlayer[players.size()]; + + for (int i = 0; i < playerCount; i++) { + ServerPlayerEntity player = players.get(i); + dplay[i] = plugin.getOrAddPlayer(player); + } + + return dplay; + } + + @Override + public void reload() { + plugin.onDisable(); + plugin.onEnable(); + plugin.onStart(); + } + + @Override + public DynmapPlayer getPlayer(String name) { + List players = server.getPlayerManager().getPlayerList(); + + for (ServerPlayerEntity player : players) { + + if (player.getName().getString().equalsIgnoreCase(name)) { + return plugin.getOrAddPlayer(player); + } + } + + return null; + } + + @Override + public Set getIPBans() { + BannedIpList bl = server.getPlayerManager().getIpBanList(); + Set ips = new HashSet(); + + for (String s : bl.getNames()) { + ips.add(s); + } + + return ips; + } + + @Override + public Future callSyncMethod(Callable task) { + return callSyncMethod(task, 0); + } + + public Future callSyncMethod(Callable task, long delay) { + FutureTask ft = new FutureTask(task); + + /* Add task record to queue */ + synchronized (schedlock) { + TaskRecord tr = new TaskRecord(cur_tick + delay, next_id++, ft); + runqueue.add(tr); + } + + return ft; + } + + void clearTaskQueue() { + this.runqueue.clear(); + } + + @Override + public String getServerName() { + String sn; + if (server.isSingleplayer()) + sn = "Integrated"; + else + sn = server.getServerIp(); + if (sn == null) sn = "Unknown Server"; + return sn; + } + + @Override + public boolean isPlayerBanned(String pid) { + PlayerManager scm = server.getPlayerManager(); + BannedPlayerList bl = scm.getUserBanList(); + try { + return bl.contains(getProfileByName(pid).get()); + } catch (NoSuchElementException e) { + /* If this profile doesn't exist, default to "banned" for good measure. */ + return true; + } + } + + @Override + public String stripChatColor(String s) { + return DynmapPlugin.patternControlCode.matcher(s).replaceAll(""); + } + + private Set registered = new HashSet(); + + @Override + public boolean requestEventNotification(DynmapListenerManager.EventType type) { + if (registered.contains(type)) { + return true; + } + + switch (type) { + case WORLD_LOAD: + case WORLD_UNLOAD: + /* Already called for normal world activation/deactivation */ + break; + + case WORLD_SPAWN_CHANGE: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onSpawnChange(SpawnChangeEvent evt) { + DynmapWorld w = new BukkitWorld(evt.getWorld()); + core.listenerManager.processWorldEvent(EventType.WORLD_SPAWN_CHANGE, w); + } + }, DynmapPlugin.this); + */ + break; + + case PLAYER_JOIN: + case PLAYER_QUIT: + /* Already handled */ + break; + + case PLAYER_BED_LEAVE: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onPlayerBedLeave(PlayerBedLeaveEvent evt) { + DynmapPlayer p = new BukkitPlayer(evt.getPlayer()); + core.listenerManager.processPlayerEvent(EventType.PLAYER_BED_LEAVE, p); + } + }, DynmapPlugin.this); + */ + break; + + case PLAYER_CHAT: + if (plugin.chathandler == null) { + plugin.setChatHandler(new DynmapPlugin.ChatHandler(plugin)); + ServerChatEvents.EVENT.register((player, message) -> plugin.chathandler.handleChat(player, message)); + } + break; + + case BLOCK_BREAK: + /* Already handled by BlockEvents logic */ + break; + + case SIGN_CHANGE: + BlockEvents.SIGN_CHANGE_EVENT.register((world, pos, lines, material, player) -> { + plugin.core.processSignChange("fabric", FabricWorld.getWorldName(plugin, world), + pos.getX(), pos.getY(), pos.getZ(), lines, player.getName().getString()); + }); + break; + + default: + Log.severe("Unhandled event type: " + type); + return false; + } + + registered.add(type); + return true; + } + + @Override + public boolean sendWebChatEvent(String source, String name, String msg) { + return DynmapCommonAPIListener.fireWebChatEvent(source, name, msg); + } + + @Override + public void broadcastMessage(String msg) { + Text component = Text.literal(msg); + server.getPlayerManager().broadcast(component, MessageType.SYSTEM); + Log.info(stripChatColor(msg)); + } + + @Override + public String[] getBiomeIDs() { + BiomeMap[] b = BiomeMap.values(); + String[] bname = new String[b.length]; + + for (int i = 0; i < bname.length; i++) { + bname[i] = b[i].toString(); + } + + return bname; + } + + @Override + public double getCacheHitRate() { + if (plugin.sscache != null) + return plugin.sscache.getHitRate(); + return 0.0; + } + + @Override + public void resetCacheStats() { + if (plugin.sscache != null) + plugin.sscache.resetStats(); + } + + @Override + public DynmapWorld getWorldByName(String wname) { + return plugin.getWorldByName(wname); + } + + @Override + public DynmapPlayer getOfflinePlayer(String name) { + /* + OfflinePlayer op = getServer().getOfflinePlayer(name); + if(op != null) { + return new BukkitPlayer(op); + } + */ + return null; + } + + @Override + public Set checkPlayerPermissions(String player, Set perms) { + if (isPlayerBanned(player)) { + return Collections.emptySet(); + } + Set rslt = plugin.hasOfflinePermissions(player, perms); + if (rslt == null) { + rslt = new HashSet(); + if (plugin.isOp(player)) { + rslt.addAll(perms); + } + } + return rslt; + } + + @Override + public boolean checkPlayerPermission(String player, String perm) { + if (isPlayerBanned(player)) { + return false; + } + return plugin.hasOfflinePermission(player, perm); + } + + /** + * Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread + */ + @Override + public MapChunkCache createMapChunkCache(DynmapWorld w, List chunks, + boolean blockdata, boolean highesty, boolean biome, boolean rawbiome) { + FabricMapChunkCache c = (FabricMapChunkCache) w.getChunkCache(chunks); + if (c == null) { + return null; + } + if (w.visibility_limits != null) { + for (VisibilityLimit limit : w.visibility_limits) { + c.setVisibleRange(limit); + } + + c.setHiddenFillStyle(w.hiddenchunkstyle); + } + + if (w.hidden_limits != null) { + for (VisibilityLimit limit : w.hidden_limits) { + c.setHiddenRange(limit); + } + + c.setHiddenFillStyle(w.hiddenchunkstyle); + } + + if (!c.setChunkDataTypes(blockdata, biome, highesty, rawbiome)) { + Log.severe("CraftBukkit build does not support biome APIs"); + } + + if (chunks.size() == 0) /* No chunks to get? */ { + c.loadChunks(0); + return c; + } + + //Now handle any chunks in server thread that are already loaded (on server thread) + final FabricMapChunkCache cc = c; + Future f = this.callSyncMethod(new Callable() { + public Boolean call() throws Exception { + // Update busy state on world + //FabricWorld fw = (FabricWorld) cc.getWorld(); + //TODO + //setBusy(fw.getWorld()); + cc.getLoadedChunks(); + return true; + } + }, 0); + try { + f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException cx) { + return null; + } catch (ExecutionException xx) { + Log.severe("Exception while loading chunks", xx.getCause()); + return null; + } catch (Exception ix) { + Log.severe(ix); + return null; + } + if (!w.isLoaded()) { + return null; + } + // Now, do rest of chunk reading from calling thread + c.readChunks(chunks.size()); + + return c; + } + + @Override + public int getMaxPlayers() { + return server.getMaxPlayerCount(); + } + + @Override + public int getCurrentPlayers() { + return server.getPlayerManager().getCurrentPlayerCount(); + } + + public void tickEvent(MinecraftServer server) { + cur_tick_starttime = System.nanoTime(); + long elapsed = cur_tick_starttime - plugin.lasttick; + plugin.lasttick = cur_tick_starttime; + plugin.avgticklen = ((plugin.avgticklen * 99) / 100) + (elapsed / 100); + plugin.tps = (double) 1E9 / (double) plugin.avgticklen; + // Tick core + if (plugin.core != null) { + plugin.core.serverTick(plugin.tps); + } + + boolean done = false; + TaskRecord tr = null; + + while (!plugin.blockupdatequeue.isEmpty()) { + DynmapPlugin.BlockUpdateRec r = plugin.blockupdatequeue.remove(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.STATE_IDS.getRawId(bs); + if (!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(DynmapPlugin.stateByID[idx])) { + if (plugin.onblockchange_with_id) + plugin.mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + idx + "]"); + else + plugin.mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange"); + } + } + + long now; + + synchronized (schedlock) { + cur_tick++; + now = System.nanoTime(); + tr = runqueue.peek(); + /* Nothing due to run */ + if ((tr == null) || (tr.getTickToRun() > cur_tick) || ((now - cur_tick_starttime) > plugin.perTickLimit)) { + done = true; + } else { + tr = runqueue.poll(); + } + } + while (!done) { + tr.run(); + + synchronized (schedlock) { + tr = runqueue.peek(); + now = System.nanoTime(); + /* Nothing due to run */ + if ((tr == null) || (tr.getTickToRun() > cur_tick) || ((now - cur_tick_starttime) > plugin.perTickLimit)) { + done = true; + } else { + tr = runqueue.poll(); + } + } + } + while (!plugin.msgqueue.isEmpty()) { + DynmapPlugin.ChatMessage cm = plugin.msgqueue.poll(); + DynmapPlayer dp = null; + if (cm.sender != null) + dp = plugin.getOrAddPlayer(cm.sender); + else + dp = new FabricPlayer(plugin, null); + + plugin.core.listenerManager.processChatEvent(DynmapListenerManager.EventType.PLAYER_CHAT, dp, cm.message); + } + // Check for generated chunks + if ((cur_tick % 20) == 0) { + } + } + + private Optional getModContainerById(String id) { + return FabricLoader.getInstance().getModContainer(id); + } + + @Override + public boolean isModLoaded(String name) { + return FabricLoader.getInstance().getModContainer(name).isPresent(); + } + + @Override + public String getModVersion(String name) { + Optional mod = getModContainerById(name); // Try case sensitive lookup + return mod.map(modContainer -> modContainer.getMetadata().getVersion().getFriendlyString()).orElse(null); + } + + @Override + public double getServerTPS() { + return plugin.tps; + } + + @Override + public String getServerIP() { + if (server.isSingleplayer()) + return "0.0.0.0"; + else + return server.getServerIp(); + } + + @Override + public File getModContainerFile(String name) { + Optional container = getModContainerById(name); // Try case sensitive lookup + if (container.isPresent()) { + Path path = container.get().getRootPath(); + if (path.getFileSystem().provider().getScheme().equals("jar")) { + path = Paths.get(path.getFileSystem().toString()); + } + return path.toFile(); + } + return null; + } + + @Override + public List getModList() { + return FabricLoader.getInstance() + .getAllMods() + .stream() + .map(container -> container.getMetadata().getId()) + .collect(Collectors.toList()); + } + + @Override + public Map getBlockIDMap() { + Map map = new HashMap(); + return map; + } + + @Override + public InputStream openResource(String modid, String rname) { + if (modid == null) modid = "minecraft"; + + if ("minecraft".equals(modid)) { + return MinecraftServer.class.getClassLoader().getResourceAsStream(rname); + } else { + if (rname.startsWith("/") || rname.startsWith("\\")) { + rname = rname.substring(1); + } + + final String finalModid = modid; + final String finalRname = rname; + return getModContainerById(modid).map(container -> { + try { + return Files.newInputStream(container.getPath(finalRname)); + } catch (IOException e) { + Log.severe("Failed to load resource of mod :" + finalModid, e); + return null; + } + }).orElse(null); + } + } + + /** + * Get block unique ID map (module:blockid) + */ + @Override + public Map getBlockUniqueIDMap() { + HashMap map = new HashMap(); + return map; + } + + /** + * Get item unique ID map (module:itemid) + */ + @Override + public Map getItemUniqueIDMap() { + HashMap map = new HashMap(); + return map; + } + +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricWorld.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricWorld.java new file mode 100644 index 00000000..500d4cba --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/FabricWorld.java @@ -0,0 +1,236 @@ +package org.dynmap.fabric_1_19; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.Heightmap; +import net.minecraft.world.LightType; +import net.minecraft.world.World; +import net.minecraft.world.border.WorldBorder; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +import java.util.List; + +public class FabricWorld extends DynmapWorld { + // TODO: Store this relative to World saves for integrated server + public static final String SAVED_WORLDS_FILE = "fabricworlds.yml"; + + private final DynmapPlugin plugin; + private World world; + private final boolean skylight; + private final boolean isnether; + private final boolean istheend; + private final String env; + private DynmapLocation spawnloc = new DynmapLocation(); + private static int maxWorldHeight = 320; // Maximum allows world height + + public static int getMaxWorldHeight() { + return maxWorldHeight; + } + + public static void setMaxWorldHeight(int h) { + maxWorldHeight = h; + } + + public static String getWorldName(DynmapPlugin plugin, World w) { + RegistryKey rk = w.getRegistryKey(); + if (rk == World.OVERWORLD) { // Overworld? + return w.getServer().getSaveProperties().getLevelName(); + } else if (rk == World.END) { + return "DIM1"; + } else if (rk == World.NETHER) { + return "DIM-1"; + } else { + return rk.getValue().getNamespace() + "_" + rk.getValue().getPath(); + } + } + + public void updateWorld(World w) { + this.updateWorldHeights(w.getHeight(), w.getBottomY(), w.getSeaLevel()); + } + + public FabricWorld(DynmapPlugin plugin, World w) { + this(plugin, getWorldName(plugin, w), w.getHeight(), + w.getSeaLevel(), + w.getRegistryKey() == World.NETHER, + w.getRegistryKey() == World.END, + w.getRegistryKey().getValue().getPath(), + w.getBottomY()); + setWorldLoaded(w); + } + + public FabricWorld(DynmapPlugin plugin, String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, int miny) { + super(name, (height > maxWorldHeight) ? maxWorldHeight : height, sealevel, miny); + this.plugin = plugin; + world = null; + setTitle(deftitle); + isnether = nether; + istheend = the_end; + skylight = !(isnether || istheend); + + if (isnether) { + env = "nether"; + } else if (istheend) { + env = "the_end"; + } else { + env = "normal"; + } + + } + + /* Test if world is nether */ + @Override + public boolean isNether() { + return isnether; + } + + public boolean isTheEnd() { + return istheend; + } + + /* Get world spawn location */ + @Override + public DynmapLocation getSpawnLocation() { + if (world != null) { + spawnloc.x = world.getLevelProperties().getSpawnX(); + spawnloc.y = world.getLevelProperties().getSpawnY(); + spawnloc.z = world.getLevelProperties().getSpawnZ(); + spawnloc.world = this.getName(); + } + return spawnloc; + } + + /* Get world time */ + @Override + public long getTime() { + if (world != null) + return world.getTimeOfDay(); + else + return -1; + } + + /* World is storming */ + @Override + public boolean hasStorm() { + if (world != null) + return world.isRaining(); + else + return false; + } + + /* World is thundering */ + @Override + public boolean isThundering() { + if (world != null) + return world.isThundering(); + else + return false; + } + + /* World is loaded */ + @Override + public boolean isLoaded() { + return (world != null); + } + + /* Set world to unloaded */ + @Override + public void setWorldUnloaded() { + getSpawnLocation(); + world = null; + } + + /* Set world to loaded */ + public void setWorldLoaded(World w) { + world = w; + this.sealevel = w.getSeaLevel(); // Read actual current sealevel from world + // Update lighting table + for (int lightLevel = 0; lightLevel < 16; lightLevel++) { + // Algorithm based on LightmapTextureManager.getBrightness() + // We can't call that method because it's client-only. + // This means the code below can stop being correct if Mojang ever + // updates the curve; in that case we should reflect the changes. + float value = (float) lightLevel / 15.0f; + float brightness = value / (4.0f - 3.0f * value); + this.setBrightnessTableEntry(lightLevel, MathHelper.lerp(w.getDimension().ambientLight(), brightness, 1.0F)); + } + } + + /* Get light level of block */ + @Override + public int getLightLevel(int x, int y, int z) { + if (world != null) + return world.getLightLevel(new BlockPos(x, y, z)); + else + return -1; + } + + /* Get highest Y coord of given location */ + @Override + public int getHighestBlockYAt(int x, int z) { + if (world != null) { + return world.getChunk(x >> 4, z >> 4).getHeightmap(Heightmap.Type.MOTION_BLOCKING).get(x & 15, z & 15); + } else + return -1; + } + + /* Test if sky light level is requestable */ + @Override + public boolean canGetSkyLightLevel() { + return skylight; + } + + /* Return sky light level */ + @Override + public int getSkyLightLevel(int x, int y, int z) { + if (world != null) { + return world.getLightLevel(LightType.SKY, new BlockPos(x, y, z)); + } else + return -1; + } + + /** + * Get world environment ID (lower case - normal, the_end, nether) + */ + @Override + public String getEnvironment() { + return env; + } + + /** + * Get map chunk cache for world + */ + @Override + public MapChunkCache getChunkCache(List chunks) { + if (world != null) { + FabricMapChunkCache c = new FabricMapChunkCache(plugin); + c.setChunks(this, chunks); + return c; + } + return null; + } + + public World getWorld() { + return world; + } + + @Override + public Polygon getWorldBorder() { + if (world != null) { + WorldBorder wb = world.getWorldBorder(); + if ((wb != null) && (wb.getSize() < 5.9E7)) { + Polygon p = new Polygon(); + p.addVertex(wb.getBoundWest(), wb.getBoundNorth()); + p.addVertex(wb.getBoundWest(), wb.getBoundSouth()); + p.addVertex(wb.getBoundEast(), wb.getBoundSouth()); + p.addVertex(wb.getBoundEast(), wb.getBoundNorth()); + return p; + } + } + return null; + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/NBT.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/NBT.java new file mode 100644 index 00000000..2f8b52a5 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/NBT.java @@ -0,0 +1,126 @@ +package org.dynmap.fabric_1_19; + +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.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.util.collection.PackedIntegerArray; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final NbtCompound obj; + public NBTCompound(NbtCompound t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.getKeys(); + } + @Override + public boolean contains(String s) { + return obj.contains(s); + } + @Override + public boolean contains(String s, int i) { + return obj.contains(s, i); + } + @Override + public byte getByte(String s) { + return obj.getByte(s); + } + @Override + public short getShort(String s) { + return obj.getShort(s); + } + @Override + public int getInt(String s) { + return obj.getInt(s); + } + @Override + public long getLong(String s) { + return obj.getLong(s); + } + @Override + public float getFloat(String s) { + return obj.getFloat(s); + } + @Override + public double getDouble(String s) { + return obj.getDouble(s); + } + @Override + public String getString(String s) { + return obj.getString(s); + } + @Override + public byte[] getByteArray(String s) { + return obj.getByteArray(s); + } + @Override + public int[] getIntArray(String s) { + return obj.getIntArray(s); + } + @Override + public long[] getLongArray(String s) { + return obj.getLongArray(s); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompound(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + return new NBTList(obj.getList(s, i)); + } + @Override + public boolean getBoolean(String s) { + return obj.getBoolean(s); + } + @Override + public String getAsString(String s) { + return obj.get(s).asString(); + } + @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 NbtList obj; + public NBTList(NbtList t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.getString(idx); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompound(idx)); + } + public String toString() { + return obj.toString(); + } + } + public static class OurBitStorage implements GenericBitStorage { + private final PackedIntegerArray bs; + public OurBitStorage(int bits, int count, long[] data) { + bs = new PackedIntegerArray(bits, count, data); + } + @Override + public int get(int idx) { + return bs.get(idx); + } + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/TaskRecord.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/TaskRecord.java new file mode 100644 index 00000000..1c7e4440 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/TaskRecord.java @@ -0,0 +1,38 @@ +package org.dynmap.fabric_1_19; + +import java.util.concurrent.FutureTask; + +class TaskRecord implements Comparable { + TaskRecord(long ticktorun, long id, FutureTask future) { + this.ticktorun = ticktorun; + this.id = id; + this.future = future; + } + + private final long ticktorun; + private final long id; + private final FutureTask future; + + void run() { + this.future.run(); + } + + long getTickToRun() { + return this.ticktorun; + } + + @Override + public int compareTo(TaskRecord o) { + if (this.ticktorun < o.ticktorun) { + return -1; + } else if (this.ticktorun > o.ticktorun) { + return 1; + } else if (this.id < o.id) { + return -1; + } else if (this.id > o.id) { + return 1; + } else { + return 0; + } + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/VersionCheck.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/VersionCheck.java new file mode 100644 index 00000000..4b6f5bf5 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/VersionCheck.java @@ -0,0 +1,98 @@ +package org.dynmap.fabric_1_19; + +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +public class VersionCheck { + private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; + + public static void runCheck(final DynmapCore core) { + new Thread(new Runnable() { + public void run() { + doCheck(core); + } + }).start(); + } + + private static int getReleaseVersion(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(0, index); + String[] split = s.split("\\."); + int v = 0; + try { + for (int i = 0; (i < split.length) && (i < 3); i++) { + v += Integer.parseInt(split[i]) << (8 * (2 - i)); + } + } catch (NumberFormatException nfx) { + } + return v; + } + + private static int getBuildNumber(String s) { + int index = s.lastIndexOf('-'); + if (index < 0) + index = s.lastIndexOf('.'); + if (index >= 0) + s = s.substring(index + 1); + try { + return Integer.parseInt(s); + } catch (NumberFormatException nfx) { + return 99999999; + } + } + + private static void doCheck(DynmapCore core) { + String pluginver = core.getDynmapPluginVersion(); + String platform = core.getDynmapPluginPlatform(); + String platver = core.getDynmapPluginPlatformVersion(); + if ((pluginver == null) || (platform == null) || (platver == null)) + return; + HttpURLConnection conn = null; + String loc = VERSION_URL; + int cur_ver = getReleaseVersion(pluginver); + int cur_bn = getBuildNumber(pluginver); + try { + while ((loc != null) && (!loc.isEmpty())) { + URL url = new URL(loc); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); + conn.connect(); + loc = conn.getHeaderField("Location"); + } + BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + while ((line = rdr.readLine()) != null) { + String[] split = line.split(":"); + if (split.length < 4) continue; + /* If our platform and version, or wildcard platform version */ + if (split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { + int recommended_ver = getReleaseVersion(split[2]); + int recommended_bn = getBuildNumber(split[2]); + if ((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) && (recommended_bn > cur_bn))) { /* Newer recommended build */ + Log.info("Version obsolete: new recommended version " + split[2] + " is available."); + } else if (cur_ver > recommended_ver) { /* Running dev or prerelease? */ + int prerel_ver = getReleaseVersion(split[3]); + int prerel_bn = getBuildNumber(split[3]); + if ((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { + Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); + } + } + } + } + } catch (Exception x) { + Log.info("Error checking for latest version"); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/access/ProtoChunkAccessor.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/access/ProtoChunkAccessor.java new file mode 100644 index 00000000..350a250b --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/access/ProtoChunkAccessor.java @@ -0,0 +1,5 @@ +package org.dynmap.fabric_1_19.access; + +public interface ProtoChunkAccessor { + boolean getTouchedByWorldGen(); +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DmapCommand.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DmapCommand.java new file mode 100644 index 00000000..d6907648 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DmapCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_1_19.command; + +import org.dynmap.fabric_1_19.DynmapPlugin; + +public class DmapCommand extends DynmapCommandExecutor { + public DmapCommand(DynmapPlugin p) { + super("dmap", p); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DmarkerCommand.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DmarkerCommand.java new file mode 100644 index 00000000..a5c31f02 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DmarkerCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_1_19.command; + +import org.dynmap.fabric_1_19.DynmapPlugin; + +public class DmarkerCommand extends DynmapCommandExecutor { + public DmarkerCommand(DynmapPlugin p) { + super("dmarker", p); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapCommand.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapCommand.java new file mode 100644 index 00000000..698b847e --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_1_19.command; + +import org.dynmap.fabric_1_19.DynmapPlugin; + +public class DynmapCommand extends DynmapCommandExecutor { + public DynmapCommand(DynmapPlugin p) { + super("dynmap", p); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapCommandExecutor.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapCommandExecutor.java new file mode 100644 index 00000000..bb4fc01b --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapCommandExecutor.java @@ -0,0 +1,63 @@ +package org.dynmap.fabric_1_19.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import net.minecraft.server.command.ServerCommandSource; +import org.dynmap.fabric_1_19.DynmapPlugin; + +import java.util.Arrays; + +import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +public class DynmapCommandExecutor implements Command { + private final String cmd; + private final DynmapPlugin plugin; + + DynmapCommandExecutor(String cmd, DynmapPlugin plugin) { + this.cmd = cmd; + this.plugin = plugin; + } + + public void register(CommandDispatcher dispatcher) { + final RootCommandNode root = dispatcher.getRoot(); + + final LiteralCommandNode command = literal(this.cmd) + .executes(this) + .build(); + + final ArgumentCommandNode args = argument("args", greedyString()) + .executes(this) + .build(); + + // So this becomes "cmd" [args] + command.addChild(args); + + // Add command to the command dispatcher via root node. + root.addChild(command); + } + + @Override + public int run(CommandContext context) throws CommandSyntaxException { + // Commands in brigadier may be proxied in Minecraft via a syntax like `/execute ... ... run dmap [args]` + // Dynmap will fail to parse this properly, so we find the starting position of the actual command being parsed after any forks or redirects. + // The start position of the range specifies where the actual command dynmap has registered starts + int start = context.getRange().getStart(); + String dynmapInput = context.getInput().substring(start); + + String[] args = dynmapInput.split("\\s+"); + plugin.handleCommand(context.getSource(), cmd, Arrays.copyOfRange(args, 1, args.length)); + return 1; + } + + // @Override // TODO: Usage? + public String getUsage(ServerCommandSource commandSource) { + return "Run /" + cmd + " help for details on using command"; + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapExpCommand.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapExpCommand.java new file mode 100644 index 00000000..29df91fc --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/command/DynmapExpCommand.java @@ -0,0 +1,9 @@ +package org.dynmap.fabric_1_19.command; + +import org.dynmap.fabric_1_19.DynmapPlugin; + +public class DynmapExpCommand extends DynmapCommandExecutor { + public DynmapExpCommand(DynmapPlugin p) { + super("dynmapexp", p); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/BlockEvents.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/BlockEvents.java new file mode 100644 index 00000000..3fefe2fa --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/BlockEvents.java @@ -0,0 +1,40 @@ +package org.dynmap.fabric_1_19.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.block.Material; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class BlockEvents { + private BlockEvents() { + } + + public static Event BLOCK_EVENT = EventFactory.createArrayBacked(BlockCallback.class, + (listeners) -> (world, pos) -> { + for (BlockCallback callback : listeners) { + callback.onBlockEvent(world, pos); + } + } + ); + + public static Event SIGN_CHANGE_EVENT = EventFactory.createArrayBacked(SignChangeCallback.class, + (listeners) -> (world, pos, lines, material, player) -> { + for (SignChangeCallback callback : listeners) { + callback.onSignChange(world, pos, lines, material, player); + } + } + ); + + @FunctionalInterface + public interface BlockCallback { + void onBlockEvent(World world, BlockPos pos); + } + + @FunctionalInterface + public interface SignChangeCallback { + void onSignChange(ServerWorld world, BlockPos pos, String[] lines, Material material, ServerPlayerEntity player); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/CustomServerChunkEvents.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/CustomServerChunkEvents.java new file mode 100644 index 00000000..ee756e20 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/CustomServerChunkEvents.java @@ -0,0 +1,21 @@ +package org.dynmap.fabric_1_19.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.chunk.Chunk; + +public class CustomServerChunkEvents { + public static Event CHUNK_GENERATE = EventFactory.createArrayBacked(ChunkGenerate.class, + (listeners) -> (world, chunk) -> { + for (ChunkGenerate callback : listeners) { + callback.onChunkGenerate(world, chunk); + } + } + ); + + @FunctionalInterface + public interface ChunkGenerate { + void onChunkGenerate(ServerWorld world, Chunk chunk); + } +} \ No newline at end of file diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/CustomServerLifecycleEvents.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/CustomServerLifecycleEvents.java new file mode 100644 index 00000000..b6385ddd --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/CustomServerLifecycleEvents.java @@ -0,0 +1,14 @@ +package org.dynmap.fabric_1_19.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; + +public class CustomServerLifecycleEvents { + public static final Event SERVER_STARTED_PRE_WORLD_LOAD = + EventFactory.createArrayBacked(ServerLifecycleEvents.ServerStarted.class, (callbacks) -> (server) -> { + for (ServerLifecycleEvents.ServerStarted callback : callbacks) { + callback.onServerStarted(server); + } + }); +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/PlayerEvents.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/PlayerEvents.java new file mode 100644 index 00000000..fc02bd34 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/PlayerEvents.java @@ -0,0 +1,62 @@ +package org.dynmap.fabric_1_19.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.network.ServerPlayerEntity; + +public class PlayerEvents { + private PlayerEvents() { + } + + public static Event PLAYER_LOGGED_IN = EventFactory.createArrayBacked(PlayerLoggedIn.class, + (listeners) -> (player) -> { + for (PlayerLoggedIn callback : listeners) { + callback.onPlayerLoggedIn(player); + } + } + ); + + public static Event PLAYER_LOGGED_OUT = EventFactory.createArrayBacked(PlayerLoggedOut.class, + (listeners) -> (player) -> { + for (PlayerLoggedOut callback : listeners) { + callback.onPlayerLoggedOut(player); + } + } + ); + + public static Event PLAYER_CHANGED_DIMENSION = EventFactory.createArrayBacked(PlayerChangedDimension.class, + (listeners) -> (player) -> { + for (PlayerChangedDimension callback : listeners) { + callback.onPlayerChangedDimension(player); + } + } + ); + + public static Event PLAYER_RESPAWN = EventFactory.createArrayBacked(PlayerRespawn.class, + (listeners) -> (player) -> { + for (PlayerRespawn callback : listeners) { + callback.onPlayerRespawn(player); + } + } + ); + + @FunctionalInterface + public interface PlayerLoggedIn { + void onPlayerLoggedIn(ServerPlayerEntity player); + } + + @FunctionalInterface + public interface PlayerLoggedOut { + void onPlayerLoggedOut(ServerPlayerEntity player); + } + + @FunctionalInterface + public interface PlayerChangedDimension { + void onPlayerChangedDimension(ServerPlayerEntity player); + } + + @FunctionalInterface + public interface PlayerRespawn { + void onPlayerRespawn(ServerPlayerEntity player); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/ServerChatEvents.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/ServerChatEvents.java new file mode 100644 index 00000000..6ab805c4 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/event/ServerChatEvents.java @@ -0,0 +1,23 @@ +package org.dynmap.fabric_1_19.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.network.ServerPlayerEntity; + +public class ServerChatEvents { + private ServerChatEvents() { + } + + public static Event EVENT = EventFactory.createArrayBacked(ServerChatCallback.class, + (listeners) -> (player, message) -> { + for (ServerChatCallback callback : listeners) { + callback.onChatMessage(player, message); + } + } + ); + + @FunctionalInterface + public interface ServerChatCallback { + void onChatMessage(ServerPlayerEntity player, String message); + } +} \ No newline at end of file diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/BiomeEffectsAccessor.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/BiomeEffectsAccessor.java new file mode 100644 index 00000000..ab6e135c --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/BiomeEffectsAccessor.java @@ -0,0 +1,11 @@ +package org.dynmap.fabric_1_19.mixin; + +import net.minecraft.world.biome.BiomeEffects; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(BiomeEffects.class) +public interface BiomeEffectsAccessor { + @Accessor + int getWaterColor(); +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/MinecraftServerMixin.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/MinecraftServerMixin.java new file mode 100644 index 00000000..6eee0d55 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/MinecraftServerMixin.java @@ -0,0 +1,16 @@ +package org.dynmap.fabric_1_19.mixin; + +import net.minecraft.server.MinecraftServer; +import org.dynmap.fabric_1_19.event.CustomServerLifecycleEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MinecraftServer.class) +public class MinecraftServerMixin { + @Inject(method = "loadWorld", at = @At("HEAD")) + protected void loadWorld(CallbackInfo info) { + CustomServerLifecycleEvents.SERVER_STARTED_PRE_WORLD_LOAD.invoker().onServerStarted((MinecraftServer) (Object) this); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/PlayerManagerMixin.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/PlayerManagerMixin.java new file mode 100644 index 00000000..a356cf25 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/PlayerManagerMixin.java @@ -0,0 +1,29 @@ +package org.dynmap.fabric_1_19.mixin; + +import net.minecraft.network.ClientConnection; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; +import org.dynmap.fabric_1_19.event.PlayerEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerManager.class) +public class PlayerManagerMixin { + @Inject(method = "onPlayerConnect", at = @At("TAIL")) + public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo info) { + PlayerEvents.PLAYER_LOGGED_IN.invoker().onPlayerLoggedIn(player); + } + + @Inject(method = "remove", at = @At("HEAD")) + public void remove(ServerPlayerEntity player, CallbackInfo info) { + PlayerEvents.PLAYER_LOGGED_OUT.invoker().onPlayerLoggedOut(player); + } + + @Inject(method = "respawnPlayer", at = @At("RETURN")) + public void respawnPlayer(ServerPlayerEntity player, boolean alive, CallbackInfoReturnable info) { + PlayerEvents.PLAYER_RESPAWN.invoker().onPlayerRespawn(info.getReturnValue()); + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ProtoChunkMixin.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ProtoChunkMixin.java new file mode 100644 index 00000000..4d0856be --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ProtoChunkMixin.java @@ -0,0 +1,30 @@ +package org.dynmap.fabric_1_19.mixin; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.chunk.ProtoChunk; +import org.dynmap.fabric_1_19.access.ProtoChunkAccessor; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ProtoChunk.class) +public class ProtoChunkMixin implements ProtoChunkAccessor { + private boolean touchedByWorldGen = false; + + @Inject( + method = "setBlockState", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/chunk/ChunkSection;setBlockState(IIILnet/minecraft/block/BlockState;)Lnet/minecraft/block/BlockState;" + ) + ) + public void setBlockState(BlockPos pos, BlockState state, boolean moved, CallbackInfoReturnable info) { + touchedByWorldGen = true; + } + + public boolean getTouchedByWorldGen() { + return touchedByWorldGen; + } +} \ No newline at end of file diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ServerPlayNetworkHandlerMixin.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ServerPlayNetworkHandlerMixin.java new file mode 100644 index 00000000..cabe6a5a --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ServerPlayNetworkHandlerMixin.java @@ -0,0 +1,70 @@ +package org.dynmap.fabric_1_19.mixin; + +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.network.message.SignedMessage; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import net.minecraft.server.filter.FilteredMessage; +import net.minecraft.server.filter.TextStream; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.LiteralTextContent; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; + +import java.util.List; + +import net.minecraft.util.registry.RegistryKey; +import org.dynmap.fabric_1_19.event.BlockEvents; +import org.dynmap.fabric_1_19.event.ServerChatEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ServerPlayNetworkHandler.class) +public abstract class ServerPlayNetworkHandlerMixin { + @Shadow + public ServerPlayerEntity player; + + @Inject( + method = "handleDecoratedMessage", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/server/filter/FilteredMessage;Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/util/registry/RegistryKey;)V", + shift = At.Shift.BEFORE + ) + ) + public void onGameMessage(FilteredMessage message, CallbackInfo ci) { + ServerChatEvents.EVENT.invoker().onChatMessage(player, message.raw().getContent().getString()); + } + + @Inject( + method = "onSignUpdate", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/block/entity/SignBlockEntity;markDirty()V", + shift = At.Shift.BEFORE + ), + locals = LocalCapture.CAPTURE_FAILHARD + ) + public void onSignUpdate(UpdateSignC2SPacket packet, List> signText, CallbackInfo info, + ServerWorld serverWorld, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity, SignBlockEntity signBlockEntity) + { + // Pull the raw text from the input. + String[] rawTexts = new String[4]; + for (int i=0; i info) { + ServerPlayerEntity player = (ServerPlayerEntity) (Object) this; + if (player.getRemovalReason() == null) { + PlayerEvents.PLAYER_CHANGED_DIMENSION.invoker().onPlayerChangedDimension(player); + } + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ThreadedAnvilChunkStorageMixin.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ThreadedAnvilChunkStorageMixin.java new file mode 100644 index 00000000..908fd912 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/ThreadedAnvilChunkStorageMixin.java @@ -0,0 +1,32 @@ +package org.dynmap.fabric_1_19.mixin; + +import net.minecraft.server.world.ChunkHolder; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import net.minecraft.world.chunk.Chunk; +import org.dynmap.fabric_1_19.access.ProtoChunkAccessor; +import org.dynmap.fabric_1_19.event.CustomServerChunkEvents; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ThreadedAnvilChunkStorage.class, priority = 666 /* fire before Fabric API CHUNK_LOAD event */) +public abstract class ThreadedAnvilChunkStorageMixin { + @Final + @Shadow + ServerWorld world; + + @Inject( + /* Same place as fabric-lifecycle-events-v1 event CHUNK_LOAD (we will fire before it) */ + method = "method_17227", + at = @At("TAIL") + ) + private void onChunkGenerate(ChunkHolder chunkHolder, Chunk protoChunk, CallbackInfoReturnable callbackInfoReturnable) { + if (((ProtoChunkAccessor)protoChunk).getTouchedByWorldGen()) { + CustomServerChunkEvents.CHUNK_GENERATE.invoker().onChunkGenerate(this.world, callbackInfoReturnable.getReturnValue()); + } + } +} \ No newline at end of file diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/WorldChunkMixin.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/WorldChunkMixin.java new file mode 100644 index 00000000..fee76172 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/mixin/WorldChunkMixin.java @@ -0,0 +1,25 @@ +package org.dynmap.fabric_1_19.mixin; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; +import org.dynmap.fabric_1_19.event.BlockEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(WorldChunk.class) +public abstract class WorldChunkMixin { + @Shadow + public abstract World getWorld(); + + @Inject(method = "setBlockState", at = @At("RETURN")) + public void setBlockState(BlockPos pos, BlockState state, boolean moved, CallbackInfoReturnable info) { + if (info.getReturnValue() != null) { + BlockEvents.BLOCK_EVENT.invoker().onBlockEvent(this.getWorld(), pos); + } + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/FilePermissions.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/FilePermissions.java new file mode 100644 index 00000000..78f21d3b --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/FilePermissions.java @@ -0,0 +1,103 @@ +package org.dynmap.fabric_1_19.permissions; + +import net.minecraft.entity.player.PlayerEntity; +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.dynmap.fabric_1_19.DynmapPlugin; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class FilePermissions implements PermissionProvider { + private HashMap> perms; + private Set defperms; + + public static FilePermissions create() { + File f = new File("dynmap/permissions.yml"); + if (!f.exists()) + return null; + ConfigurationNode cfg = new ConfigurationNode(f); + cfg.load(); + + Log.info("Using permissions.yml for access control"); + + return new FilePermissions(cfg); + } + + private FilePermissions(ConfigurationNode cfg) { + perms = new HashMap>(); + for (String k : cfg.keySet()) { + List p = cfg.getStrings(k, null); + if (p != null) { + k = k.toLowerCase(); + HashSet pset = new HashSet(); + for (String perm : p) { + pset.add(perm.toLowerCase()); + } + perms.put(k, pset); + if (k.equals("defaultuser")) { + defperms = pset; + } + } + } + } + + private boolean hasPerm(String player, String perm) { + Set ps = perms.get(player); + if ((ps != null) && (ps.contains(perm))) { + return true; + } + if (defperms.contains(perm)) { + return true; + } + return false; + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + player = player.toLowerCase(); + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } else { + for (String p : perms) { + if (hasPerm(player, p)) { + rslt.add(p); + } + } + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + player = player.toLowerCase(); + if (DynmapPlugin.plugin.isOp(player)) { + return true; + } else { + return hasPerm(player, perm); + } + } + + @Override + public boolean has(PlayerEntity psender, String permission) { + if (psender != null) { + String n = psender.getName().getString().toLowerCase(); + return hasPerm(n, permission); + } + return true; + } + + @Override + public boolean hasPermissionNode(PlayerEntity psender, String permission) { + if (psender != null) { + String player = psender.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(player); + } + return false; + } + +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/OpPermissions.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/OpPermissions.java new file mode 100644 index 00000000..fdf9369b --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/OpPermissions.java @@ -0,0 +1,52 @@ +package org.dynmap.fabric_1_19.permissions; + +import net.minecraft.entity.player.PlayerEntity; +import org.dynmap.Log; +import org.dynmap.fabric_1_19.DynmapPlugin; + +import java.util.HashSet; +import java.util.Set; + +public class OpPermissions implements PermissionProvider { + public HashSet usrCommands = new HashSet(); + + public OpPermissions(String[] usrCommands) { + for (String usrCommand : usrCommands) { + this.usrCommands.add(usrCommand); + } + Log.info("Using ops.txt for access control"); + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + HashSet rslt = new HashSet(); + if (DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + return rslt; + } + + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player); + } + + @Override + public boolean has(PlayerEntity psender, String permission) { + if (psender != null) { + if (usrCommands.contains(permission)) { + return true; + } + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } + + @Override + public boolean hasPermissionNode(PlayerEntity psender, String permission) { + if (psender != null) { + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } +} diff --git a/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/PermissionProvider.java b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/PermissionProvider.java new file mode 100644 index 00000000..7affe811 --- /dev/null +++ b/fabric-1.19/src/main/java/org/dynmap/fabric_1_19/permissions/PermissionProvider.java @@ -0,0 +1,16 @@ +package org.dynmap.fabric_1_19.permissions; + +import net.minecraft.entity.player.PlayerEntity; + +import java.util.Set; + +public interface PermissionProvider { + boolean has(PlayerEntity sender, String permission); + + boolean hasPermissionNode(PlayerEntity sender, String permission); + + Set hasOfflinePermissions(String player, Set perms); + + boolean hasOfflinePermission(String player, String perm); + +} diff --git a/fabric-1.19/src/main/resources/assets/dynmap/icon.png b/fabric-1.19/src/main/resources/assets/dynmap/icon.png new file mode 100644 index 00000000..d18f3e14 Binary files /dev/null and b/fabric-1.19/src/main/resources/assets/dynmap/icon.png differ diff --git a/fabric-1.19/src/main/resources/configuration.txt b/fabric-1.19/src/main/resources/configuration.txt new file mode 100644 index 00000000..c0379e35 --- /dev/null +++ b/fabric-1.19/src/main/resources/configuration.txt @@ -0,0 +1,491 @@ +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/dynmap/ + +# All map templates are defined in the templates directory +# To use the HDMap very-low-res (2 ppb) map templates as world defaults, set value to vlowres +# The definitions of these templates are in normal-vlowres.txt, nether-vlowres.txt, and the_end-vlowres.txt +# To use the HDMap low-res (4 ppb) map templates as world defaults, set value to lowres +# The definitions of these templates are in normal-lowres.txt, nether-lowres.txt, and the_end-lowres.txt +# To use the HDMap hi-res (16 ppb) map templates (these can take a VERY long time for initial fullrender), set value to hires +# The definitions of these templates are in normal-hires.txt, nether-hires.txt, and the_end-hires.txt +# To use the HDMap low-res (4 ppb) map templates, with support for boosting resolution selectively to hi-res (16 ppb), set value to low_boost_hi +# The definitions of these templates are in normal-low_boost_hi.txt, nether-low_boost_hi.txt, and the_end-low_boost_hi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to vhi-res (32 ppb), set value to hi_boost_vhi +# The definitions of these templates are in normal-hi_boost_vhi.txt, nether-hi_boost_vhi.txt, and the_end-hi_boost_vhi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to xhi-res (64 ppb), set value to hi_boost_xhi +# The definitions of these templates are in normal-hi_boost_xhi.txt, nether-hi_boost_xhi.txt, and the_end-hi_boost_xhi.txt +deftemplatesuffix: hires + +# Set default tile scale (0 = 128px x 128x, 1 = 256px x 256px, 2 = 512px x 512px, 3 = 1024px x 1024px, 4 = 2048px x 2048px) - 0 is default +# Note: changing this value will result in all maps that use the default value being required to be fully rendered +#defaulttilescale: 0 + +# Map storage scheme: only uncommoent one 'type' value +# filetree: classic and default scheme: tree of files, with all map data under the directory indicated by 'tilespath' setting +# sqlite: single SQLite database file (this can get VERY BIG), located at 'dbfile' setting (default is file dynmap.db in data directory) +# mysql: MySQL database, at hostname:port in database, accessed via userid with password +# mariadb: MariaDB database, at hostname:port in database, accessed via userid with password +# postgres: PostgreSQL database, at hostname:port in database, accessed via userid with password +storage: + # Filetree storage (standard tree of image files for maps) + type: filetree + # SQLite db for map storage (uses dbfile as storage location) + #type: sqlite + #dbfile: dynmap.db + # MySQL DB for map storage (at 'hostname':'port' in database 'database' using user 'userid' password 'password' and table prefix 'prefix' + #type: mysql + #hostname: localhost + #port: 3306 + #database: dynmap + #userid: dynmap + #password: dynmap + #prefix: "" + # + # AWS S3 backet web site + #type: aws_s3 + #bucketname: "dynmap-bucket-name" + #region: us-east-1 + #aws_access_key_id: "" + #aws_secret_access_key: "" + #prefix: "" + +components: + - class: org.dynmap.ClientConfigurationComponent + + - class: org.dynmap.InternalClientUpdateComponent + sendhealth: true + sendposition: true + allowwebchat: true + webchat-interval: 5 + hidewebchatip: false + trustclientname: false + includehiddenplayers: false + # (optional) if true, color codes in player display names are used + use-name-colors: false + # (optional) if true, player login IDs will be used for web chat when their IPs match + use-player-login-ip: true + # (optional) if use-player-login-ip is true, setting this to true will cause chat messages not matching a known player IP to be ignored + require-player-login-ip: false + # (optional) block player login IDs that are banned from chatting + block-banned-player-chat: true + # Require login for web-to-server chat (requires login-enabled: true) + webchat-requires-login: false + # If set to true, users must have dynmap.webchat permission in order to chat + webchat-permissions: false + # Limit length of single chat messages + chatlengthlimit: 256 + # # Optional - make players hidden when they are inside/underground/in shadows (#=light level: 0=full shadow,15=sky) + # hideifshadow: 4 + # # Optional - make player hidden when they are under cover (#=sky light level,0=underground,15=open to sky) + # hideifundercover: 14 + # # (Optional) if true, players that are crouching/sneaking will be hidden + hideifsneaking: false + # If true, player positions/status is protected (login with ID with dynmap.playermarkers.seeall permission required for info other than self) + protected-player-info: false + # If true, hide players with invisibility potion effects active + hide-if-invisiblity-potion: true + # If true, player names are not shown on map, chat, list + hidenames: false + #- class: org.dynmap.JsonFileClientUpdateComponent + # writeinterval: 1 + # sendhealth: true + # sendposition: true + # allowwebchat: true + # webchat-interval: 5 + # hidewebchatip: false + # includehiddenplayers: false + # use-name-colors: false + # use-player-login-ip: false + # require-player-login-ip: false + # block-banned-player-chat: true + # hideifshadow: 0 + # hideifundercover: 0 + # hideifsneaking: false + # # Require login for web-to-server chat (requires login-enabled: true) + # webchat-requires-login: false + # # If set to true, users must have dynmap.webchat permission in order to chat + # webchat-permissions: false + # # Limit length of single chat messages + # chatlengthlimit: 256 + # hide-if-invisiblity-potion: true + # hidenames: false + + - class: org.dynmap.SimpleWebChatComponent + allowchat: true + # If true, web UI users can supply name for chat using 'playername' URL parameter. 'trustclientname' must also be set true. + allowurlname: false + + # Note: this component is needed for the dmarker commands, and for the Marker API to be available to other plugins + - class: org.dynmap.MarkersComponent + type: markers + showlabel: false + enablesigns: false + # Default marker set for sign markers + default-sign-set: markers + # (optional) add spawn point markers to standard marker layer + showspawn: true + spawnicon: world + spawnlabel: "Spawn" + # (optional) layer for showing offline player's positions (for 'maxofflinetime' minutes after logoff) + showofflineplayers: false + offlinelabel: "Offline" + offlineicon: offlineuser + offlinehidebydefault: true + offlineminzoom: 0 + maxofflinetime: 30 + # (optional) layer for showing player's spawn beds + showspawnbeds: false + spawnbedlabel: "Spawn Beds" + spawnbedicon: bed + spawnbedhidebydefault: true + spawnbedminzoom: 0 + spawnbedformat: "%name%'s bed" + # (optional) Show world border (vanilla 1.8+) + showworldborder: true + worldborderlabel: "Border" + + - class: org.dynmap.ClientComponent + type: chat + allowurlname: false + - class: org.dynmap.ClientComponent + type: chatballoon + focuschatballoons: false + - class: org.dynmap.ClientComponent + type: chatbox + showplayerfaces: true + messagettl: 5 + # Optional: set number of lines in scrollable message history: if set, messagettl is not used to age out messages + #scrollback: 100 + # Optional: set maximum number of lines visible for chatbox + #visiblelines: 10 + # Optional: send push button + sendbutton: false + - class: org.dynmap.ClientComponent + type: playermarkers + showplayerfaces: true + showplayerhealth: true + # If true, show player body too (only valid if showplayerfaces=true) + showplayerbody: false + # Option to make player faces small - don't use with showplayerhealth or largeplayerfaces + smallplayerfaces: false + # Option to make player faces larger - don't use with showplayerhealth or smallplayerfaces + largeplayerfaces: false + # Optional - make player faces layer hidden by default + hidebydefault: false + # Optional - ordering priority in layer menu (low goes before high - default is 0) + layerprio: 0 + # Optional - label for player marker layer (default is 'Players') + label: "Players" + + #- class: org.dynmap.ClientComponent + # type: digitalclock + - class: org.dynmap.ClientComponent + type: link + + - class: org.dynmap.ClientComponent + type: timeofdayclock + showdigitalclock: true + #showweather: true + # Mouse pointer world coordinate display + - class: org.dynmap.ClientComponent + type: coord + label: "Location" + hidey: false + show-mcr: false + show-chunk: false + + # Note: more than one logo component can be defined + #- class: org.dynmap.ClientComponent + # type: logo + # text: "Dynmap" + # #logourl: "images/block_surface.png" + # linkurl: "http://forums.bukkit.org/threads/dynmap.489/" + # # Valid positions: top-left, top-right, bottom-left, bottom-right + # position: bottom-right + + #- class: org.dynmap.ClientComponent + # type: inactive + # timeout: 1800 # in seconds (1800 seconds = 30 minutes) + # redirecturl: inactive.html + # #showmessage: 'You were inactive for too long.' + + #- class: org.dynmap.TestComponent + # stuff: "This is some configuration-value" + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +display-whitelist: false + +# How often a tile gets rendered (in seconds). +renderinterval: 1 + +# How many tiles on update queue before accelerate render interval +renderacceleratethreshold: 60 + +# How often to render tiles when backlog is above renderacceleratethreshold +renderaccelerateinterval: 0.2 + +# How many update tiles to work on at once (if not defined, default is 1/2 the number of cores) +tiles-rendered-at-once: 2 + +# If true, use normal priority threads for rendering (versus low priority) - this can keep rendering +# from starving on busy Windows boxes (Linux JVMs pretty much ignore thread priority), but may result +# in more competition for CPU resources with other processes +usenormalthreadpriority: true + +# Save and restore pending tile renders - prevents their loss on server shutdown or /reload +saverestorepending: true + +# Save period for pending jobs (in seconds): periodic saving for crash recovery of jobs +save-pending-period: 900 + +# Zoom-out tile update period - how often to scan for and process tile updates into zoom-out tiles (in seconds) +zoomoutperiod: 30 + +# Control whether zoom out tiles are validated on startup (can be needed if zoomout processing is interrupted, but can be expensive on large maps) +initial-zoomout-validate: true + +# Default delay on processing of updated tiles, in seconds. This can reduce potentially expensive re-rendering +# of frequently updated tiles (such as due to machines, pistons, quarries or other automation). Values can +# also be set on individual worlds and individual maps. +tileupdatedelay: 30 + +# Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable +enabletilehash: true + +# Optional - hide ores: render as normal stone (so that they aren't revealed by maps) +#hideores: true + +# Optional - enabled BetterGrass style rendering of grass and snow block sides +#better-grass: true + +# Optional - enable smooth lighting by default on all maps supporting it (can be set per map as lighting option) +smooth-lighting: true + +# Optional - use world provider lighting table (good for custom worlds with custom lighting curves, like nether) +# false=classic Dynmap lighting curve +use-brightness-table: true + +# Optional - render specific block names using the textures and models of another block name: can be used to hide/disguise specific +# blocks (e.g. make ores look like stone, hide chests) or to provide simple support for rendering unsupported custom blocks +block-alias: +# "minecraft:quartz_ore": "stone" +# "diamond_ore": "coal_ore" + +# Default image format for HDMaps (png, jpg, jpg-q75, jpg-q80, jpg-q85, jpg-q90, jpg-q95, jpg-q100, webp, webp-q75, webp-q80, webp-q85, webp-q90, webp-q95, webp-q100, webp-l), +# Note: any webp format requires the presence of the 'webp command line tools' (cwebp, dwebp) (https://developers.google.com/speed/webp/download) +# +# Has no effect on maps with explicit format settings +image-format: jpg-q90 + +# If cwebp or dwebp are not on the PATH, use these settings to provide their full path. Do not use these settings if the tools are on the PATH +# For Windows, include .exe +# +#cwebpPath: /usr/bin/cwebp +#dwebpPath: /usr/bin/dwebp + +# use-generated-textures: if true, use generated textures (same as client); false is static water/lava textures +# correct-water-lighting: if true, use corrected water lighting (same as client); false is legacy water (darker) +# transparent-leaves: if true, leaves are transparent (lighting-wise): false is needed for some Spout versions that break lighting on leaf blocks +use-generated-textures: true +correct-water-lighting: true +transparent-leaves: true + +# ctm-support: if true, Connected Texture Mod (CTM) in texture packs is enabled (default) +ctm-support: true +# custom-colors-support: if true, Custom Colors in texture packs is enabled (default) +custom-colors-support: true + +# Control loading of player faces (if set to false, skins are never fetched) +#fetchskins: false + +# Control updating of player faces, once loaded (if faces are being managed by other apps or manually) +#refreshskins: false + +# Customize URL used for fetching player skins (%player% is macro for name, %uuid% for UUID) +skin-url: "http://skins.minecraft.net/MinecraftSkins/%player%.png" + +# Control behavior for new (1.0+) compass orientation (sunrise moved 90 degrees: east is now what used to be south) +# default is 'newrose' (preserve pre-1.0 maps, rotate rose) +# 'newnorth' is used to rotate maps and rose (requires fullrender of any HDMap map - same as 'newrose' for FlatMap or KzedMap) +compass-mode: newnorth + +# Triggers for automatic updates : blockupdate-with-id is debug for breaking down updates by ID:meta +# To disable, set just 'none' and comment/delete the rest +render-triggers: + - blockupdate + #- blockupdate-with-id + - chunkgenerate + #- none + +# Title for the web page - if not specified, defaults to the server's name (unless it is the default of 'Unknown Server') +#webpage-title: "My Awesome Server Map" + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# If set to false, disable extraction of webpath content (good if using custom web UI or 3rd party web UI) +# Note: web interface is unsupported in this configuration - you're on your own +update-webpath-files: true + +# The path were the /dynmapexp command exports OBJ ZIP files +exportpath: export + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +# If not set, uses same setting as server in server.properties (or 0.0.0.0 if not specified) +#webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Maximum concurrent session on internal web server - limits resources used in Bukkit server +max-sessions: 30 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Enable/disable having the web server allow symbolic links (true=compatible with existing code, false=more secure (default)) +allow-symlinks: true + +# Enable login support +login-enabled: false +# Require login to access website (requires login-enabled: true) +login-required: false + +# Period between tile renders for fullrender, in seconds (non-zero to pace fullrenders, lessen CPU load) +timesliceinterval: 0.0 + +# Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load +maxchunkspertick: 200 + +# Progress report interval for fullrender/radiusrender, in tiles. Must be 100 or greater +progressloginterval: 100 + +# Parallel fullrender: if defined, number of concurrent threads used for fullrender or radiusrender +# Note: setting this will result in much more intensive CPU use, some additional memory use. Caution should be used when +# setting this to equal or exceed the number of physical cores on the system. +#parallelrendercnt: 4 + +# Interval the browser should poll for updates. +updaterate: 2000 + +# If nonzero, server will pause fullrender/radiusrender processing when 'fullrenderplayerlimit' or more users are logged in +fullrenderplayerlimit: 0 +# If nonzero, server will pause update render processing when 'updateplayerlimit' or more users are logged in +updateplayerlimit: 0 +# Target limit on server thread use - msec per tick +per-tick-time-limit: 50 +# If TPS of server is below this setting, update renders processing is paused +update-min-tps: 18.0 +# If TPS of server is below this setting, full/radius renders processing is paused +fullrender-min-tps: 18.0 +# If TPS of server is below this setting, zoom out processing is paused +zoomout-min-tps: 18.0 + +showplayerfacesinmenu: true + +# Control whether players that are hidden or not on current map are grayed out (true=yes) +grayplayerswhenhidden: true + +# Set sidebaropened: 'true' to pin menu sidebar opened permanently, 'pinned' to default the sidebar to pinned, but allow it to unpin +#sidebaropened: true + +# Customized HTTP response headers - add 'id: value' pairs to all HTTP response headers (internal web server only) +#http-response-headers: +# Access-Control-Allow-Origin: "my-domain.com" +# X-Custom-Header-Of-Mine: "MyHeaderValue" + +# Trusted proxies for web server - which proxy addresses are trusted to supply valid X-Forwarded-For fields +# This now supports both IP address, and subnet ranges (e.g. 192.168.1.0/24 or 202.24.0.0/14 ) +trusted-proxies: + - "127.0.0.1" + - "0:0:0:0:0:0:0:1" + +joinmessage: "%playername% joined" +quitmessage: "%playername% quit" +spammessage: "You may only chat once every %interval% seconds." +# format for messages from web: %playername% substitutes sender ID (typically IP), %message% includes text +webmsgformat: "&color;2[WEB] %playername%: &color;f%message%" + +# Control whether layer control is presented on the UI (default is true) +showlayercontrol: true + +# Enable checking for banned IPs via banned-ips.txt (internal web server only) +check-banned-ips: true + +# Default selection when map page is loaded +defaultzoom: 0 +defaultworld: world +defaultmap: flat +# (optional) Zoom level and map to switch to when following a player, if possible +#followzoom: 3 +#followmap: surface + +# If true, make persistent record of IP addresses used by player logins, to support web IP to player matching +persist-ids-by-ip: true + +# If true, map text to cyrillic +cyrillic-support: false + +# Messages to customize +msg: + maptypes: "Map Types" + players: "Players" + chatrequireslogin: "Chat Requires Login" + chatnotallowed: "You are not permitted to send chat messages" + hiddennamejoin: "Player joined" + hiddennamequit: "Player quit" + +# URL for client configuration (only need to be tailored for proxies or other non-standard configurations) +url: + # configuration URL + #configuration: "up/configuration" + # update URL + #update: "up/world/{world}/{timestamp}" + # sendmessage URL + #sendmessage: "up/sendmessage" + # login URL + #login: "up/login" + # register URL + #register: "up/register" + # tiles base URL + #tiles: "tiles/" + # markers base URL + #markers: "tiles/" + # Snapshot cache size, in chunks +snapshotcachesize: 500 +# Snapshot cache uses soft references (true), else weak references (false) +soft-ref-cache: true + +# Player enter/exit title messages for map markers +# +# Processing period - how often to check player positions vs markers - default is 1000ms (1 second) +#enterexitperiod: 1000 +# Title message fade in time, in ticks (0.05 second intervals) - default is 10 (1/2 second) +#titleFadeIn: 10 +# Title message stay time, in ticks (0.05 second intervals) - default is 70 (3.5 seconds) +#titleStay: 70 +# Title message fade out time, in ticks (0.05 seocnd intervals) - default is 20 (1 second) +#titleFadeOut: 20 +# Enter/exit messages use on screen titles (true - default), if false chat messages are sent instead +#enterexitUseTitle: true +# Set true if new enter messages should supercede pending exit messages (vs being queued in order), default false +#enterReplacesExits: true + +# Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server +# If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) +#publicURL: http://my.greatserver.com/dynmap + +# Set to true to enable verbose startup messages - can help with debugging map configuration problems +# Set to false for a much quieter startup log +verbose: false + +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger +# Debug: dump blocks missing render data +dump-missing-blocks: false + +# Log4J defense: string substituted for attempts to use macros in web chat +hackAttemptBlurb: "(IaM5uchA1337Haxr-Ban Me!)" diff --git a/fabric-1.19/src/main/resources/dynmap.mixins.json b/fabric-1.19/src/main/resources/dynmap.mixins.json new file mode 100644 index 00000000..ea58931e --- /dev/null +++ b/fabric-1.19/src/main/resources/dynmap.mixins.json @@ -0,0 +1,19 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.dynmap.fabric_1_19.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "BiomeEffectsAccessor", + "MinecraftServerMixin", + "PlayerManagerMixin", + "ProtoChunkMixin", + "ServerPlayerEntityMixin", + "ServerPlayNetworkHandlerMixin", + "ThreadedAnvilChunkStorageMixin", + "WorldChunkMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-1.19/src/main/resources/fabric.mod.json b/fabric-1.19/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..83e56af5 --- /dev/null +++ b/fabric-1.19/src/main/resources/fabric.mod.json @@ -0,0 +1,33 @@ +{ + "schemaVersion": 1, + "id": "dynmap", + "version": "${version}", + "name": "Dynmap", + "description": "Dynamic, Google-maps style rendered maps for your Minecraft server", + "authors": [ + "mikeprimm", + "LolHens", + "i509VCB" + ], + "contact": { + "homepage": "https://github.com/webbukkit/dynmap", + "sources": "https://github.com/webbukkit/dynmap" + }, + "license": "Apache-2.0", + "icon": "assets/dynmap/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "org.dynmap.fabric_1_19.DynmapMod" + ] + }, + "mixins": [ + "dynmap.mixins.json" + ], + + "depends": { + "fabricloader": ">=0.14.6", + "fabric": ">=0.55.2", + "minecraft": "1.19.x" + } +} diff --git a/fabric-1.19/src/main/resources/permissions.yml.example b/fabric-1.19/src/main/resources/permissions.yml.example new file mode 100644 index 00000000..a25f9adc --- /dev/null +++ b/fabric-1.19/src/main/resources/permissions.yml.example @@ -0,0 +1,27 @@ +# +# Sample permissions.yml for dynmap - trivial, flat-file based permissions for dynmap features +# To use, copy this file to dynmap/permissions.yml, and edit appropriate. File is YAML format. +# +# All operators have full permissions to all functions. +# All users receive the permissions under the 'defaultuser' section +# Specific users can be given more permissions by defining a section with their name containing their permisssions +# All permissions correspond to those documented here (https://github.com/webbukkit/dynmap/wiki/Permissions), but +# do NOT have the 'dynmap.' prefix when used here (e.g. 'dynmap.fullrender' permission is just 'fullrender' here). +# +defaultuser: + - render + - show.self + - hide.self + - sendtoweb + - stats + - marker.list + - marker.listsets + - marker.icons + - webregister + - webchat + #- marker.sign + +#playername1: +# - fullrender +# - cancelrender +# - radiusrender diff --git a/forge-1.12.2/src/main/resources/configuration.txt b/forge-1.12.2/src/main/resources/configuration.txt index 8b7fd7ff..55c511d6 100644 --- a/forge-1.12.2/src/main/resources/configuration.txt +++ b/forge-1.12.2/src/main/resources/configuration.txt @@ -461,7 +461,10 @@ soft-ref-cache: true #enterexitUseTitle: true # Set true if new enter messages should supercede pending exit messages (vs being queued in order), default false #enterReplacesExits: true - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/forge-1.14.4/src/main/resources/configuration.txt b/forge-1.14.4/src/main/resources/configuration.txt index 4e9bec12..a58a9e9d 100644 --- a/forge-1.14.4/src/main/resources/configuration.txt +++ b/forge-1.14.4/src/main/resources/configuration.txt @@ -478,7 +478,10 @@ soft-ref-cache: true # Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/forge-1.15.2/src/main/resources/configuration.txt b/forge-1.15.2/src/main/resources/configuration.txt index 4e9bec12..a58a9e9d 100644 --- a/forge-1.15.2/src/main/resources/configuration.txt +++ b/forge-1.15.2/src/main/resources/configuration.txt @@ -478,7 +478,10 @@ soft-ref-cache: true # Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/forge-1.16.5/src/main/resources/configuration.txt b/forge-1.16.5/src/main/resources/configuration.txt index 748a4f77..161202ed 100644 --- a/forge-1.16.5/src/main/resources/configuration.txt +++ b/forge-1.16.5/src/main/resources/configuration.txt @@ -478,7 +478,10 @@ soft-ref-cache: true # Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/forge-1.17.1/src/main/resources/configuration.txt b/forge-1.17.1/src/main/resources/configuration.txt index a41e9950..6931e0c1 100644 --- a/forge-1.17.1/src/main/resources/configuration.txt +++ b/forge-1.17.1/src/main/resources/configuration.txt @@ -478,7 +478,10 @@ soft-ref-cache: true # Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/forge-1.18.2/src/main/resources/configuration.txt b/forge-1.18.2/src/main/resources/configuration.txt index 748a4f77..161202ed 100644 --- a/forge-1.18.2/src/main/resources/configuration.txt +++ b/forge-1.18.2/src/main/resources/configuration.txt @@ -478,7 +478,10 @@ soft-ref-cache: true # Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/forge-1.18/src/main/resources/configuration.txt b/forge-1.18/src/main/resources/configuration.txt index 748a4f77..161202ed 100644 --- a/forge-1.18/src/main/resources/configuration.txt +++ b/forge-1.18/src/main/resources/configuration.txt @@ -478,7 +478,10 @@ soft-ref-cache: true # Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap - + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false diff --git a/forge-1.19/.gitignore b/forge-1.19/.gitignore new file mode 100644 index 00000000..84c048a7 --- /dev/null +++ b/forge-1.19/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/forge-1.19/bin/.gitignore b/forge-1.19/bin/.gitignore new file mode 100644 index 00000000..7eed456b --- /dev/null +++ b/forge-1.19/bin/.gitignore @@ -0,0 +1,2 @@ +/main/ +/test/ diff --git a/forge-1.19/build.gradle b/forge-1.19/build.gradle new file mode 100644 index 00000000..ce729e91 --- /dev/null +++ b/forge-1.19/build.gradle @@ -0,0 +1,92 @@ +buildscript { + repositories { + maven { url = 'https://files.minecraftforge.net/maven' } + jcenter() + mavenCentral() + } + dependencies { + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true + } +} +apply plugin: 'net.minecraftforge.gradle' +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'eclipse' + +eclipse { + project { + name = "Dynmap(Forge-1.19)" + } +} + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly. + +println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch')) + +ext.buildNumber = System.getenv().BUILD_NUMBER ?: "Dev" + +minecraft { + mappings channel: 'official', version: '1.19' + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + runs { + server { + workingDirectory project.file('run').canonicalPath + } + } +} + +project.archivesBaseName = "${project.archivesBaseName}-forge-1.19" + +dependencies { + implementation project(path: ":DynmapCore", configuration: "shadow") + implementation project(path: ':DynmapCoreAPI') + + minecraft 'net.minecraftforge:forge:1.19-41.0.38' +} + +processResources +{ + filesMatching('META-INF/mods.toml') { + // replace version and mcversion + expand( + version: project.version + '-' + project.ext.buildNumber, + mcversion: "1.19" + ) + } +} + +shadowJar { + dependencies { + include(dependency(':DynmapCore')) + include(dependency("commons-codec:commons-codec:")) + exclude("META-INF/maven/**") + exclude("META-INF/services/**") + } + relocate('org.apache.commons.codec', 'org.dynmap.forge_1_19.commons.codec') + + archiveName = "Dynmap-${parent.version}-forge-1.19.jar" + destinationDir = file '../target' +} + +shadowJar.doLast { + task -> + ant.checksum file: task.archivePath +} + +afterEvaluate { +reobf { + shadowJar { + mappings = createMcpToSrg.output + } +} +} + +task deobfJar(type: Jar) { + from sourceSets.main.output + classifier = 'dev' +} + +artifacts { + archives deobfJar +} + +build.dependsOn(shadowJar) diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/ClientProxy.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/ClientProxy.java new file mode 100644 index 00000000..a467dc70 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/ClientProxy.java @@ -0,0 +1,6 @@ +package org.dynmap.forge_1_19; + +public class ClientProxy extends Proxy { + public ClientProxy() { + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/DynmapMod.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/DynmapMod.java new file mode 100644 index 00000000..c9355651 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/DynmapMod.java @@ -0,0 +1,136 @@ +package org.dynmap.forge_1_19; + +import java.io.File; + +import org.apache.commons.lang3.tuple.Pair; +import org.dynmap.DynmapCommonAPI; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.Log; +import org.dynmap.forge_1_19.DynmapPlugin.OurLog; + +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.server.ServerAboutToStartEvent; +import net.minecraftforge.event.server.ServerStartedEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.IExtensionPoint; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.StartupMessageManager; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.network.NetworkConstants;; + +@Mod("dynmap") +public class DynmapMod +{ + // The instance of your mod that Forge uses. + public static DynmapMod instance; + + // Says where the client and server 'proxy' code is loaded. + public static Proxy proxy = DistExecutor.runForDist(() -> ClientProxy::new, () -> Proxy::new); + + public static DynmapPlugin plugin; + public static File jarfile; + public static String ver; + public static boolean useforcedchunks; + + public class APICallback extends DynmapCommonAPIListener { + @Override + public void apiListenerAdded() { + if(plugin == null) { + plugin = proxy.startServer(server); + } + } + @Override + public void apiEnabled(DynmapCommonAPI api) { + } + } + + //TODO + //public class LoadingCallback implements net.minecraftforge.common.ForgeChunkManager.LoadingCallback { + // @Override + // public void ticketsLoaded(List tickets, World world) { + // if(tickets.size() > 0) { + // DynmapPlugin.setBusy(world, tickets.get(0)); + // for(int i = 1; i < tickets.size(); i++) { + // ForgeChunkManager.releaseTicket(tickets.get(i)); + // } + // } + // } + //} + + public DynmapMod() { + instance = this; + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init); + + MinecraftForge.EVENT_BUS.register(this); + + ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, + ()->new IExtensionPoint.DisplayTest(()->NetworkConstants.IGNORESERVERONLY, (remote, isServer)-> true)); + + Log.setLogger(new OurLog()); + org.dynmap.modsupport.ModSupportImpl.init(); + } + + public void setup(final FMLCommonSetupEvent event) + { + //TOOO + jarfile = ModList.get().getModFileById("dynmap").getFile().getFilePath().toFile(); + + ver = ModList.get().getModContainerById("dynmap").get().getModInfo().getVersion().toString(); + + //// Load configuration file - use suggested (config/WesterosBlocks.cfg) + //Configuration cfg = new Configuration(event.getSuggestedConfigurationFile()); + //try { + // cfg.load(); + // + // useforcedchunks = cfg.get("Settings", "UseForcedChunks", true).getBoolean(true); + //} + //finally + //{ + // cfg.save(); + //} + } + + public void init(FMLLoadCompleteEvent event) + { + /* Set up for chunk loading notice from chunk manager */ + //TODO + //if(useforcedchunks) { + // ForgeChunkManager.setForcedChunkLoadingCallback(DynmapMod.instance, new LoadingCallback()); + //} + //else { + // Log.info("[Dynmap] World loading using forced chunks is disabled"); + //} + } + + private MinecraftServer server; + + @SubscribeEvent + public void onServerStarting(ServerAboutToStartEvent event) { + server = event.getServer(); + if(plugin == null) + plugin = proxy.startServer(server); + plugin.onStarting(server.getCommands().getDispatcher()); + } + + @SubscribeEvent + public void onServerStarted(ServerStartedEvent event) { + DynmapCommonAPIListener.register(new APICallback()); + plugin.serverStarted(); + } + + @SubscribeEvent + public void serverStopping(ServerStoppingEvent event) + { + proxy.stopServer(plugin); + plugin = null; + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/DynmapPlugin.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/DynmapPlugin.java new file mode 100644 index 00000000..f069f2bf --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/DynmapPlugin.java @@ -0,0 +1,2018 @@ +package org.dynmap.forge_1_19; + +import java.io.File; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.regex.Pattern; + +import net.minecraft.Util; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.core.BlockPos; +import net.minecraft.core.IdMapper; +import net.minecraft.core.Registry; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket; +import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.GameProfileCache; +import net.minecraft.server.players.UserBanList; +import net.minecraft.world.entity.Pose; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelAccessor; +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.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.ServerChatEvent; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; +import net.minecraftforge.event.entity.player.PlayerEvent.PlayerRespawnEvent; +import net.minecraftforge.event.world.BlockEvent; +import net.minecraftforge.event.world.ChunkDataEvent; +import net.minecraftforge.event.world.ChunkEvent; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.loading.LoadingModList; +import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.dynmap.ConfigurationNode; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapCommonAPIListener; +import org.dynmap.DynmapCore; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.Log; +import org.dynmap.MapManager; +import org.dynmap.PlayerList; +import org.dynmap.common.BiomeMap; +import org.dynmap.common.DynmapCommandSender; +import org.dynmap.common.DynmapPlayer; +import org.dynmap.common.DynmapServerInterface; +import org.dynmap.common.DynmapListenerManager.EventType; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.forge_1_19.DmapCommand; +import org.dynmap.forge_1_19.DmarkerCommand; +import org.dynmap.forge_1_19.DynmapCommand; +import org.dynmap.forge_1_19.permissions.FilePermissions; +import org.dynmap.forge_1_19.permissions.OpPermissions; +import org.dynmap.forge_1_19.permissions.PermissionProvider; +import org.dynmap.permissions.PermissionsHandler; +import org.dynmap.renderer.DynmapBlockState; +import org.dynmap.utils.DynmapLogger; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.VisibilityLimit; + +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.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +import net.minecraft.world.level.EmptyBlockGetter; + +public class DynmapPlugin +{ + private DynmapCore core; + private PermissionProvider permissions; + private boolean core_enabled; + public GenericChunkCache sscache; + public PlayerList playerList; + private MapManager mapManager; + private static net.minecraft.server.MinecraftServer server; + public static DynmapPlugin plugin; + private ChatHandler chathandler; + private HashMap sortWeights = new HashMap(); + // Drop world load ticket after 30 seconds + private long worldIdleTimeoutNS = 30 * 1000000000L; + private HashMap worlds = new HashMap(); + private LevelAccessor last_world; + private ForgeWorld last_fworld; + private Map players = new HashMap(); + //TODO private ForgeMetrics metrics; + private HashSet modsused = new HashSet(); + private ForgeServer fserver = new ForgeServer(); + private boolean tickregistered = false; + // TPS calculator + private double tps; + private long lasttick; + private long avgticklen; + // Per tick limit, in nsec + private long perTickLimit = (50000000); // 50 ms + private boolean useSaveFolder = true; + + private static final String[] TRIGGER_DEFAULTS = { "blockupdate", "chunkpopulate", "chunkgenerate" }; + + private static final Pattern patternControlCode = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]"); + + public static class BlockUpdateRec { + LevelAccessor w; + String wid; + int x, y, z; + } + ConcurrentLinkedQueue blockupdatequeue = new ConcurrentLinkedQueue(); + + public static DynmapBlockState[] stateByID; + + private Map knownloadedchunks = new HashMap(); + private boolean didInitialKnownChunks = false; + private void addKnownChunk(ForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset == null) { + cset = new LongOpenHashSet(); + knownloadedchunks.put(fw.getName(), cset); + } + cset.add(pos.toLong()); + } + private void removeKnownChunk(ForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + cset.remove(pos.toLong()); + } + } + private boolean checkIfKnownChunk(ForgeWorld fw, ChunkPos pos) { + LongOpenHashSet cset = knownloadedchunks.get(fw.getName()); + if (cset != null) { + return cset.contains(pos.toLong()); + } + return false; + } + + private static Registry reg = null; + + private static Registry getBiomeReg() { + if (reg == null) { + reg = server.registryAccess().ownedRegistryOrThrow(Registry.BIOME_REGISTRY); + } + return reg; + } + + /** + * Initialize block states (org.dynmap.blockstate.DynmapBlockState) + */ + public void initializeBlockStates() { + stateByID = new DynmapBlockState[512*32]; // Simple map - scale as needed + Arrays.fill(stateByID, DynmapBlockState.AIR); // Default to air + + IdMapper bsids = Block.BLOCK_STATE_REGISTRY; + + DynmapBlockState basebs = null; + Block baseb = null; + int baseidx = 0; + + Iterator iter = bsids.iterator(); + DynmapBlockState.Builder bld = new DynmapBlockState.Builder(); + while (iter.hasNext()) { + BlockState bs = iter.next(); + int idx = bsids.getId(bs); + if (idx >= stateByID.length) { + int plen = stateByID.length; + stateByID = Arrays.copyOf(stateByID, idx*11/10); // grow array by 10% + Arrays.fill(stateByID, plen, stateByID.length, DynmapBlockState.AIR); + } + Block b = bs.getBlock(); + // If this is new block vs last, it's the base block state + if (b != baseb) { + basebs = null; + baseidx = idx; + baseb = b; + } + ResourceLocation ui = Registry.BLOCK.getKey(b); + + if (ui == null) { + continue; + } + String bn = ui.getNamespace() + ":" + ui.getPath(); + // Only do defined names, and not "air" + if (!bn.equals(DynmapBlockState.AIR_BLOCK)) { + Material mat = bs.getMaterial(); + String statename = ""; + for (net.minecraft.world.level.block.state.properties.Property p : bs.getProperties()) { + if (statename.length() > 0) { + statename += ","; + } + statename += p.getName() + "=" + bs.getValue(p).toString(); + } + int lightAtten = 15; + try { // Workaround for mods with broken block state logic... + lightAtten =bs.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 15 : (bs.propagatesSkylightDown(EmptyBlockGetter.INSTANCE, BlockPos.ZERO) ? 0 : 1); + } catch (Exception x) { + Log.warning(String.format("Exception while checking lighting data for block state: %s[%s]", bn, statename)); + Log.verboseinfo("Exception: " + x.toString()); + } + //Log.info("statename=" + bn + "[" + statename + "], lightAtten=" + lightAtten); + // Fill in base attributes + bld.setBaseState(basebs).setStateIndex(idx - baseidx).setBlockName(bn).setStateName(statename).setMaterial(mat.toString()).setLegacyBlockID(idx).setAttenuatesLight(lightAtten); + if (mat.isSolid()) { bld.setSolid(); } + if (mat == Material.AIR) { bld.setAir(); } + if (mat == Material.WOOD) { bld.setLog(); } + if (mat == Material.LEAVES) { bld.setLeaves(); } + if ((!bs.getFluidState().isEmpty()) && !(bs.getBlock() instanceof LiquidBlock)) { + bld.setWaterlogged(); + } + DynmapBlockState dbs = bld.build(); // Build state + stateByID[idx] = dbs; + if (basebs == null) { basebs = dbs; } + } + } + for (int gidx = 0; gidx < DynmapBlockState.getGlobalIndexMax(); gidx++) { + DynmapBlockState bs = DynmapBlockState.getStateByGlobalIndex(gidx); + //Log.info(gidx + ":" + bs.toString() + ", gidx=" + bs.globalStateIndex + ", sidx=" + bs.stateIndex); + } + } + + //public static final Item getItemByID(int id) { + // return Item.getItemById(id); + //} + + private static Biome[] biomelist = null; + + public static final Biome[] getBiomeList() { + 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; + } + //public static final NetworkManager getNetworkManager(ServerPlayNetHandler nh) { + // return nh.netManager; + //} + + private ForgePlayer getOrAddPlayer(ServerPlayer p) { + String name = p.getName().getString(); + ForgePlayer fp = players.get(name); + if(fp != null) { + fp.player = p; + } + else { + fp = new ForgePlayer(p); + players.put(name, fp); + } + return fp; + } + + private static class TaskRecord implements Comparable + { + private long ticktorun; + private long id; + private FutureTask future; + @Override + public int compareTo(Object o) + { + TaskRecord tr = (TaskRecord)o; + + if (this.ticktorun < tr.ticktorun) + { + return -1; + } + else if (this.ticktorun > tr.ticktorun) + { + return 1; + } + else if (this.id < tr.id) + { + return -1; + } + else if (this.id > tr.id) + { + return 1; + } + else + { + return 0; + } + } + } + + private class ChatMessage { + String message; + ServerPlayer sender; + } + private ConcurrentLinkedQueue msgqueue = new ConcurrentLinkedQueue(); + + public class ChatHandler { + @SubscribeEvent + public void handleChat(ServerChatEvent event) { + String msg = event.getMessage(); + if(!msg.startsWith("/")) { + ChatMessage cm = new ChatMessage(); + cm.message = msg; + cm.sender = event.getPlayer(); + msgqueue.add(cm); + } + } + } + + /** TODO: depends on forge chunk manager + private static class WorldBusyRecord { + long last_ts; + Ticket ticket; + } + private static HashMap busy_worlds = new HashMap(); + + private void setBusy(World w) { + setBusy(w, null); + } + static void setBusy(World w, Ticket t) { + if(w == null) return; + if (!DynmapMod.useforcedchunks) return; + WorldBusyRecord wbr = busy_worlds.get(w.provider.getDimension()); + if(wbr == null) { // Not busy, make ticket and keep spawn loaded + Debug.debug("World " + w.getWorldInfo().getWorldName() + "/"+ w.provider.getDimensionType().getName() + " is busy"); + wbr = new WorldBusyRecord(); + if(t != null) + wbr.ticket = t; + else + wbr.ticket = ForgeChunkManager.requestTicket(DynmapMod.instance, w, ForgeChunkManager.Type.NORMAL); + if(wbr.ticket != null) { + BlockPos cc = w.getSpawnPoint(); + ChunkPos ccip = new ChunkPos(cc.getX() >> 4, cc.getZ() >> 4); + ForgeChunkManager.forceChunk(wbr.ticket, ccip); + busy_worlds.put(w.provider.getDimension(), wbr); // Add to busy list + } + } + wbr.last_ts = System.nanoTime(); + } + + private void doIdleOutOfWorlds() { + if (!DynmapMod.useforcedchunks) return; + long ts = System.nanoTime() - worldIdleTimeoutNS; + for(Iterator itr = busy_worlds.values().iterator(); itr.hasNext();) { + WorldBusyRecord wbr = itr.next(); + if(wbr.last_ts < ts) { + World w = wbr.ticket.world; + Debug.debug("World " + w.getWorldInfo().getWorldName() + "/" + wbr.ticket.world.provider.getDimensionType().getName() + " is idle"); + if (wbr.ticket != null) + ForgeChunkManager.releaseTicket(wbr.ticket); // Release hold on world + itr.remove(); + } + } + } + */ + + public static class OurLog implements DynmapLogger { + Logger log; + public static final String DM = "[Dynmap] "; + OurLog() { + log = LogManager.getLogger("Dynmap"); + } + @Override + public void info(String s) { + log.info(DM + s); + } + + @Override + public void severe(Throwable t) { + log.fatal(t); + } + + @Override + public void severe(String s) { + log.fatal(DM + s); + } + + @Override + public void severe(String s, Throwable t) { + log.fatal(DM + s, t); + } + + @Override + public void verboseinfo(String s) { + log.info(DM + s); + } + + @Override + public void warning(String s) { + log.warn(DM + s); + } + + @Override + public void warning(String s, Throwable t) { + log.warn(DM + s, t); + } + } + + public DynmapPlugin(MinecraftServer srv) + { + plugin = this; + this.server = srv; + } + + public boolean isOp(String player) { + String[] ops = server.getPlayerList().getOps().getUserList(); + for (String op : ops) { + if (op.equalsIgnoreCase(player)) { + return true; + } + } + return (server.isSingleplayer() && player.equalsIgnoreCase(server.getSingleplayerProfile().getName())); + } + + private boolean hasPerm(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermission(psender.getName().getString(), permission)) { + return true; + } + return permissions.has(psender, permission); + } + + private boolean hasPermNode(ServerPlayer psender, String permission) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if ((psender != null) && (ph != null) && ph.hasPermissionNode(psender.getName().getString(), permission)) { + return true; + } + return permissions.hasPermissionNode(psender, permission); + } + + private Set hasOfflinePermissions(String player, Set perms) { + Set rslt = null; + PermissionsHandler ph = PermissionsHandler.getHandler(); + if(ph != null) { + rslt = ph.hasOfflinePermissions(player, perms); + } + Set rslt2 = hasOfflinePermissions(player, perms); + if((rslt != null) && (rslt2 != null)) { + Set newrslt = new HashSet(rslt); + newrslt.addAll(rslt2); + rslt = newrslt; + } + else if(rslt2 != null) { + rslt = rslt2; + } + return rslt; + } + private boolean hasOfflinePermission(String player, String perm) { + PermissionsHandler ph = PermissionsHandler.getHandler(); + if(ph != null) { + if(ph.hasOfflinePermission(player, perm)) { + return true; + } + } + return permissions.hasOfflinePermission(player, perm); + } + + /** + * Server access abstraction class + */ + public class ForgeServer extends DynmapServerInterface + { + /* Server thread scheduler */ + private Object schedlock = new Object(); + private long cur_tick; + private long next_id; + private long cur_tick_starttime; + private PriorityQueue runqueue = new PriorityQueue(); + + public ForgeServer() { + } + + private GameProfile getProfileByName(String player) { + GameProfileCache cache = server.getProfileCache(); + Optional val = cache.get(player); + return val.isPresent() ? val.get() : null; + } + + @Override + public int getBlockIDAt(String wname, int x, int y, int z) { + return -1; + } + + @Override + public int isSignAt(String wname, int x, int y, int z) { + return -1; + } + + @Override + public void scheduleServerTask(Runnable run, long delay) + { + TaskRecord tr = new TaskRecord(); + tr.future = new FutureTask(run, null); + + /* Add task record to queue */ + synchronized (schedlock) + { + tr.id = next_id++; + tr.ticktorun = cur_tick + delay; + runqueue.add(tr); + } + } + @Override + public DynmapPlayer[] getOnlinePlayers() + { + if(server.getPlayerList() == null) + return new DynmapPlayer[0]; + List playlist = server.getPlayerList().getPlayers(); + int pcnt = playlist.size(); + DynmapPlayer[] dplay = new DynmapPlayer[pcnt]; + + for (int i = 0; i < pcnt; i++) + { + ServerPlayer p = playlist.get(i); + dplay[i] = getOrAddPlayer(p); + } + + return dplay; + } + @Override + public void reload() + { + plugin.onDisable(); + plugin.onEnable(); + plugin.onStart(); + } + @Override + public DynmapPlayer getPlayer(String name) + { + List players = server.getPlayerList().getPlayers(); + + for (ServerPlayer p : players) + { + if (p.getName().getString().equalsIgnoreCase(name)) + { + return getOrAddPlayer(p); + } + } + + return null; + } + @Override + public Set getIPBans() + { + UserBanList bl = server.getPlayerList().getBans(); + Set ips = new HashSet(); + + for (String s : bl.getUserList()) { + ips.add(s); + } + + return ips; + } + @Override + public Future callSyncMethod(Callable task) { + return callSyncMethod(task, 0); + } + public Future callSyncMethod(Callable task, long delay) + { + TaskRecord tr = new TaskRecord(); + FutureTask ft = new FutureTask(task); + tr.future = ft; + + /* Add task record to queue */ + synchronized (schedlock) + { + tr.id = next_id++; + tr.ticktorun = cur_tick + delay; + runqueue.add(tr); + } + + return ft; + } + @Override + public String getServerName() + { + String sn; + if (server.isSingleplayer()) + sn = "Integrated"; + else + sn = server.getLocalIp(); + if(sn == null) sn = "Unknown Server"; + return sn; + } + @Override + public boolean isPlayerBanned(String pid) + { + UserBanList bl = server.getPlayerList().getBans(); + return bl.isBanned(getProfileByName(pid)); + } + + @Override + public String stripChatColor(String s) + { + return patternControlCode.matcher(s).replaceAll(""); + } + private Set registered = new HashSet(); + @Override + public boolean requestEventNotification(EventType type) + { + if (registered.contains(type)) + { + return true; + } + + switch (type) + { + case WORLD_LOAD: + case WORLD_UNLOAD: + /* Already called for normal world activation/deactivation */ + break; + + case WORLD_SPAWN_CHANGE: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onSpawnChange(SpawnChangeEvent evt) { + DynmapWorld w = new BukkitWorld(evt.getWorld()); + core.listenerManager.processWorldEvent(EventType.WORLD_SPAWN_CHANGE, w); + } + }, DynmapPlugin.this); + */ + break; + + case PLAYER_JOIN: + case PLAYER_QUIT: + /* Already handled */ + break; + + case PLAYER_BED_LEAVE: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onPlayerBedLeave(PlayerBedLeaveEvent evt) { + DynmapPlayer p = new BukkitPlayer(evt.getPlayer()); + core.listenerManager.processPlayerEvent(EventType.PLAYER_BED_LEAVE, p); + } + }, DynmapPlugin.this); + */ + break; + + case PLAYER_CHAT: + if (chathandler == null) { + chathandler = new ChatHandler(); + MinecraftForge.EVENT_BUS.register(chathandler); + } + break; + + case BLOCK_BREAK: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onBlockBreak(BlockBreakEvent evt) { + if(evt.isCancelled()) return; + Block b = evt.getBlock(); + if(b == null) return; + Location l = b.getLocation(); + core.listenerManager.processBlockEvent(EventType.BLOCK_BREAK, b.getType().getId(), + BukkitWorld.normalizeWorldName(l.getWorld().getName()), l.getBlockX(), l.getBlockY(), l.getBlockZ()); + } + }, DynmapPlugin.this); + */ + break; + + case SIGN_CHANGE: + /*TODO + pm.registerEvents(new Listener() { + @EventHandler(priority=EventPriority.MONITOR) + public void onSignChange(SignChangeEvent evt) { + if(evt.isCancelled()) return; + Block b = evt.getBlock(); + Location l = b.getLocation(); + String[] lines = evt.getLines(); + DynmapPlayer dp = null; + Player p = evt.getPlayer(); + if(p != null) dp = new BukkitPlayer(p); + core.listenerManager.processSignChangeEvent(EventType.SIGN_CHANGE, b.getType().getId(), + BukkitWorld.normalizeWorldName(l.getWorld().getName()), l.getBlockX(), l.getBlockY(), l.getBlockZ(), lines, dp); + } + }, DynmapPlugin.this); + */ + break; + + default: + Log.severe("Unhandled event type: " + type); + return false; + } + + registered.add(type); + return true; + } + @Override + public boolean sendWebChatEvent(String source, String name, String msg) + { + return DynmapCommonAPIListener.fireWebChatEvent(source, name, msg); + } + @Override + public void broadcastMessage(String msg) + { + Component component = Component.literal(msg); + server.getPlayerList().broadcastSystemMessage(component, ChatType.SYSTEM); + Log.info(stripChatColor(msg)); + } + @Override + public String[] getBiomeIDs() + { + BiomeMap[] b = BiomeMap.values(); + String[] bname = new String[b.length]; + + for (int i = 0; i < bname.length; i++) + { + bname[i] = b[i].toString(); + } + + return bname; + } + @Override + public double getCacheHitRate() + { + if(sscache != null) + return sscache.getHitRate(); + return 0.0; + } + @Override + public void resetCacheStats() + { + if(sscache != null) + sscache.resetStats(); + } + @Override + public DynmapWorld getWorldByName(String wname) + { + return DynmapPlugin.this.getWorldByName(wname); + } + @Override + public DynmapPlayer getOfflinePlayer(String name) + { + /* + OfflinePlayer op = getServer().getOfflinePlayer(name); + if(op != null) { + return new BukkitPlayer(op); + } + */ + return null; + } + @Override + public Set checkPlayerPermissions(String player, Set perms) + { + net.minecraft.server.players.PlayerList scm = server.getPlayerList(); + if (scm == null) return Collections.emptySet(); + UserBanList bl = scm.getBans(); + if (bl == null) return Collections.emptySet(); + if(bl.isBanned(getProfileByName(player))) { + return Collections.emptySet(); + } + Set rslt = hasOfflinePermissions(player, perms); + if (rslt == null) { + rslt = new HashSet(); + if(plugin.isOp(player)) { + rslt.addAll(perms); + } + } + return rslt; + } + @Override + public boolean checkPlayerPermission(String player, String perm) + { + net.minecraft.server.players.PlayerList scm = server.getPlayerList(); + if (scm == null) return false; + UserBanList bl = scm.getBans(); + if (bl == null) return false; + if(bl.isBanned(getProfileByName(player))) { + return false; + } + return hasOfflinePermission(player, perm); + } + /** + * Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread + */ + @Override + public MapChunkCache createMapChunkCache(DynmapWorld w, List chunks, + boolean blockdata, boolean highesty, boolean biome, boolean rawbiome) + { + ForgeMapChunkCache c = (ForgeMapChunkCache) w.getChunkCache(chunks); + if(c == null) { + return null; + } + if (w.visibility_limits != null) + { + for (VisibilityLimit limit: w.visibility_limits) + { + c.setVisibleRange(limit); + } + + c.setHiddenFillStyle(w.hiddenchunkstyle); + } + + if (w.hidden_limits != null) + { + for (VisibilityLimit limit: w.hidden_limits) + { + c.setHiddenRange(limit); + } + + c.setHiddenFillStyle(w.hiddenchunkstyle); + } + + if (chunks.size() == 0) /* No chunks to get? */ + { + c.loadChunks(0); + return c; + } + + //Now handle any chunks in server thread that are already loaded (on server thread) + final ForgeMapChunkCache cc = c; + Future f = this.callSyncMethod(new Callable() { + public Boolean call() throws Exception { + // Update busy state on world + ForgeWorld fw = (ForgeWorld)cc.getWorld(); + //TODO + //setBusy(fw.getWorld()); + cc.getLoadedChunks(); + return true; + } + }, 0); + try { + f.get(); + } + catch (CancellationException cx) { + return null; + } + catch (InterruptedException cx) { + return null; + } + catch (ExecutionException xx) { + Log.severe("Exception while loading chunks", xx.getCause()); + return null; + } + catch (Exception ix) { + Log.severe(ix); + return null; + } + if(w.isLoaded() == false) { + return null; + } + // Now, do rest of chunk reading from calling thread + c.readChunks(chunks.size()); + + return c; + } + @Override + public int getMaxPlayers() + { + return server.getMaxPlayers(); + } + @Override + public int getCurrentPlayers() + { + return server.getPlayerList().getPlayerCount(); + } + + @SubscribeEvent + public void tickEvent(TickEvent.ServerTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + return; + } + cur_tick_starttime = System.nanoTime(); + long elapsed = cur_tick_starttime - lasttick; + lasttick = cur_tick_starttime; + avgticklen = ((avgticklen * 99) / 100) + (elapsed / 100); + tps = (double)1E9 / (double)avgticklen; + // Tick core + if (core != null) { + core.serverTick(tps); + } + + boolean done = false; + TaskRecord tr = null; + + while(!blockupdatequeue.isEmpty()) { + BlockUpdateRec r = blockupdatequeue.remove(); + BlockState bs = r.w.getBlockState(new BlockPos(r.x, r.y, r.z)); + int idx = Block.BLOCK_STATE_REGISTRY.getId(bs); + if((idx >= 0) && (!org.dynmap.hdmap.HDBlockModels.isChangeIgnoredBlock(stateByID[idx]))) { + if(onblockchange_with_id) + mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange[" + idx + "]"); + else + mapManager.touch(r.wid, r.x, r.y, r.z, "blockchange"); + } + } + + long now; + + synchronized(schedlock) { + cur_tick++; + now = System.nanoTime(); + tr = runqueue.peek(); + /* Nothing due to run */ + if((tr == null) || (tr.ticktorun > cur_tick) || ((now - cur_tick_starttime) > perTickLimit)) { + done = true; + } + else { + tr = runqueue.poll(); + } + } + while (!done) { + tr.future.run(); + + synchronized(schedlock) { + tr = runqueue.peek(); + now = System.nanoTime(); + /* Nothing due to run */ + if((tr == null) || (tr.ticktorun > cur_tick) || ((now - cur_tick_starttime) > perTickLimit)) { + done = true; + } + else { + tr = runqueue.poll(); + } + } + } + while(!msgqueue.isEmpty()) { + ChatMessage cm = msgqueue.poll(); + DynmapPlayer dp = null; + if(cm.sender != null) + dp = getOrAddPlayer(cm.sender); + else + dp = new ForgePlayer(null); + + core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, cm.message); + } + // Check for generated chunks + if((cur_tick % 20) == 0) { + } + } + + @Override + public boolean isModLoaded(String name) { + boolean loaded = ModList.get().isLoaded(name); + if (loaded) { + modsused.add(name); + } + return loaded; + } + @Override + public String getModVersion(String name) { + Optional mod = ModList.get().getModContainerById(name); // Try case sensitive lookup + if (mod.isPresent()) { + ArtifactVersion vi = mod.get().getModInfo().getVersion(); + return vi.getMajorVersion() + "." + vi.getMinorVersion() + "." + vi.getIncrementalVersion(); + } + return null; + } + @Override + public double getServerTPS() { + return tps; + } + + @Override + public String getServerIP() { + if (server.isSingleplayer()) + return "0.0.0.0"; + else + return server.getLocalIp(); + } + @Override + public File getModContainerFile(String name) { + ModFileInfo mfi = LoadingModList.get().getModFileById(name); // Try case sensitive lookup + if (mfi != null) { + File f = mfi.getFile().getFilePath().toFile(); + return f; + } + return null; + } + @Override + public List getModList() { + List mil = LoadingModList.get().getMods(); + List lst = new ArrayList(); + for (ModInfo mi : mil) { + lst.add(mi.getModId()); + } + return lst; + } + + @Override + public Map getBlockIDMap() { + Map map = new HashMap(); + return map; + } + + @Override + public InputStream openResource(String modid, String rname) { + if (modid == null) modid = "minecraft"; + + Optional mc = ModList.get().getModContainerById(modid); + Object mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod != null) { + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.getResourceAsStream(rname); + if (is != null) { + return is; + } + } + List mcl = LoadingModList.get().getMods(); + for (ModInfo mci : mcl) { + mc = ModList.get().getModContainerById(mci.getModId()); + mod = (mc.isPresent()) ? mc.get().getMod() : null; + if (mod == null) continue; + ClassLoader cl = mod.getClass().getClassLoader(); + if (cl == null) cl = ClassLoader.getSystemClassLoader(); + InputStream is = cl.getResourceAsStream(rname); + if (is != null) { + return is; + } + } + return null; + } + /** + * Get block unique ID map (module:blockid) + */ + @Override + public Map getBlockUniqueIDMap() { + HashMap map = new HashMap(); + return map; + } + /** + * Get item unique ID map (module:itemid) + */ + @Override + public Map getItemUniqueIDMap() { + HashMap map = new HashMap(); + return map; + } + + } + private static final Gson gson = new GsonBuilder().create(); + + public class TexturesPayload { + public long timestamp; + public String profileId; + public String profileName; + public boolean isPublic; + public Map textures; + + } + public class ProfileTexture { + public String url; + } + + /** + * Player access abstraction class + */ + public class ForgePlayer extends ForgeCommandSender implements DynmapPlayer + { + private ServerPlayer player; + private final String skinurl; + private final UUID uuid; + + + public ForgePlayer(ServerPlayer p) + { + player = p; + String url = null; + if (player != null) { + uuid = player.getUUID(); + GameProfile prof = player.getGameProfile(); + if (prof != null) { + Property textureProperty = Iterables.getFirst(prof.getProperties().get("textures"), null); + + if (textureProperty != null) { + TexturesPayload result = null; + try { + String json = new String(Base64.getDecoder().decode(textureProperty.getValue()), StandardCharsets.UTF_8); + result = gson.fromJson(json, TexturesPayload.class); + } catch (JsonParseException e) { + } + if ((result != null) && (result.textures != null) && (result.textures.containsKey("SKIN"))) { + url = result.textures.get("SKIN").url; + } + } + } + } + else { + uuid = null; + } + skinurl = url; + } + @Override + public boolean isConnected() + { + return true; + } + @Override + public String getName() + { + if(player != null) { + String n = player.getName().getString();; + return n; + } + else + return "[Server]"; + } + @Override + public String getDisplayName() + { + if(player != null) { + String n = player.getDisplayName().getString(); + return n; + } + else + return "[Server]"; + } + @Override + public boolean isOnline() + { + return true; + } + @Override + public DynmapLocation getLocation() + { + if (player == null) { + return null; + } + Vec3 v = player.position(); + return toLoc(player.getLevel(), v.x, v.y, v.z); + } + @Override + public String getWorld() + { + if (player == null) + { + return null; + } + + if (player.level != null) + { + return DynmapPlugin.this.getWorld((ServerLevel)player.level).getName(); + } + + return null; + } + public static final Connection getNetworkManager(ServerGamePacketListenerImpl nh) { + return nh.connection; + } + + @Override + public InetSocketAddress getAddress() + { + if((player != null) && (player instanceof ServerPlayer)) { + ServerGamePacketListenerImpl nsh = ((ServerPlayer)player).connection; + if((nsh != null) && (getNetworkManager(nsh) != null)) { + SocketAddress sa = getNetworkManager(nsh).getRemoteAddress(); + if(sa instanceof InetSocketAddress) { + return (InetSocketAddress)sa; + } + } + } + return null; + } + @Override + public boolean isSneaking() + { + if (player != null) + { + return player.getPose() == Pose.CROUCHING; + } + + return false; + } + @Override + public double getHealth() + { + if (player != null) + { + double h = player.getHealth(); + if(h > 20) h = 20; + return h; // Scale to 20 range + } + else + { + return 0; + } + } + @Override + public int getArmorPoints() + { + if (player != null) + { + return player.getArmorValue(); + } + else + { + return 0; + } + } + @Override + public DynmapLocation getBedSpawnLocation() + { + return null; + } + @Override + public long getLastLoginTime() + { + return 0; + } + @Override + public long getFirstLoginTime() + { + return 0; + } + @Override + public boolean hasPrivilege(String privid) + { + if(player != null) + return hasPerm(player, privid); + return false; + } + @Override + public boolean isOp() + { + return DynmapPlugin.this.isOp(player.getName().getString()); + } + @Override + public void sendMessage(String msg) + { + Component ichatcomponent = Component.literal(msg); + player.sendSystemMessage(ichatcomponent); + } + @Override + public boolean isInvisible() { + if(player != null) { + return player.isInvisible(); + } + return false; + } + @Override + public int getSortWeight() { + Integer wt = sortWeights.get(getName()); + if (wt != null) + return wt; + return 0; + } + @Override + public void setSortWeight(int wt) { + if (wt == 0) { + sortWeights.remove(getName()); + } + else { + sortWeights.put(getName(), wt); + } + } + @Override + public boolean hasPermissionNode(String node) { + if(player != null) + return hasPermNode(player, node); + return false; + } + @Override + public String getSkinURL() { + return skinurl; + } + @Override + public UUID getUUID() { + return uuid; + } + /** + * Send title and subtitle text (called from server thread) + */ + @Override + public void sendTitleText(String title, String subtitle, int fadeInTicks, int stayTicks, int fadeOutTicks) { + if (player instanceof ServerPlayer) { + ServerPlayer mp = (ServerPlayer) player; + ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks); + mp.connection.send(times); + if (title != null) { + ClientboundSetTitleTextPacket titlepkt = new ClientboundSetTitleTextPacket(Component.literal(title)); + mp.connection.send(titlepkt); + } + + if (subtitle != null) { + ClientboundSetSubtitleTextPacket subtitlepkt = new ClientboundSetSubtitleTextPacket(Component.literal(subtitle)); + mp.connection.send(subtitlepkt); + } + } + } + } + /* Handler for generic console command sender */ + public class ForgeCommandSender implements DynmapCommandSender + { + private CommandSourceStack sender; + + protected ForgeCommandSender() { + sender = null; + } + + public ForgeCommandSender(CommandSourceStack send) + { + sender = send; + } + + @Override + public boolean hasPrivilege(String privid) + { + return true; + } + + @Override + public void sendMessage(String msg) + { + if(sender != null) { + Component ichatcomponent = Component.literal(msg); + sender.sendSuccess(ichatcomponent, true); + } + } + + @Override + public boolean isConnected() + { + return false; + } + @Override + public boolean isOp() + { + return true; + } + @Override + public boolean hasPermissionNode(String node) { + return true; + } + } + + public void loadExtraBiomes(String mcver) { + int cnt = 0; + BiomeMap.loadWellKnownByVersion(mcver); + + Biome[] list = getBiomeList(); + + for (int i = 0; i < list.length; i++) { + Biome bb = list[i]; + if (bb != null) { + ResourceLocation regid = getBiomeReg().getKey(bb); + String id = regid.getPath(); + String rl = regid.toString(); + float tmp = bb.getBaseTemperature(), hum = bb.getDownfall(); + int watermult = bb.getWaterColor(); + Log.verboseinfo("biome[" + i + "]: hum=" + hum + ", tmp=" + tmp + ", mult=" + Integer.toHexString(watermult)); + + BiomeMap bmap = BiomeMap.NULL; + if (rl != null) { // If resource location, lookup by this + bmap = BiomeMap.byBiomeResourceLocation(rl); + } + else { + bmap = BiomeMap.byBiomeID(i); + } + if (bmap.isDefault() || (bmap == BiomeMap.NULL)) { + bmap = new BiomeMap((rl != null) ? BiomeMap.NO_INDEX : i, id, tmp, hum, rl); + Log.verboseinfo("Add custom biome [" + bmap.toString() + "] (" + i + ")"); + cnt++; + } + else { + bmap.setTemperature(tmp); + bmap.setRainfall(hum); + } + if (watermult != -1) { + bmap.setWaterColorMultiplier(watermult); + Log.verboseinfo("Set watercolormult for " + bmap.toString() + " (" + i + ") to " + Integer.toHexString(watermult)); + } + } + } + if(cnt > 0) + Log.info("Added " + cnt + " custom biome mappings"); + } + + private String[] getBiomeNames() { + Biome[] list = getBiomeList(); + String[] lst = new String[list.length]; + for(int i = 0; i < list.length; i++) { + Biome bb = list[i]; + if (bb != null) { + lst[i] = bb.toString(); + } + } + return lst; + } + + public void onEnable() + { + /* Get MC version */ + String mcver = server.getServerVersion(); + /* Load extra biomes */ + loadExtraBiomes(mcver); + /* Set up player login/quit event handler */ + registerPlayerLoginListener(); + + /* Initialize permissions handler */ + permissions = FilePermissions.create(); + if(permissions == null) { + permissions = new OpPermissions(new String[] { "webchat", "marker.icons", "marker.list", "webregister", "stats", "hide.self", "show.self" }); + } + /* Get and initialize data folder */ + File dataDirectory = new File("dynmap"); + + if (dataDirectory.exists() == false) + { + dataDirectory.mkdirs(); + } + + /* Instantiate core */ + if (core == null) + { + core = new DynmapCore(); + } + + /* Inject dependencies */ + core.setPluginJarFile(DynmapMod.jarfile); + core.setPluginVersion(DynmapMod.ver); + core.setMinecraftVersion(mcver); + core.setDataFolder(dataDirectory); + core.setServer(fserver); + core.setTriggerDefault(TRIGGER_DEFAULTS); + core.setBiomeNames(getBiomeNames()); + + if(!core.initConfiguration(null)) + { + return; + } + // Extract default permission example, if needed + File filepermexample = new File(core.getDataFolder(), "permissions.yml.example"); + core.createDefaultFileFromResource("/permissions.yml.example", filepermexample); + + DynmapCommonAPIListener.apiInitialized(core); + } + + private static int test(CommandSource source) throws CommandSyntaxException + { + Log.warning(source.toString()); + return 1; + } + + private DynmapCommand dynmapCmd; + private DmapCommand dmapCmd; + private DmarkerCommand dmarkerCmd; + private DynmapExpCommand dynmapexpCmd; + + public void onStarting(CommandDispatcher cd) { + /* Register command hander */ + dynmapCmd = new DynmapCommand(this); + dmapCmd = new DmapCommand(this); + dmarkerCmd = new DmarkerCommand(this); + dynmapexpCmd = new DynmapExpCommand(this); + dynmapCmd.register(cd); + dmapCmd.register(cd); + dmarkerCmd.register(cd); + dynmapexpCmd.register(cd); + + Log.info("Register commands"); + } + + public void onStart() { + initializeBlockStates(); + /* Enable core */ + if (!core.enableCore(null)) + { + return; + } + core_enabled = true; + VersionCheck.runCheck(core); + // Get per tick time limit + perTickLimit = core.getMaxTickUseMS() * 1000000; + // Prep TPS + lasttick = System.nanoTime(); + tps = 20.0; + + /* Register tick handler */ + if(!tickregistered) { + MinecraftForge.EVENT_BUS.register(fserver); + tickregistered = true; + } + + playerList = core.playerList; + sscache = new GenericChunkCache(core.getSnapShotCacheSize(), core.useSoftRefInSnapShotCache()); + /* Get map manager from core */ + mapManager = core.getMapManager(); + + /* Load saved world definitions */ + loadWorlds(); + + /* Initialized the currently loaded worlds */ + for (ServerLevel world : server.getAllLevels()) { + ForgeWorld w = this.getWorld(world); + } + for(ForgeWorld w : worlds.values()) { + if (core.processWorldLoad(w)) { /* Have core process load first - fire event listeners if good load after */ + if(w.isLoaded()) { + core.listenerManager.processWorldEvent(EventType.WORLD_LOAD, w); + } + } + } + core.updateConfigHashcode(); + + /* Register our update trigger events */ + registerEvents(); + Log.info("Register events"); + + //DynmapCommonAPIListener.apiInitialized(core); + + Log.info("Enabled"); + } + + public void onDisable() + { + DynmapCommonAPIListener.apiTerminated(); + + //if (metrics != null) { + // metrics.stop(); + // metrics = null; + //} + /* Save worlds */ + saveWorlds(); + + /* Purge tick queue */ + fserver.runqueue.clear(); + + /* Disable core */ + core.disableCore(); + core_enabled = false; + + if (sscache != null) + { + sscache.cleanup(); + sscache = null; + } + + Log.info("Disabled"); + } + + void onCommand(CommandSourceStack commandSourceStack, String cmd, String[] args) + { + DynmapCommandSender dsender; + ServerPlayer psender; + try { + psender = commandSourceStack.getPlayerOrException(); + } catch (com.mojang.brigadier.exceptions.CommandSyntaxException x) { + psender = null; + } + + if (psender != null) + { + dsender = new ForgePlayer(psender); + } + else + { + dsender = new ForgeCommandSender(commandSourceStack); + } + try { + core.processCommand(dsender, cmd, cmd, args); + } catch (Exception x) { + dsender.sendMessage("Command internal error: " + x.getMessage()); + Log.severe("Error with command: " + cmd + Arrays.deepToString(args), x); + } + } + + private DynmapLocation toLoc(ServerLevel level, double x, double y, double z) + { + return new DynmapLocation(DynmapPlugin.this.getWorld(level).getName(), x, y, z); + } + + public class PlayerTracker { + @SubscribeEvent + public void onPlayerLogin(PlayerLoggedInEvent event) { + if(!core_enabled) return; + final DynmapPlayer dp = getOrAddPlayer((ServerPlayer)event.getPlayer()); + /* This event can be called from off server thread, so push processing there */ + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processPlayerEvent(EventType.PLAYER_JOIN, dp); + } + }, 2); + } + @SubscribeEvent + public void onPlayerLogout(PlayerLoggedOutEvent event) { + if(!core_enabled) return; + final DynmapPlayer dp = getOrAddPlayer((ServerPlayer)event.getPlayer()); + final String name = event.getPlayer().getName().getString(); + /* This event can be called from off server thread, so push processing there */ + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processPlayerEvent(EventType.PLAYER_QUIT, dp); + players.remove(name); + } + }, 0); + } + @SubscribeEvent + public void onPlayerChangedDimension(PlayerChangedDimensionEvent event) { + if(!core_enabled) return; + getOrAddPlayer((ServerPlayer)event.getPlayer()); // Freshen player object reference + } + @SubscribeEvent + public void onPlayerRespawn(PlayerRespawnEvent event) { + if(!core_enabled) return; + getOrAddPlayer((ServerPlayer)event.getPlayer()); // Freshen player object reference + } + } + private PlayerTracker playerTracker = null; + + private void registerPlayerLoginListener() + { + if (playerTracker == null) { + playerTracker = new PlayerTracker(); + MinecraftForge.EVENT_BUS.register(playerTracker); + } + } + + public class WorldTracker { + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleWorldLoad(WorldEvent.Load event) { + if(!core_enabled) return; + LevelAccessor w = event.getWorld(); + if(!(w instanceof ServerLevel)) return; + final ForgeWorld fw = getWorld((ServerLevel)w); + // This event can be called from off server thread, so push processing there + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + if(core.processWorldLoad(fw)) // Have core process load first - fire event listeners if good load after + core.listenerManager.processWorldEvent(EventType.WORLD_LOAD, fw); + } + }, 0); + } + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleWorldUnload(WorldEvent.Unload event) { + if(!core_enabled) return; + LevelAccessor w = event.getWorld(); + if(!(w instanceof ServerLevel)) return; + final ForgeWorld fw = getWorld((ServerLevel)w); + if(fw != null) { + // This event can be called from off server thread, so push processing there + core.getServer().scheduleServerTask(new Runnable() { + public void run() { + core.listenerManager.processWorldEvent(EventType.WORLD_UNLOAD, fw); + core.processWorldUnload(fw); + } + }, 0); + // Set world unloaded (needs to be immediate, since it may be invalid after event) + fw.setWorldUnloaded(); + // Clean up tracker + //WorldUpdateTracker wut = updateTrackers.remove(fw.getName()); + //if(wut != null) wut.world = null; + } + } + + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleChunkLoad(ChunkEvent.Load event) { + if(!onchunkgenerate) return; + + LevelAccessor w = event.getWorld(); + if(!(w instanceof ServerLevel)) return; + ChunkAccess c = event.getChunk(); + if ((c != null) && (c.getStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + ForgeWorld fw = getWorld((ServerLevel)w, false); + if (fw != null) { + addKnownChunk(fw, c.getPos()); + } + } + } + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleChunkUnload(ChunkEvent.Unload event) { + if(!onchunkgenerate) return; + + LevelAccessor w = event.getWorld(); + if(!(w instanceof ServerLevel)) return; + ChunkAccess c = event.getChunk(); + if (c != null) { + ForgeWorld fw = getWorld((ServerLevel)w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for(int i = 0; i < sections.length; i++) { + if((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = sections[i].bottomBlockY(); + if (sy < ymin) ymin = sy; + if ((sy+16) > ymax) ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + //Log.info(String.format("chunkkeyerate(unload)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x+15, ymax, z+15, "chunkgenerate"); + } + } + removeKnownChunk(fw, cp); + } + } + } + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleChunkDataSave(ChunkDataEvent.Save event) { + if(!onchunkgenerate) return; + + LevelAccessor w = event.getWorld(); + if(!(w instanceof ServerLevel)) return; + ChunkAccess c = event.getChunk(); + if (c != null) { + ForgeWorld fw = getWorld((ServerLevel)w, false); + ChunkPos cp = c.getPos(); + if (fw != null) { + if (!checkIfKnownChunk(fw, cp)) { + int ymax = Integer.MIN_VALUE; + int ymin = Integer.MAX_VALUE; + LevelChunkSection[] sections = c.getSections(); + for(int i = 0; i < sections.length; i++) { + if((sections[i] != null) && (sections[i].hasOnlyAir() == false)) { + int sy = sections[i].bottomBlockY(); + if (sy < ymin) ymin = sy; + if ((sy+16) > ymax) ymax = sy + 16; + } + } + int x = cp.x << 4; + int z = cp.z << 4; + // If not empty AND not initial scan + if (ymax != Integer.MIN_VALUE) { + //Log.info(String.format("chunkkeyerate(save)(%s,%d,%d,%d,%d,%d,%s)", fw.getName(), x, ymin, z, x+15, ymax, z+15)); + mapManager.touchVolume(fw.getName(), x, ymin, z, x+15, ymax, z+15, "chunkgenerate"); + } + // If cooked, add to known + if ((c.getStatus() == ChunkStatus.FULL) && (c instanceof LevelChunk)) { + addKnownChunk(fw, cp); + } + } + } + } + } + @SubscribeEvent(priority=EventPriority.LOWEST) + public void handleBlockEvent(BlockEvent event) { + if(!core_enabled) return; + if(!onblockchange) return; + BlockUpdateRec r = new BlockUpdateRec(); + r.w = event.getWorld(); + if(!(r.w instanceof ServerLevel)) return; // band-aid to prevent errors in unsupported 'running in client' scenario + ForgeWorld fw = getWorld((ServerLevel)r.w, false); + if (fw == null) return; + r.wid = fw.getName(); + BlockPos p = event.getPos(); + r.x = p.getX(); + r.y = p.getY(); + r.z = p.getZ(); + blockupdatequeue.add(r); + } + } + private WorldTracker worldTracker = null; + private boolean onblockchange = false; + private boolean onchunkpopulate = false; + private boolean onchunkgenerate = false; + private boolean onblockchange_with_id = false; + + private void registerEvents() + { + // To trigger rendering. + onblockchange = core.isTrigger("blockupdate"); + onchunkpopulate = core.isTrigger("chunkpopulate"); + onchunkgenerate = core.isTrigger("chunkgenerate"); + onblockchange_with_id = core.isTrigger("blockupdate-with-id"); + if(onblockchange_with_id) + onblockchange = true; + if ((worldTracker == null) && (onblockchange || onchunkpopulate || onchunkgenerate)) { + worldTracker = new WorldTracker(); + MinecraftForge.EVENT_BUS.register(worldTracker); + } + // Prime the known full chunks + if (onchunkgenerate && (server.getAllLevels() != null)) { + for (ServerLevel world : server.getAllLevels()) { + ForgeWorld fw = getWorld(world); + if (fw == null) continue; + Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; + for (Entry k : chunks.long2ObjectEntrySet()) { + long key = k.getKey().longValue(); + ChunkHolder ch = k.getValue(); + ChunkAccess c = null; + try { + c = ch.getLastAvailable(); + } catch (Exception x) { } + if (c == null) continue; + ChunkStatus cs = c.getStatus(); + ChunkPos pos = ch.getPos(); + if (cs == ChunkStatus.FULL) { // Cooked? + // Add it as known + addKnownChunk(fw, pos); + } + } + } + } + } + + private ForgeWorld getWorldByName(String name) { + return worlds.get(name); + } + + private ForgeWorld getWorld(ServerLevel w) { + return getWorld(w, true); + } + + private ForgeWorld getWorld(ServerLevel w, boolean add_if_not_found) { + if(last_world == w) { + return last_fworld; + } + String wname = ForgeWorld.getWorldName(w); + + for(ForgeWorld fw : worlds.values()) { + if(fw.getRawName().equals(wname)) { + last_world = w; + last_fworld = fw; + if(fw.isLoaded() == false) { + fw.setWorldLoaded(w); + } + fw.updateWorld(w); + return fw; + } + } + ForgeWorld fw = null; + if(add_if_not_found) { + /* Add to list if not found */ + fw = new ForgeWorld(w); + worlds.put(fw.getName(), fw); + } + last_world = w; + last_fworld = fw; + return fw; + } + + private void saveWorlds() { + File f = new File(core.getDataFolder(), "forgeworlds.yml"); + ConfigurationNode cn = new ConfigurationNode(f); + ArrayList> lst = new ArrayList>(); + for(DynmapWorld fw : core.mapManager.getWorlds()) { + HashMap vals = new HashMap(); + vals.put("name", fw.getRawName()); + vals.put("height", fw.worldheight); + vals.put("miny", fw.minY); + vals.put("sealevel", fw.sealevel); + vals.put("nether", fw.isNether()); + vals.put("the_end", ((ForgeWorld)fw).isTheEnd()); + vals.put("title", fw.getTitle()); + lst.add(vals); + } + cn.put("worlds", lst); + cn.put("useSaveFolderAsName", useSaveFolder); + cn.put("maxWorldHeight", ForgeWorld.getMaxWorldHeight()); + + cn.save(); + } + private void loadWorlds() { + File f = new File(core.getDataFolder(), "forgeworlds.yml"); + if(f.canRead() == false) { + useSaveFolder = true; + return; + } + ConfigurationNode cn = new ConfigurationNode(f); + cn.load(); + // If defined, use maxWorldHeight + ForgeWorld.setMaxWorldHeight(cn.getInteger("maxWorldHeight", 256)); + + // If setting defined, use it + if (cn.containsKey("useSaveFolderAsName")) { + useSaveFolder = cn.getBoolean("useSaveFolderAsName", useSaveFolder); + } + List> lst = cn.getMapList("worlds"); + if(lst == null) { + Log.warning("Discarding bad forgeworlds.yml"); + return; + } + + for(Map world : lst) { + try { + String name = (String)world.get("name"); + int height = (Integer)world.get("height"); + Integer miny = (Integer) world.get("miny"); + int sealevel = (Integer)world.get("sealevel"); + boolean nether = (Boolean)world.get("nether"); + boolean theend = (Boolean)world.get("the_end"); + String title = (String)world.get("title"); + if(name != null) { + ForgeWorld fw = new ForgeWorld(name, height, sealevel, nether, theend, title, (miny != null) ? miny : 0); + fw.setWorldUnloaded(); + core.processWorldLoad(fw); + worlds.put(fw.getName(), fw); + } + } catch (Exception x) { + Log.warning("Unable to load saved worlds from forgeworlds.yml"); + return; + } + } + } + public void serverStarted() { + this.onStart(); + if (core != null) { + core.serverStarted(); + } + } + public MinecraftServer getMCServer() { + return server; + } +} + +class DynmapCommandHandler +{ + private String cmd; + private DynmapPlugin plugin; + + public DynmapCommandHandler(String cmd, DynmapPlugin p) + { + this.cmd = cmd; + this.plugin = p; + } + + public void register(CommandDispatcher cd) { + cd.register(Commands.literal(cmd). + then(RequiredArgumentBuilder. argument("args", StringArgumentType.greedyString()). + executes((ctx) -> this.execute(plugin.getMCServer(), ctx.getSource(), ctx.getInput()))). + executes((ctx) -> this.execute(plugin.getMCServer(), ctx.getSource(), ctx.getInput()))); + } + +// @Override + public int execute(MinecraftServer server, CommandSourceStack commandSourceStack, + String cmdline) { + String[] args = cmdline.split("\\s+"); + plugin.onCommand(commandSourceStack, cmd, Arrays.copyOfRange(args, 1, args.length)); + return 1; + } + +// @Override + public String getUsage(CommandSource arg0) { + return "Run /" + cmd + " help for details on using command"; + } +} + +class DynmapCommand extends DynmapCommandHandler { + DynmapCommand(DynmapPlugin p) { + super("dynmap", p); + } +} +class DmapCommand extends DynmapCommandHandler { + DmapCommand(DynmapPlugin p) { + super("dmap", p); + } +} +class DmarkerCommand extends DynmapCommandHandler { + DmarkerCommand(DynmapPlugin p) { + super("dmarker", p); + } +} +class DynmapExpCommand extends DynmapCommandHandler { + DynmapExpCommand(DynmapPlugin p) { + super("dynmapexp", p); + } +} + diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/ForgeMapChunkCache.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/ForgeMapChunkCache.java new file mode 100644 index 00000000..7231843b --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/ForgeMapChunkCache.java @@ -0,0 +1,90 @@ +package org.dynmap.forge_1_19; + +import java.util.List; +import java.util.NoSuchElementException; + +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.common.chunk.GenericChunk; +import org.dynmap.common.chunk.GenericChunkCache; +import org.dynmap.common.chunk.GenericMapChunkCache; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since + * rendering is off server thread + */ +public class ForgeMapChunkCache extends GenericMapChunkCache { + private ServerLevel w; + private ServerChunkCache cps; + /** + * Construct empty cache + */ + public ForgeMapChunkCache(GenericChunkCache cc) { + super(cc); + } + + // Load generic chunk from existing and already loaded chunk + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + GenericChunk gc = null; + ChunkAccess ch = cps.getChunk(chunk.x, chunk.z, ChunkStatus.FULL, false); + if (ch != null) { + CompoundTag nbt = ChunkSerializer.write(w, ch); + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + } + return gc; + } + // Load generic chunk from unloaded chunk + protected GenericChunk loadChunk(DynmapChunk chunk) { + GenericChunk gc = null; + CompoundTag nbt = readChunk(chunk.x, chunk.z); + // If read was good + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + return gc; + } + + public void setChunks(ForgeWorld dw, List chunks) { + this.w = dw.getWorld(); + if (dw.isLoaded()) { + /* Check if world's provider is ServerChunkProvider */ + cps = this.w.getChunkSource(); + } + super.setChunks(dw, chunks); + } + + private CompoundTag readChunk(int x, int z) { + try { + CompoundTag rslt = cps.chunkMap.readChunk(new ChunkPos(x, z)).join().get(); + if (rslt.contains("Level")) { + rslt = rslt.getCompound("Level"); + } + // Don't load uncooked chunks + String stat = rslt.getString("Status"); + ChunkStatus cs = ChunkStatus.byName(stat); + if ((stat == null) || + // Needs to be at least lighted + (!cs.isOrAfter(ChunkStatus.LIGHT))) { + rslt = null; + } + // Log.info(String.format("loadChunk(%d,%d)=%s", x, z, (rslt != null) ? + // rslt.toString() : "null")); + return rslt; + } catch (NoSuchElementException nsex) { + return null; + } catch (Exception exc) { + Log.severe(String.format("Error reading chunk: %s,%d,%d", dw.getName(), x, z), exc); + return null; + } + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/ForgeWorld.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/ForgeWorld.java new file mode 100644 index 00000000..02ea2f64 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/ForgeWorld.java @@ -0,0 +1,249 @@ +package org.dynmap.forge_1_19; +/** + * Forge specific implementation of DynmapWorld + */ +import java.util.List; + +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; + +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapLocation; +import org.dynmap.DynmapWorld; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.Polygon; + +public class ForgeWorld extends DynmapWorld +{ + private ServerLevelAccessor world; + private final boolean skylight; + private final boolean isnether; + private final boolean istheend; + private final String env; + private DynmapLocation spawnloc = new DynmapLocation(); + private static int maxWorldHeight = 320; // Maximum allows world height + + public static int getMaxWorldHeight() { + return maxWorldHeight; + } + public static void setMaxWorldHeight(int h) { + maxWorldHeight = h; + } + + public static String getWorldName(ServerLevelAccessor w) { + ResourceKey rk = w.getLevel().dimension(); + String id = rk.location().getNamespace() + "_" + rk.location().getPath(); + if (id.equals("minecraft_overworld")) { // Overworld? + return w.getLevel().serverLevelData.getLevelName(); + } + else if (id.equals("minecraft_the_end")) { + return "DIM1"; + } + else if (id.equals("minecraft_the_nether")) { + return "DIM-1"; + } + else { + return id; + } + } + + public void updateWorld(ServerLevelAccessor w) { + this.updateWorldHeights(w.getLevel().getHeight(), w.getLevel().dimensionType().minY(), w.getLevel().getSeaLevel()); + } + + public ForgeWorld(ServerLevelAccessor w) + { + this(getWorldName(w), + w.getLevel().getHeight(), + w.getLevel().getSeaLevel(), + w.getLevel().dimension() == Level.NETHER, + w.getLevel().dimension() == Level.END, + getWorldName(w), + w.getLevel().dimensionType().minY()); + setWorldLoaded(w); + } + public ForgeWorld(String name, int height, int sealevel, boolean nether, boolean the_end, String deftitle, int miny) + { + super(name, (height > maxWorldHeight)?maxWorldHeight:height, sealevel, miny); + world = null; + setTitle(deftitle); + isnether = nether; + istheend = the_end; + skylight = !(isnether || istheend); + + if (isnether) + { + env = "nether"; + } + else if (istheend) + { + env = "the_end"; + } + else + { + env = "normal"; + } + //Log.info(getName() + ": skylight=" + skylight + ", height=" + this.worldheight + ", isnether=" + isnether + ", istheend=" + istheend); + } + /* Test if world is nether */ + @Override + public boolean isNether() + { + return isnether; + } + public boolean isTheEnd() + { + return istheend; + } + /* Get world spawn location */ + @Override + public DynmapLocation getSpawnLocation() + { + if(world != null) { + BlockPos p = world.getLevel().getSharedSpawnPos(); + spawnloc.x = p.getX(); + spawnloc.y = p.getY(); + spawnloc.z = p.getZ(); + spawnloc.world = this.getName(); + } + return spawnloc; + } + /* Get world time */ + @Override + public long getTime() + { + if(world != null) + return world.getLevel().getDayTime(); + else + return -1; + } + /* World is storming */ + @Override + public boolean hasStorm() + { + if(world != null) + return world.getLevel().isRaining(); + else + return false; + } + /* World is thundering */ + @Override + public boolean isThundering() + { + if(world != null) + return world.getLevel().isThundering(); + else + return false; + } + /* World is loaded */ + @Override + public boolean isLoaded() + { + return (world != null); + } + /* Set world to unloaded */ + @Override + public void setWorldUnloaded() + { + getSpawnLocation(); + world = null; + } + /* Set world to loaded */ + public void setWorldLoaded(ServerLevelAccessor w) { + world = w; + this.sealevel = w.getLevel().getSeaLevel(); // Read actual current sealevel from world + // Update lighting table + for (int i = 0; i < 16; i++) { + // Algorithm based on LightmapTextureManager.getBrightness() + // We can't call that method because it's client-only. + // This means the code below can stop being correct if Mojang ever + // updates the curve; in that case we should reflect the changes. + float value = (float) i / 15.0f; + float brightness = value / (4.0f - 3.0f * value); + this.setBrightnessTableEntry(i, brightness); + //Log.info(getName() + ": light " + i + " = " + light); + } + } + /* Get light level of block */ + @Override + public int getLightLevel(int x, int y, int z) + { + if(world != null) + return world.getLevel().getLightEngine().getRawBrightness(new BlockPos(x, y, z), 0); + else + return -1; + } + /* Get highest Y coord of given location */ + @Override + public int getHighestBlockYAt(int x, int z) + { + if(world != null) { + return world.getLevel().getChunk(x >> 4, z >> 4).getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15); + } + else + return -1; + } + /* Test if sky light level is requestable */ + @Override + public boolean canGetSkyLightLevel() + { + return skylight; + } + /* Return sky light level */ + @Override + public int getSkyLightLevel(int x, int y, int z) + { + if(world != null) { + return world.getLevel().getBrightness(LightLayer.SKY, new BlockPos(x, y, z)); + } + else + return -1; + } + /** + * Get world environment ID (lower case - normal, the_end, nether) + */ + @Override + public String getEnvironment() + { + return env; + } + /** + * Get map chunk cache for world + */ + @Override + public MapChunkCache getChunkCache(List chunks) + { + if (world != null) { + ForgeMapChunkCache c = new ForgeMapChunkCache(DynmapPlugin.plugin.sscache); + c.setChunks(this, chunks); + return c; + } + return null; + } + + public ServerLevel getWorld() + { + return world.getLevel(); + } + @Override + public Polygon getWorldBorder() { + if (world != null) { + WorldBorder wb = world.getWorldBorder(); + if ((wb != null) && (wb.getSize() < 5.9E7)) { + Polygon p = new Polygon(); + p.addVertex(wb.getMinX(), wb.getMinZ()); + p.addVertex(wb.getMinX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMaxZ()); + p.addVertex(wb.getMaxX(), wb.getMinZ()); + return p; + } + } + return null; + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/NBT.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/NBT.java new file mode 100644 index 00000000..aad82091 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/NBT.java @@ -0,0 +1,126 @@ +package org.dynmap.forge_1_19; + +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.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.SimpleBitStorage; + +public class NBT { + + public static class NBTCompound implements GenericNBTCompound { + private final CompoundTag obj; + public NBTCompound(CompoundTag t) { + this.obj = t; + } + @Override + public Set getAllKeys() { + return obj.getAllKeys(); + } + @Override + public boolean contains(String s) { + return obj.contains(s); + } + @Override + public boolean contains(String s, int i) { + return obj.contains(s, i); + } + @Override + public byte getByte(String s) { + return obj.getByte(s); + } + @Override + public short getShort(String s) { + return obj.getShort(s); + } + @Override + public int getInt(String s) { + return obj.getInt(s); + } + @Override + public long getLong(String s) { + return obj.getLong(s); + } + @Override + public float getFloat(String s) { + return obj.getFloat(s); + } + @Override + public double getDouble(String s) { + return obj.getDouble(s); + } + @Override + public String getString(String s) { + return obj.getString(s); + } + @Override + public byte[] getByteArray(String s) { + return obj.getByteArray(s); + } + @Override + public int[] getIntArray(String s) { + return obj.getIntArray(s); + } + @Override + public long[] getLongArray(String s) { + return obj.getLongArray(s); + } + @Override + public GenericNBTCompound getCompound(String s) { + return new NBTCompound(obj.getCompound(s)); + } + @Override + public GenericNBTList getList(String s, int i) { + return new NBTList(obj.getList(s, i)); + } + @Override + public boolean getBoolean(String s) { + return obj.getBoolean(s); + } + @Override + public String getAsString(String s) { + return obj.get(s).getAsString(); + } + @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 ListTag obj; + public NBTList(ListTag t) { + obj = t; + } + @Override + public int size() { + return obj.size(); + } + @Override + public String getString(int idx) { + return obj.getString(idx); + } + @Override + public GenericNBTCompound getCompound(int idx) { + return new NBTCompound(obj.getCompound(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.get(idx); + } + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/Proxy.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/Proxy.java new file mode 100644 index 00000000..c2b6ee29 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/Proxy.java @@ -0,0 +1,24 @@ +package org.dynmap.forge_1_19; + +import net.minecraft.server.MinecraftServer; + +/** + * Server side proxy - methods for creating and cleaning up plugin + */ +public class Proxy +{ + public Proxy() + { + } + public DynmapPlugin startServer(MinecraftServer srv) { + DynmapPlugin plugin = DynmapPlugin.plugin; + if (plugin == null) { + plugin = new DynmapPlugin(srv); + plugin.onEnable(); + } + return plugin; + } + public void stopServer(DynmapPlugin plugin) { + plugin.onDisable(); + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/VersionCheck.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/VersionCheck.java new file mode 100644 index 00000000..044e2df3 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/VersionCheck.java @@ -0,0 +1,97 @@ +package org.dynmap.forge_1_19; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.dynmap.DynmapCore; +import org.dynmap.Log; + +public class VersionCheck { + private static final String VERSION_URL = "http://mikeprimm.com/dynmap/releases.php"; + public static void runCheck(final DynmapCore core) { + new Thread(new Runnable() { + public void run() { + doCheck(core); + } + }).start(); + } + + private static int getReleaseVersion(String s) { + int index = s.lastIndexOf('-'); + if(index < 0) + index = s.lastIndexOf('.'); + if(index >= 0) + s = s.substring(0, index); + String[] split = s.split("\\."); + int v = 0; + try { + for(int i = 0; (i < split.length) && (i < 3); i++) { + v += Integer.parseInt(split[i]) << (8 * (2 - i)); + } + } catch (NumberFormatException nfx) {} + return v; + } + + private static int getBuildNumber(String s) { + int index = s.lastIndexOf('-'); + if(index < 0) + index = s.lastIndexOf('.'); + if(index >= 0) + s = s.substring(index+1); + try { + return Integer.parseInt(s); + } catch (NumberFormatException nfx) { + return 99999999; + } + } + + private static void doCheck(DynmapCore core) { + String pluginver = core.getDynmapPluginVersion(); + String platform = core.getDynmapPluginPlatform(); + String platver = core.getDynmapPluginPlatformVersion(); + if((pluginver == null) || (platform == null) || (platver == null)) + return; + HttpURLConnection conn = null; + String loc = VERSION_URL; + int cur_ver = getReleaseVersion(pluginver); + int cur_bn = getBuildNumber(pluginver); + try { + while((loc != null) && (!loc.isEmpty())) { + URL url = new URL(loc); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("User-Agent", "Dynmap (" + platform + "/" + platver + "/" + pluginver); + conn.connect(); + loc = conn.getHeaderField("Location"); + } + BufferedReader rdr = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = null; + while((line = rdr.readLine()) != null) { + String[] split = line.split(":"); + if(split.length < 4) continue; + /* If our platform and version, or wildcard platform version */ + if(split[0].equals(platform) && (split[1].equals("*") || split[1].equals(platver))) { + int recommended_ver = getReleaseVersion(split[2]); + int recommended_bn = getBuildNumber(split[2]); + if((recommended_ver > cur_ver) || ((recommended_ver == cur_ver) && (recommended_bn > cur_bn))) { /* Newer recommended build */ + Log.info("Version obsolete: new recommended version " + split[2] + " is available."); + } + else if(cur_ver > recommended_ver) { /* Running dev or prerelease? */ + int prerel_ver = getReleaseVersion(split[3]); + int prerel_bn = getBuildNumber(split[3]); + if((prerel_ver > cur_ver) || ((prerel_ver == cur_ver) && (prerel_bn > cur_bn))) { + Log.info("Version obsolete: new prerelease version " + split[3] + " is available."); + } + } + } + } + } catch (Exception x) { + Log.info("Error checking for latest version"); + } finally { + if(conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/FilePermissions.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/FilePermissions.java new file mode 100644 index 00000000..c3121bd3 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/FilePermissions.java @@ -0,0 +1,103 @@ +package org.dynmap.forge_1_19.permissions; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.dynmap.forge_1_19.DynmapPlugin; + +import net.minecraft.server.level.ServerPlayer; + +public class FilePermissions implements PermissionProvider { + private HashMap> perms; + private Set defperms; + + public static FilePermissions create() { + File f = new File("dynmap/permissions.yml"); + if(!f.exists()) + return null; + ConfigurationNode cfg = new ConfigurationNode(f); + cfg.load(); + + Log.info("Using permissions.yml for access control"); + + return new FilePermissions(cfg); + } + + private FilePermissions(ConfigurationNode cfg) { + perms = new HashMap>(); + for(String k : cfg.keySet()) { + List p = cfg.getStrings(k, null); + if(p != null) { + k = k.toLowerCase(); + HashSet pset = new HashSet(); + for(String perm : p) { + pset.add(perm.toLowerCase()); + } + perms.put(k, pset); + if(k.equals("defaultuser")) { + defperms = pset; + } + } + } + } + + private boolean hasPerm(String player, String perm) { + Set ps = perms.get(player); + if((ps != null) && (ps.contains(perm))) { + return true; + } + if(defperms.contains(perm)) { + return true; + } + return false; + } + @Override + public Set hasOfflinePermissions(String player, Set perms) { + player = player.toLowerCase(); + HashSet rslt = new HashSet(); + if(DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + else { + for(String p : perms) { + if(hasPerm(player, p)) { + rslt.add(p); + } + } + } + return rslt; + } + @Override + public boolean hasOfflinePermission(String player, String perm) { + player = player.toLowerCase(); + if(DynmapPlugin.plugin.isOp(player)) { + return true; + } + else { + return hasPerm(player, perm); + } + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if(psender != null) { + String n = psender.getName().getString().toLowerCase(); + return hasPerm(n, permission); + } + return true; + } + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if(psender != null) { + String player = psender.getName().getString().toLowerCase(); + return DynmapPlugin.plugin.isOp(player); + } + return false; + } + +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/OpPermissions.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/OpPermissions.java new file mode 100644 index 00000000..ddbe5c78 --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/OpPermissions.java @@ -0,0 +1,51 @@ +package org.dynmap.forge_1_19.permissions; + +import java.util.HashSet; +import java.util.Set; + +import org.dynmap.Log; +import org.dynmap.forge_1_19.DynmapPlugin; + +import net.minecraft.server.level.ServerPlayer; + +public class OpPermissions implements PermissionProvider { + public HashSet usrCommands = new HashSet(); + + public OpPermissions(String[] usrCommands) { + for (String usrCommand : usrCommands) { + this.usrCommands.add(usrCommand); + } + Log.info("Using ops.txt for access control"); + } + + @Override + public Set hasOfflinePermissions(String player, Set perms) { + HashSet rslt = new HashSet(); + if(DynmapPlugin.plugin.isOp(player)) { + rslt.addAll(perms); + } + return rslt; + } + @Override + public boolean hasOfflinePermission(String player, String perm) { + return DynmapPlugin.plugin.isOp(player); + } + + @Override + public boolean has(ServerPlayer psender, String permission) { + if(psender != null) { + if(usrCommands.contains(permission)) { + return true; + } + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } + @Override + public boolean hasPermissionNode(ServerPlayer psender, String permission) { + if(psender != null) { + return DynmapPlugin.plugin.isOp(psender.getName().getString()); + } + return true; + } +} diff --git a/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/PermissionProvider.java b/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/PermissionProvider.java new file mode 100644 index 00000000..d41a772d --- /dev/null +++ b/forge-1.19/src/main/java/org/dynmap/forge_1_19/permissions/PermissionProvider.java @@ -0,0 +1,15 @@ +package org.dynmap.forge_1_19.permissions; + +import java.util.Set; + +import net.minecraft.server.level.ServerPlayer; + +public interface PermissionProvider { + boolean has(ServerPlayer sender, String permission); + boolean hasPermissionNode(ServerPlayer sender, String permission); + + Set hasOfflinePermissions(String player, Set perms); + + boolean hasOfflinePermission(String player, String perm); + +} diff --git a/forge-1.19/src/main/resources/META-INF/accesstransformer.cfg b/forge-1.19/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 00000000..58a91d30 --- /dev/null +++ b/forge-1.19/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,4 @@ +public net.minecraft.world.level.biome.BiomeSpecialEffects$Builder f_47928_ # waterColor +public net.minecraft.server.level.ServerLevel f_8549_ # serverLevelData +public net.minecraft.server.level.ChunkMap f_140130_ # visibleChunkMap +public net.minecraft.server.level.ChunkMap m_214963_(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture; # readChunk( diff --git a/forge-1.19/src/main/resources/META-INF/mods.toml b/forge-1.19/src/main/resources/META-INF/mods.toml new file mode 100644 index 00000000..17241af4 --- /dev/null +++ b/forge-1.19/src/main/resources/META-INF/mods.toml @@ -0,0 +1,26 @@ +modLoader="javafml" +loaderVersion="[41,)" +issueTrackerURL="https://github.com/webbukkit/dynmap/issues" +license="Apache Public License v2" +[[mods]] +modId="dynmap" +version="${version}" +displayName="Dynmap" +authors="mikeprimm" +description=''' +Dynamic, Google-maps style rendered maps for your Minecraft server +''' + +[[dependencies.dynmap]] + modId="forge" + mandatory=true + versionRange="[41,)" + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT or SERVER + side="SERVER" +[[dependencies.dynmap]] + modId="minecraft" + mandatory=true + versionRange="[1.19,1.20)" + ordering="NONE" + side="SERVER" diff --git a/forge-1.19/src/main/resources/configuration.txt b/forge-1.19/src/main/resources/configuration.txt new file mode 100644 index 00000000..161202ed --- /dev/null +++ b/forge-1.19/src/main/resources/configuration.txt @@ -0,0 +1,496 @@ +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/dynmap/ + +# All map templates are defined in the templates directory +# To use the HDMap very-low-res (2 ppb) map templates as world defaults, set value to vlowres +# The definitions of these templates are in normal-vlowres.txt, nether-vlowres.txt, and the_end-vlowres.txt +# To use the HDMap low-res (4 ppb) map templates as world defaults, set value to lowres +# The definitions of these templates are in normal-lowres.txt, nether-lowres.txt, and the_end-lowres.txt +# To use the HDMap hi-res (16 ppb) map templates (these can take a VERY long time for initial fullrender), set value to hires +# The definitions of these templates are in normal-hires.txt, nether-hires.txt, and the_end-hires.txt +# To use the HDMap low-res (4 ppb) map templates, with support for boosting resolution selectively to hi-res (16 ppb), set value to low_boost_hi +# The definitions of these templates are in normal-low_boost_hi.txt, nether-low_boost_hi.txt, and the_end-low_boost_hi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to vhi-res (32 ppb), set value to hi_boost_vhi +# The definitions of these templates are in normal-hi_boost_vhi.txt, nether-hi_boost_vhi.txt, and the_end-hi_boost_vhi.txt +# To use the HDMap hi-res (16 ppb) map templates, with support for boosting resolution selectively to xhi-res (64 ppb), set value to hi_boost_xhi +# The definitions of these templates are in normal-hi_boost_xhi.txt, nether-hi_boost_xhi.txt, and the_end-hi_boost_xhi.txt +deftemplatesuffix: hires + +# Set default tile scale (0 = 128px x 128x, 1 = 256px x 256px, 2 = 512px x 512px, 3 = 1024px x 1024px, 4 = 2048px x 2048px) - 0 is default +# Note: changing this value will result in all maps that use the default value being required to be fully rendered +#defaulttilescale: 0 + +# Map storage scheme: only uncommoent one 'type' value +# filetree: classic and default scheme: tree of files, with all map data under the directory indicated by 'tilespath' setting +# sqlite: single SQLite database file (this can get VERY BIG), located at 'dbfile' setting (default is file dynmap.db in data directory) +# mysql: MySQL database, at hostname:port in database, accessed via userid with password +# mariadb: MariaDB database, at hostname:port in database, accessed via userid with password +# postgres: PostgreSQL database, at hostname:port in database, accessed via userid with password +storage: + # Filetree storage (standard tree of image files for maps) + type: filetree + # SQLite db for map storage (uses dbfile as storage location) + #type: sqlite + #dbfile: dynmap.db + # MySQL DB for map storage (at 'hostname':'port' in database 'database' using user 'userid' password 'password' and table prefix 'prefix' + #type: mysql + #hostname: localhost + #port: 3306 + #database: dynmap + #userid: dynmap + #password: dynmap + #prefix: "" + # + # AWS S3 backet web site + #type: aws_s3 + #bucketname: "dynmap-bucket-name" + #region: us-east-1 + #aws_access_key_id: "" + #aws_secret_access_key: "" + #prefix: "" + +components: + - class: org.dynmap.ClientConfigurationComponent + + - class: org.dynmap.InternalClientUpdateComponent + sendhealth: true + sendposition: true + allowwebchat: true + webchat-interval: 5 + hidewebchatip: false + trustclientname: false + includehiddenplayers: false + # (optional) if true, color codes in player display names are used + use-name-colors: false + # (optional) if true, player login IDs will be used for web chat when their IPs match + use-player-login-ip: true + # (optional) if use-player-login-ip is true, setting this to true will cause chat messages not matching a known player IP to be ignored + require-player-login-ip: false + # (optional) block player login IDs that are banned from chatting + block-banned-player-chat: true + # Require login for web-to-server chat (requires login-enabled: true) + webchat-requires-login: false + # If set to true, users must have dynmap.webchat permission in order to chat + webchat-permissions: false + # Limit length of single chat messages + chatlengthlimit: 256 + # # Optional - make players hidden when they are inside/underground/in shadows (#=light level: 0=full shadow,15=sky) + # hideifshadow: 4 + # # Optional - make player hidden when they are under cover (#=sky light level,0=underground,15=open to sky) + # hideifundercover: 14 + # # (Optional) if true, players that are crouching/sneaking will be hidden + hideifsneaking: false + # If true, player positions/status is protected (login with ID with dynmap.playermarkers.seeall permission required for info other than self) + protected-player-info: false + # If true, hide players with invisibility potion effects active + hide-if-invisiblity-potion: true + # If true, player names are not shown on map, chat, list + hidenames: false + #- class: org.dynmap.JsonFileClientUpdateComponent + # writeinterval: 1 + # sendhealth: true + # sendposition: true + # allowwebchat: true + # webchat-interval: 5 + # hidewebchatip: false + # includehiddenplayers: false + # use-name-colors: false + # use-player-login-ip: false + # require-player-login-ip: false + # block-banned-player-chat: true + # hideifshadow: 0 + # hideifundercover: 0 + # hideifsneaking: false + # # Require login for web-to-server chat (requires login-enabled: true) + # webchat-requires-login: false + # # If set to true, users must have dynmap.webchat permission in order to chat + # webchat-permissions: false + # # Limit length of single chat messages + # chatlengthlimit: 256 + # hide-if-invisiblity-potion: true + # hidenames: false + + - class: org.dynmap.SimpleWebChatComponent + allowchat: true + # If true, web UI users can supply name for chat using 'playername' URL parameter. 'trustclientname' must also be set true. + allowurlname: false + + # Note: this component is needed for the dmarker commands, and for the Marker API to be available to other plugins + - class: org.dynmap.MarkersComponent + type: markers + showlabel: false + enablesigns: false + # Default marker set for sign markers + default-sign-set: markers + # (optional) add spawn point markers to standard marker layer + showspawn: true + spawnicon: world + spawnlabel: "Spawn" + # (optional) layer for showing offline player's positions (for 'maxofflinetime' minutes after logoff) + showofflineplayers: false + offlinelabel: "Offline" + offlineicon: offlineuser + offlinehidebydefault: true + offlineminzoom: 0 + maxofflinetime: 30 + # (optional) layer for showing player's spawn beds + showspawnbeds: false + spawnbedlabel: "Spawn Beds" + spawnbedicon: bed + spawnbedhidebydefault: true + spawnbedminzoom: 0 + spawnbedformat: "%name%'s bed" + # (optional) Show world border (vanilla 1.8+) + showworldborder: true + worldborderlabel: "Border" + + - class: org.dynmap.ClientComponent + type: chat + allowurlname: false + - class: org.dynmap.ClientComponent + type: chatballoon + focuschatballoons: false + - class: org.dynmap.ClientComponent + type: chatbox + showplayerfaces: true + messagettl: 5 + # Optional: set number of lines in scrollable message history: if set, messagettl is not used to age out messages + #scrollback: 100 + # Optional: set maximum number of lines visible for chatbox + #visiblelines: 10 + # Optional: send push button + sendbutton: false + - class: org.dynmap.ClientComponent + type: playermarkers + showplayerfaces: true + showplayerhealth: true + # If true, show player body too (only valid if showplayerfaces=true) + showplayerbody: false + # Option to make player faces small - don't use with showplayerhealth or largeplayerfaces + smallplayerfaces: false + # Option to make player faces larger - don't use with showplayerhealth or smallplayerfaces + largeplayerfaces: false + # Optional - make player faces layer hidden by default + hidebydefault: false + # Optional - ordering priority in layer menu (low goes before high - default is 0) + layerprio: 0 + # Optional - label for player marker layer (default is 'Players') + label: "Players" + + #- class: org.dynmap.ClientComponent + # type: digitalclock + - class: org.dynmap.ClientComponent + type: link + + - class: org.dynmap.ClientComponent + type: timeofdayclock + showdigitalclock: true + #showweather: true + # Mouse pointer world coordinate display + - class: org.dynmap.ClientComponent + type: coord + label: "Location" + hidey: false + show-mcr: false + show-chunk: false + + # Note: more than one logo component can be defined + #- class: org.dynmap.ClientComponent + # type: logo + # text: "Dynmap" + # #logourl: "images/block_surface.png" + # linkurl: "http://forums.bukkit.org/threads/dynmap.489/" + # # Valid positions: top-left, top-right, bottom-left, bottom-right + # position: bottom-right + + #- class: org.dynmap.ClientComponent + # type: inactive + # timeout: 1800 # in seconds (1800 seconds = 30 minutes) + # redirecturl: inactive.html + # #showmessage: 'You were inactive for too long.' + + #- class: org.dynmap.TestComponent + # stuff: "This is some configuration-value" + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +display-whitelist: false + +# How often a tile gets rendered (in seconds). +renderinterval: 1 + +# How many tiles on update queue before accelerate render interval +renderacceleratethreshold: 60 + +# How often to render tiles when backlog is above renderacceleratethreshold +renderaccelerateinterval: 0.2 + +# How many update tiles to work on at once (if not defined, default is 1/2 the number of cores) +tiles-rendered-at-once: 2 + +# If true, use normal priority threads for rendering (versus low priority) - this can keep rendering +# from starving on busy Windows boxes (Linux JVMs pretty much ignore thread priority), but may result +# in more competition for CPU resources with other processes +usenormalthreadpriority: true + +# Save and restore pending tile renders - prevents their loss on server shutdown or /reload +saverestorepending: true + +# Save period for pending jobs (in seconds): periodic saving for crash recovery of jobs +save-pending-period: 900 + +# Zoom-out tile update period - how often to scan for and process tile updates into zoom-out tiles (in seconds) +zoomoutperiod: 30 + +# Control whether zoom out tiles are validated on startup (can be needed if zoomout processing is interrupted, but can be expensive on large maps) +initial-zoomout-validate: true + +# Default delay on processing of updated tiles, in seconds. This can reduce potentially expensive re-rendering +# of frequently updated tiles (such as due to machines, pistons, quarries or other automation). Values can +# also be set on individual worlds and individual maps. +tileupdatedelay: 30 + +# Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable +enabletilehash: true + +# Optional - hide ores: render as normal stone (so that they aren't revealed by maps) +#hideores: true + +# Optional - enabled BetterGrass style rendering of grass and snow block sides +#better-grass: true + +# Optional - enable smooth lighting by default on all maps supporting it (can be set per map as lighting option) +smooth-lighting: true + +# Optional - use world provider lighting table (good for custom worlds with custom lighting curves, like nether) +# false=classic Dynmap lighting curve +use-brightness-table: true + +# Optional - render specific block names using the textures and models of another block name: can be used to hide/disguise specific +# blocks (e.g. make ores look like stone, hide chests) or to provide simple support for rendering unsupported custom blocks +block-alias: +# "minecraft:quartz_ore": "stone" +# "diamond_ore": "coal_ore" + +# Default image format for HDMaps (png, jpg, jpg-q75, jpg-q80, jpg-q85, jpg-q90, jpg-q95, jpg-q100, webp, webp-q75, webp-q80, webp-q85, webp-q90, webp-q95, webp-q100), +# Note: any webp format requires the presence of the 'webp command line tools' (cwebp, dwebp) (https://developers.google.com/speed/webp/download) +# +# Has no effect on maps with explicit format settings +image-format: jpg-q90 + +# If cwebp or dwebp are not on the PATH, use these settings to provide their full path. Do not use these settings if the tools are on the PATH +# For Windows, include .exe +# +#cwebpPath: /usr/bin/cwebp +#dwebpPath: /usr/bin/dwebp + +# use-generated-textures: if true, use generated textures (same as client); false is static water/lava textures +# correct-water-lighting: if true, use corrected water lighting (same as client); false is legacy water (darker) +# transparent-leaves: if true, leaves are transparent (lighting-wise): false is needed for some Spout versions that break lighting on leaf blocks +use-generated-textures: true +correct-water-lighting: true +transparent-leaves: true + +# ctm-support: if true, Connected Texture Mod (CTM) in texture packs is enabled (default) +ctm-support: true +# custom-colors-support: if true, Custom Colors in texture packs is enabled (default) +custom-colors-support: true + +# Control loading of player faces (if set to false, skins are never fetched) +#fetchskins: false + +# Control updating of player faces, once loaded (if faces are being managed by other apps or manually) +#refreshskins: false + +# Customize URL used for fetching player skins (%player% is macro for name, %uuid% for UUID) +skin-url: "http://skins.minecraft.net/MinecraftSkins/%player%.png" + +# Control behavior for new (1.0+) compass orientation (sunrise moved 90 degrees: east is now what used to be south) +# default is 'newrose' (preserve pre-1.0 maps, rotate rose) +# 'newnorth' is used to rotate maps and rose (requires fullrender of any HDMap map - same as 'newrose' for FlatMap or KzedMap) +compass-mode: newnorth + +# Triggers for automatic updates : blockupdate-with-id is debug for breaking down updates by ID:meta +# To disable, set just 'none' and comment/delete the rest +render-triggers: + - blockupdate + #- blockupdate-with-id + #- lightingupdate + - chunkpopulate + - chunkgenerate + #- none + +# Title for the web page - if not specified, defaults to the server's name (unless it is the default of 'Unknown Server') +#webpage-title: "My Awesome Server Map" + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# If set to false, disable extraction of webpath content (good if using custom web UI or 3rd party web UI) +# Note: web interface is unsupported in this configuration - you're on your own +update-webpath-files: true + +# The path were the /dynmapexp command exports OBJ ZIP files +exportpath: export + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +# If not set, uses same setting as server in server.properties (or 0.0.0.0 if not specified) +#webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Maximum concurrent session on internal web server - limits resources used in Bukkit server +max-sessions: 30 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Enable/disable having the web server allow symbolic links (true=compatible with existing code, false=more secure (default)) +allow-symlinks: true + +# Enable login support +login-enabled: false +# Require login to access website (requires login-enabled: true) +login-required: false + +# Period between tile renders for fullrender, in seconds (non-zero to pace fullrenders, lessen CPU load) +timesliceinterval: 0.0 + +# Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load +maxchunkspertick: 200 + +# Progress report interval for fullrender/radiusrender, in tiles. Must be 100 or greater +progressloginterval: 100 + +# Parallel fullrender: if defined, number of concurrent threads used for fullrender or radiusrender +# Note: setting this will result in much more intensive CPU use, some additional memory use. Caution should be used when +# setting this to equal or exceed the number of physical cores on the system. +#parallelrendercnt: 4 + +# Interval the browser should poll for updates. +updaterate: 2000 + +# If nonzero, server will pause fullrender/radiusrender processing when 'fullrenderplayerlimit' or more users are logged in +fullrenderplayerlimit: 0 +# If nonzero, server will pause update render processing when 'updateplayerlimit' or more users are logged in +updateplayerlimit: 0 +# Target limit on server thread use - msec per tick +per-tick-time-limit: 50 +# If TPS of server is below this setting, update renders processing is paused +update-min-tps: 18.0 +# If TPS of server is below this setting, full/radius renders processing is paused +fullrender-min-tps: 18.0 +# If TPS of server is below this setting, zoom out processing is paused +zoomout-min-tps: 18.0 + +showplayerfacesinmenu: true + +# Control whether players that are hidden or not on current map are grayed out (true=yes) +grayplayerswhenhidden: true + +# Set sidebaropened: 'true' to pin menu sidebar opened permanently, 'pinned' to default the sidebar to pinned, but allow it to unpin +#sidebaropened: true + +# Customized HTTP response headers - add 'id: value' pairs to all HTTP response headers (internal web server only) +#http-response-headers: +# Access-Control-Allow-Origin: "my-domain.com" +# X-Custom-Header-Of-Mine: "MyHeaderValue" + +# Trusted proxies for web server - which proxy addresses are trusted to supply valid X-Forwarded-For fields +# This now supports both IP address, and subnet ranges (e.g. 192.168.1.0/24 or 202.24.0.0/14 ) +trusted-proxies: + - "127.0.0.1" + - "0:0:0:0:0:0:0:1" + +joinmessage: "%playername% joined" +quitmessage: "%playername% quit" +spammessage: "You may only chat once every %interval% seconds." +# format for messages from web: %playername% substitutes sender ID (typically IP), %message% includes text +webmsgformat: "&color;2[WEB] %playername%: &color;f%message%" + +# Control whether layer control is presented on the UI (default is true) +showlayercontrol: true + +# Enable checking for banned IPs via banned-ips.txt (internal web server only) +check-banned-ips: true + +# Default selection when map page is loaded +defaultzoom: 0 +defaultworld: world +defaultmap: flat +# (optional) Zoom level and map to switch to when following a player, if possible +#followzoom: 3 +#followmap: surface + +# If true, make persistent record of IP addresses used by player logins, to support web IP to player matching +persist-ids-by-ip: true + +# If true, map text to cyrillic +cyrillic-support: false + +# Messages to customize +msg: + maptypes: "Map Types" + players: "Players" + chatrequireslogin: "Chat Requires Login" + chatnotallowed: "You are not permitted to send chat messages" + hiddennamejoin: "Player joined" + hiddennamequit: "Player quit" + +# URL for client configuration (only need to be tailored for proxies or other non-standard configurations) +url: + # configuration URL + #configuration: "up/configuration" + # update URL + #update: "up/world/{world}/{timestamp}" + # sendmessage URL + #sendmessage: "up/sendmessage" + # login URL + #login: "up/login" + # register URL + #register: "up/register" + # tiles base URL + #tiles: "tiles/" + # markers base URL + #markers: "tiles/" + # Snapshot cache size, in chunks +snapshotcachesize: 500 +# Snapshot cache uses soft references (true), else weak references (false) +soft-ref-cache: true + +# Player enter/exit title messages for map markers +# +# Processing period - how often to check player positions vs markers - default is 1000ms (1 second) +#enterexitperiod: 1000 +# Title message fade in time, in ticks (0.05 second intervals) - default is 10 (1/2 second) +#titleFadeIn: 10 +# Title message stay time, in ticks (0.05 second intervals) - default is 70 (3.5 seconds) +#titleStay: 70 +# Title message fade out time, in ticks (0.05 seocnd intervals) - default is 20 (1 second) +#titleFadeOut: 20 +# Enter/exit messages use on screen titles (true - default), if false chat messages are sent instead +#enterexitUseTitle: true +# Set true if new enter messages should supercede pending exit messages (vs being queued in order), default false +#enterReplacesExits: true + +# Published public URL for Dynmap server (allows users to use 'dynmap url' command to get public URL usable to access server +# If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) +#publicURL: http://my.greatserver.com/dynmap + +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + +# Set to true to enable verbose startup messages - can help with debugging map configuration problems +# Set to false for a much quieter startup log +verbose: false + +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger +# Debug: dump blocks missing render data +dump-missing-blocks: false + +# Log4J defense: string substituted for attempts to use macros in web chat +hackAttemptBlurb: "(IaM5uchA1337Haxr-Ban Me!)" diff --git a/forge-1.19/src/main/resources/pack.mcmeta b/forge-1.19/src/main/resources/pack.mcmeta new file mode 100644 index 00000000..8ae5db1a --- /dev/null +++ b/forge-1.19/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Dynmap resources", + "pack_format": 8 + } +} diff --git a/forge-1.19/src/main/resources/permissions.yml.example b/forge-1.19/src/main/resources/permissions.yml.example new file mode 100644 index 00000000..a25f9adc --- /dev/null +++ b/forge-1.19/src/main/resources/permissions.yml.example @@ -0,0 +1,27 @@ +# +# Sample permissions.yml for dynmap - trivial, flat-file based permissions for dynmap features +# To use, copy this file to dynmap/permissions.yml, and edit appropriate. File is YAML format. +# +# All operators have full permissions to all functions. +# All users receive the permissions under the 'defaultuser' section +# Specific users can be given more permissions by defining a section with their name containing their permisssions +# All permissions correspond to those documented here (https://github.com/webbukkit/dynmap/wiki/Permissions), but +# do NOT have the 'dynmap.' prefix when used here (e.g. 'dynmap.fullrender' permission is just 'fullrender' here). +# +defaultuser: + - render + - show.self + - hide.self + - sendtoweb + - stats + - marker.list + - marker.listsets + - marker.icons + - webregister + - webchat + #- marker.sign + +#playername1: +# - fullrender +# - cancelrender +# - radiusrender diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 29953ea1..7454180f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102e..aa991fce 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d5..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/oldbuilds/build.gradle b/oldbuilds/build.gradle index cb3a2143..9e3696e3 100644 --- a/oldbuilds/build.gradle +++ b/oldbuilds/build.gradle @@ -25,7 +25,7 @@ allprojects { apply plugin: 'java' group = 'us.dynmap' - version = '3.4-SNAPSHOT' + version = '3.4-beta-4' } diff --git a/settings.gradle b/settings.gradle index b7e91e37..23f38e16 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,16 +19,19 @@ include ':bukkit-helper-116-4' include ':bukkit-helper-117' include ':bukkit-helper-118' include ':bukkit-helper-118-2' +include ':bukkit-helper-119' include ':bukkit-helper' include ':dynmap-api' include ':DynmapCore' include ':DynmapCoreAPI' +include ':fabric-1.19' include ':fabric-1.18.2' include ':fabric-1.18' include ':fabric-1.17.1' include ':fabric-1.16.4' include ':fabric-1.15.2' include ':fabric-1.14.4' +include ':forge-1.19' include ':forge-1.18.2' include ':forge-1.18' include ':forge-1.17.1' @@ -47,16 +50,19 @@ project(':bukkit-helper-116-4').projectDir = "$rootDir/bukkit-helper-116-4" as F project(':bukkit-helper-117').projectDir = "$rootDir/bukkit-helper-117" as File project(':bukkit-helper-118').projectDir = "$rootDir/bukkit-helper-118" as File project(':bukkit-helper-118-2').projectDir = "$rootDir/bukkit-helper-118-2" as File +project(':bukkit-helper-119').projectDir = "$rootDir/bukkit-helper-119" 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 project(':DynmapCoreAPI').projectDir = "$rootDir/DynmapCoreAPI" as File +project(':fabric-1.19').projectDir = "$rootDir/fabric-1.19" as File project(':fabric-1.18.2').projectDir = "$rootDir/fabric-1.18.2" as File project(':fabric-1.18').projectDir = "$rootDir/fabric-1.18" as File project(':fabric-1.17.1').projectDir = "$rootDir/fabric-1.17.1" as File project(':fabric-1.16.4').projectDir = "$rootDir/fabric-1.16.4" as File project(':fabric-1.15.2').projectDir = "$rootDir/fabric-1.15.2" as File project(':fabric-1.14.4').projectDir = "$rootDir/fabric-1.14.4" as File +project(':forge-1.19').projectDir = "$rootDir/forge-1.19" as File project(':forge-1.18.2').projectDir = "$rootDir/forge-1.18.2" as File project(':forge-1.18').projectDir = "$rootDir/forge-1.18" as File project(':forge-1.17.1').projectDir = "$rootDir/forge-1.17.1" as File diff --git a/spigot/build.gradle b/spigot/build.gradle index 4f60145e..b84c5169 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -31,7 +31,7 @@ dependencies { implementation group: 'org.anjocaido', name: 'EssentialsGroupManager', version: '2.10.1' implementation group: 'org.bstats', name: 'bstats-bukkit', version: '2.2.1' implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.2' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9' implementation project(':bukkit-helper') implementation(project(':bukkit-helper-113-2')) { transitive = false @@ -63,6 +63,9 @@ dependencies { implementation(project(':bukkit-helper-118-2')) { transitive = false } + implementation(project(':bukkit-helper-119')) { + transitive = false + } } processResources { @@ -96,6 +99,7 @@ shadowJar { include(dependency(':bukkit-helper-117')) include(dependency(':bukkit-helper-118')) include(dependency(':bukkit-helper-118-2')) + include(dependency(':bukkit-helper-119')) } relocate('org.bstats', 'org.dynmap.bstats') destinationDir = file '../target' diff --git a/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java b/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java index fec426fd..a426a840 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java +++ b/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java @@ -104,6 +104,7 @@ import org.dynmap.common.DynmapPlayer; import org.dynmap.common.DynmapServerInterface; import org.dynmap.common.chunk.GenericChunkCache; import org.dynmap.common.DynmapListenerManager.EventType; +import org.dynmap.common.chunk.GenericMapChunkCache; import org.dynmap.hdmap.HDMap; import org.dynmap.markers.MarkerAPI; import org.dynmap.modsupport.ModSupportImpl; @@ -514,46 +515,62 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI { final MapChunkCache cc = c; while(!cc.isDoneLoading()) { - Future f = core.getServer().callSyncMethod(new Callable() { - public Boolean call() throws Exception { - boolean exhausted = true; - - if (prev_tick != cur_tick) { - prev_tick = cur_tick; - cur_tick_starttime = System.nanoTime(); - } - if(chunks_in_cur_tick > 0) { - boolean done = false; - while (!done) { - int cnt = chunks_in_cur_tick; - if (cnt > 5) cnt = 5; - chunks_in_cur_tick -= cc.loadChunks(cnt); - exhausted = (chunks_in_cur_tick == 0) || ((System.nanoTime() - cur_tick_starttime) > perTickLimit); - done = exhausted || cc.isDoneLoading(); + if (BukkitVersionHelper.helper.isUnsafeAsync()) { + Future f = core.getServer().callSyncMethod(new Callable() { + public Boolean call() throws Exception { + boolean exhausted = true; + + if (prev_tick != cur_tick) { + prev_tick = cur_tick; + cur_tick_starttime = System.nanoTime(); } + if (chunks_in_cur_tick > 0) { + boolean done = false; + while (!done) { + int cnt = chunks_in_cur_tick; + if (cnt > 5) cnt = 5; + chunks_in_cur_tick -= cc.loadChunks(cnt); + exhausted = (chunks_in_cur_tick == 0) || ((System.nanoTime() - cur_tick_starttime) > perTickLimit); + done = exhausted || cc.isDoneLoading(); + } + } + return exhausted; } - return exhausted; + }); + if (f == null) { + return null; + } + Boolean delay; + try { + delay = f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException cx) { + return null; + } catch (ExecutionException ex) { + Log.severe("Exception while fetching chunks: ", ex.getCause()); + return null; + } catch (Exception ix) { + Log.severe(ix); + return null; + } + + if ((delay != null) && delay.booleanValue()) { + try { + Thread.sleep(25); + } catch (InterruptedException ix) { + } + } + } else { + if (prev_tick != cur_tick) { + prev_tick = cur_tick; + cur_tick_starttime = System.nanoTime(); + } + if (cc instanceof GenericMapChunkCache) { + ((GenericMapChunkCache) cc).loadChunksAsync(); + } else { + cc.loadChunks(Integer.MAX_VALUE); } - }); - if (f == null) { - return null; - } - Boolean delay; - try { - delay = f.get(); - } catch (CancellationException cx) { - return null; - } catch (InterruptedException cx) { - return null; - } catch (ExecutionException ex) { - Log.severe("Exception while fetching chunks: ", ex.getCause()); - return null; - } catch (Exception ix) { - Log.severe(ix); - return null; - } - if((delay != null) && delay.booleanValue()) { - try { Thread.sleep(25); } catch (InterruptedException ix) {} } } /* If cancelled due to world unload return nothing */ diff --git a/spigot/src/main/java/org/dynmap/bukkit/Helper.java b/spigot/src/main/java/org/dynmap/bukkit/Helper.java index de807ecb..3856f595 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/Helper.java +++ b/spigot/src/main/java/org/dynmap/bukkit/Helper.java @@ -15,6 +15,7 @@ import org.dynmap.bukkit.helper.v116_4.BukkitVersionHelperSpigot116_4; import org.dynmap.bukkit.helper.v117.BukkitVersionHelperSpigot117; import org.dynmap.bukkit.helper.v118.BukkitVersionHelperSpigot118; import org.dynmap.bukkit.helper.v118_2.BukkitVersionHelperSpigot118_2; +import org.dynmap.bukkit.helper.v119.BukkitVersionHelperSpigot119; public class Helper { @@ -40,6 +41,9 @@ public class Helper { Log.info("Loading Glowstone support"); BukkitVersionHelper.helper = new BukkitVersionHelperGlowstone(); } + else if (v.contains("(MC: 1.19")) { + BukkitVersionHelper.helper = new BukkitVersionHelperSpigot119(); + } else if (v.contains("(MC: 1.18)") || (v.contains("(MC: 1.18.1)"))) { BukkitVersionHelper.helper = new BukkitVersionHelperSpigot118(); } diff --git a/spigot/src/main/resources/configuration.txt b/spigot/src/main/resources/configuration.txt index 87b593fd..f48f19e2 100644 --- a/spigot/src/main/resources/configuration.txt +++ b/spigot/src/main/resources/configuration.txt @@ -505,6 +505,9 @@ soft-ref-cache: true # If not set, 'dynmap url' will not return anything. URL should be fully qualified (e.g. https://mc.westeroscraft.com/) #publicURL: http://my.greatserver.com/dynmap +# Send this message if the player does not have permission to use the command +noPermissionMsg: "You don't have permission to use this command!" + # Set to true to enable verbose startup messages - can help with debugging map configuration problems # Set to false for a much quieter startup log verbose: false