From 451b166512bc41b2a0d7f7a704f5ebb1da1e2ce7 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Thu, 3 Jan 2019 20:49:39 +0800 Subject: [PATCH 01/83] Added "Do not fork" notice --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2e4134e4..86fd85d8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +## DO NOT FORK ME! Fork [flagmaggot/blocktopograph](https://github.com/flagmaggot/blocktopograph) instead! +## 【This fork is not maintained NOW】 + # Blocktopograph By @protolambda. From 87879820ec4af75fdedba5f6cc6556d1945281c1 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Fri, 11 Jan 2019 10:39:34 +0800 Subject: [PATCH 02/83] 0 --- .gitignore | 1 - app/build.gradle | 68 +++++++---------------------------- app/libs/android-leveldb.aar | Bin 0 -> 200457 bytes app/libs/tileview.aar | Bin 0 -> 77325 bytes build.gradle | 19 ++++++++-- settings.gradle | 4 +-- 6 files changed, 31 insertions(+), 61 deletions(-) create mode 100644 app/libs/android-leveldb.aar create mode 100644 app/libs/tileview.aar diff --git a/.gitignore b/.gitignore index a44b8419..0e236211 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ bin gen obj -libs local.properties .DS_Store .metadata diff --git a/app/build.gradle b/app/build.gradle index caf50d0c..68f00b04 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,7 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion '28.0.3' + compileSdkVersion 28 defaultConfig { applicationId 'com.sirkut.blocktopograph' @@ -12,73 +11,32 @@ android { versionName "1.7" } - def keystorePropertiesFile = rootProject.file("keystore.properties") - def hasKeystore = keystorePropertiesFile.exists() - def keystoreProperties - if(hasKeystore) { - keystoreProperties = new Properties() - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) - } + buildTypes { - signingConfigs { - if (hasKeystore) { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile rootProject.file(keystoreProperties['storeFile']) - storePassword keystoreProperties['storePassword'] - } - } - config { - keyAlias 'key1' - keyPassword 'keypassword' - storeFile file('/home/alex/Documents/keystore') - storePassword 'keypassword' + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } - } - - productFlavors { - if (hasKeystore) { - mainFlavor { - signingConfig signingConfigs.release - } - } - } - - buildTypes { - if (hasKeystore) { - release { - minifyEnabled true - shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - // opensource { - //minifyEnabled true - //shrinkResources true - // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - //} release { - signingConfig signingConfigs.config - } + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } } } dependencies { - implementation fileTree(include: ['*.jar', '*.so'], dir: 'libs') + implementation fileTree(include: ['*.jar', '*.so','*.aar'], dir: 'libs') testImplementation 'junit:junit:4.12' - implementation project(':android-leveldb') - implementation project(':tileview') - implementation 'com.android.support:appcompat-v7:25.4.0' - implementation 'com.android.support:recyclerview-v7:25.4.0' + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.github.clans:fab:1.6.4' - implementation 'com.android.support:design:25.4.0' + implementation 'com.android.support:design:28.0.0' implementation 'com.github.bmelnychuk:atv:1.2.8' //core is the new recommended alias for analytics implementation 'com.google.firebase:firebase-core:16.0.6' } -apply plugin: 'com.google.gms.google-services' +//apply plugin: 'com.google.gms.google-services' diff --git a/app/libs/android-leveldb.aar b/app/libs/android-leveldb.aar new file mode 100644 index 0000000000000000000000000000000000000000..0d736020c863c4e1249da5f1e5e00b5bb276abdb GIT binary patch literal 200457 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 #}hOv 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 + if (details.requested.group == 'com.android.support' + && !details.requested.name.contains('multidex')) { + details.useVersion "$supportlib_version" + } + } + } +} + task clean(type: Delete) { delete rootProject.buildDir } diff --git a/settings.gradle b/settings.gradle index 90e98b00..e7b4def4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1 @@ -include ':app', ':tileview', ':android-leveldb' -project(':tileview').projectDir = new File(rootProject.projectDir, '../TileView/tileview') -project(':android-leveldb').projectDir = new File(rootProject.projectDir, '../android-leveldb/library') +include ':app' From 29f80b08f4d2994cd0fef85bddebdd378cd86e2d Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Fri, 11 Jan 2019 10:53:11 +0800 Subject: [PATCH 03/83] 1 --- .../mithrilmania/blocktopograph/World.java | 45 +++++---- .../blocktopograph/WorldData.java | 94 ++++++++++++------- 2 files changed, 86 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/World.java b/app/src/main/java/com/mithrilmania/blocktopograph/World.java index da95f088..4680dd9c 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/World.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/World.java @@ -4,6 +4,7 @@ import com.mithrilmania.blocktopograph.nbt.convert.LevelDataConverter; import com.mithrilmania.blocktopograph.nbt.convert.NBTConstants; import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; +import com.mithrilmania.blocktopograph.nbt.tags.IntTag; import com.mithrilmania.blocktopograph.nbt.tags.LongTag; import com.mithrilmania.blocktopograph.util.io.TextFile; import com.litl.leveldb.Iterator; @@ -17,7 +18,7 @@ public class World implements Serializable { private static final long serialVersionUID = 792709417041090031L; public String getWorldDisplayName() { - if(worldName == null) return null; + if (worldName == null) return null; //return worldname, without special color codes // (character prefixed by the section-sign character) // Short quick regex, shouldn't affect performance too much @@ -41,7 +42,7 @@ public enum SpecialDBEntryType { public final String keyName; public final byte[] keyBytes; - SpecialDBEntryType(String keyName){ + SpecialDBEntryType(String keyName) { this.keyName = keyName; this.keyBytes = keyName.getBytes(NBTConstants.CHARSET); } @@ -65,36 +66,41 @@ public enum SpecialDBEntryType { public static class WorldLoadException extends Exception { private static final long serialVersionUID = 1812348294537392782L; - public WorldLoadException(String msg){ super(msg); } + public WorldLoadException(String msg) { + super(msg); + } } public World(File worldFolder) throws WorldLoadException { - if(!worldFolder.exists()) throw new WorldLoadException("Error: '"+worldFolder.getPath()+"' does not exist!"); + if (!worldFolder.exists()) + throw new WorldLoadException("Error: '" + worldFolder.getPath() + "' does not exist!"); this.worldFolder = worldFolder; // check for a custom world name File levelNameTxt = new File(this.worldFolder, "levelname.txt"); - if(levelNameTxt.exists()) worldName = TextFile.readTextFileFirstLine(levelNameTxt);// new way of naming worlds + if (levelNameTxt.exists()) + worldName = TextFile.readTextFileFirstLine(levelNameTxt);// new way of naming worlds else worldName = this.worldFolder.getName();// legacy way of naming worlds this.levelFile = new File(this.worldFolder, "level.dat"); - if(!levelFile.exists()) throw new WorldLoadException("Error: Level-file: '"+levelFile.getPath()+"' does not exist!"); + if (!levelFile.exists()) + throw new WorldLoadException("Error: Level-file: '" + levelFile.getPath() + "' does not exist!"); try { this.level = LevelDataConverter.read(levelFile); } catch (IOException e) { e.printStackTrace(); - throw new WorldLoadException("Error: failed to read level: '"+levelFile.getPath()+"' !"); + throw new WorldLoadException("Error: failed to read level: '" + levelFile.getPath() + "' !"); } } - public long getWorldSeed(){ - if(this.level == null) return 0; + public long getWorldSeed() { + if (this.level == null) return 0; LongTag seed = (LongTag) this.level.getChildTagByKey("RandomSeed"); return seed == null ? 0 : seed.getValue(); @@ -108,7 +114,7 @@ public void writeLevel(CompoundTag level) throws IOException { private MarkerManager markersManager; public MarkerManager getMarkerManager() { - if(markersManager == null) + if (markersManager == null) markersManager = new MarkerManager(new File(this.worldFolder, "markers.txt")); return markersManager; @@ -117,13 +123,17 @@ public MarkerManager getMarkerManager() { /** * @return worldFolder name, also unique save-file ID */ - public String getID(){ + public String getID() { return this.worldFolder.getName(); } - public WorldData getWorldData(){ - if(this.worldData == null) this.worldData = new WorldData(this); + public WorldData getWorldData() { + if (this.worldData == null) { + ///Meow + boolean isMeow = ((IntTag) level.getChildTagByKey("StorageVersion")).getValue() >= 7; + this.worldData = new WorldData(this, isMeow); + } return this.worldData; } @@ -158,7 +168,7 @@ public ChunkData createEmptyChunkData(int chunkX, int chunkZ, ChunkTag dataType, */ public void closeDown() throws WorldData.WorldDBException { - if(this.worldData != null) this.worldData.closeDB(); + if (this.worldData != null) this.worldData.closeDB(); } public void pause() throws WorldData.WorldDBException { @@ -174,7 +184,7 @@ public void resume() throws WorldData.WorldDBException { } //function meant for debugging, not used in production - public void logDBKeys(){ + public void logDBKeys() { try { this.getWorldData(); @@ -182,10 +192,11 @@ public void logDBKeys(){ Iterator it = worldData.db.iterator(); - for(it.seekToFirst(); it.isValid(); it.next()){ + for (it.seekToFirst(); it.isValid(); it.next()) { byte[] key = it.getKey(); byte[] value = it.getValue(); - /*if(key.length == 9 && key[8] == RegionDataType.TERRAIN.dataID) */ Log.d("key: " + new String(key) + " key in Hex: " + WorldData.bytesToHex(key, 0, key.length) + " size: "+value.length); + /*if(key.length == 9 && key[8] == RegionDataType.TERRAIN.dataID) */ + Log.d("key: " + new String(key) + " key in Hex: " + WorldData.bytesToHex(key, 0, key.length) + " size: " + value.length); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java index c9dd8f30..402b7d13 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java @@ -20,50 +20,66 @@ public class WorldData { public static class WorldDataLoadException extends Exception { private static final long serialVersionUID = 659185044124115547L; - public WorldDataLoadException(String msg){ super(msg); } + public WorldDataLoadException(String msg) { + super(msg); + } } public static class WorldDBException extends Exception { private static final long serialVersionUID = -3299282170140961220L; - public WorldDBException(String msg){ super(msg); } + public WorldDBException(String msg) { + super(msg); + } } public static class WorldDBLoadException extends Exception { private static final long serialVersionUID = 4412238820886423076L; - public WorldDBLoadException(String msg){ super(msg); } + public WorldDBLoadException(String msg) { + super(msg); + } } + ///Meow + private boolean isMeow; + private World world; public DB db; - public WorldData(World world){ + public WorldData(World world, boolean isMeow) { this.world = world; + + //Meow + this.isMeow = isMeow; + android.util.Log.e("233", "isMeow=" + isMeow); } //load db when needed (does not load it!) @SuppressLint({"SetWorldReadable", "SetWorldWritable"}) public void load() throws WorldDataLoadException { - if(db != null) return; + if (db != null) return; File dbFile = new File(this.world.worldFolder, "db"); - if(!dbFile.canRead()){ - if(!dbFile.setReadable(true, false)) throw new WorldDataLoadException("World-db folder is not readable! World-db folder: "+dbFile.getAbsolutePath()); + if (!dbFile.canRead()) { + if (!dbFile.setReadable(true, false)) + throw new WorldDataLoadException("World-db folder is not readable! World-db folder: " + dbFile.getAbsolutePath()); } - if(!dbFile.canWrite()){ - if(!dbFile.setWritable(true, false)) throw new WorldDataLoadException("World-db folder is not writable! World-db folder: "+dbFile.getAbsolutePath()); + if (!dbFile.canWrite()) { + if (!dbFile.setWritable(true, false)) + throw new WorldDataLoadException("World-db folder is not writable! World-db folder: " + dbFile.getAbsolutePath()); } - Log.d("WorldFolder: "+this.world.worldFolder.getAbsolutePath()); - Log.d("WorldFolder permissions: read: " + dbFile.canRead() + " write: "+dbFile.canWrite()); + Log.d("WorldFolder: " + this.world.worldFolder.getAbsolutePath()); + Log.d("WorldFolder permissions: read: " + dbFile.canRead() + " write: " + dbFile.canWrite()); - if(dbFile.listFiles() == null) throw new WorldDataLoadException("Failed loading world-db: cannot list files in worldfolder"); + if (dbFile.listFiles() == null) + throw new WorldDataLoadException("Failed loading world-db: cannot list files in worldfolder"); - for(File dbEntry : dbFile.listFiles()){ - Log.d("File in db: "+dbEntry.getAbsolutePath()); + for (File dbEntry : dbFile.listFiles()) { + Log.d("File in db: " + dbEntry.getAbsolutePath()); } this.db = new DB(dbFile); @@ -72,14 +88,15 @@ public void load() throws WorldDataLoadException { //open db to make it available for this app public void openDB() throws WorldDBException { - if(this.db == null) throw new WorldDBException("DB is null!!! (db is not loaded probably)"); + if (this.db == null) + throw new WorldDBException("DB is null!!! (db is not loaded probably)"); - if(this.db.isClosed()){ - try{ + if (this.db.isClosed()) { + try { this.db.open(); - } catch (Exception e){ + } catch (Exception e) { - throw new WorldDBException("DB could not be opened! "+e.getMessage()); + throw new WorldDBException("DB could not be opened! " + e.getMessage()); } } @@ -87,31 +104,36 @@ public void openDB() throws WorldDBException { //another method for debugging, makes it easy to print a readable byte array final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + public static String bytesToHex(byte[] bytes, int start, int end) { - char[] hexChars = new char[(end-start) * 2]; - for ( int j = start; j < end; j++ ) { + char[] hexChars = new char[(end - start) * 2]; + for (int j = start; j < end; j++) { int v = bytes[j] & 0xFF; - hexChars[(j-start) * 2] = hexArray[v >>> 4]; - hexChars[(j-start) * 2 + 1] = hexArray[v & 0x0F]; + hexChars[(j - start) * 2] = hexArray[v >>> 4]; + hexChars[(j - start) * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } //close db to make it available for other apps (Minecraft itself!) public void closeDB() throws WorldDBException { - if(this.db == null) throw new WorldDBException("DB is null!!! (db is not loaded probably)"); + if (this.db == null) + throw new WorldDBException("DB is null!!! (db is not loaded probably)"); try { this.db.close(); - } catch (Exception e){ + } catch (Exception e) { //db was already closed (probably) e.printStackTrace(); } } - /** WARNING: DELETES WORLD !!! */ + /** + * WARNING: DELETES WORLD !!! + */ public void destroy() throws WorldDBException { - if(this.db == null) throw new WorldDBException("DB is null!!! (db is not loaded probably)"); + if (this.db == null) + throw new WorldDBException("DB is null!!! (db is not loaded probably)"); this.db.close(); this.db.destroy(); @@ -142,33 +164,33 @@ public void removeChunkData(int x, int z, ChunkTag type, Dimension dimension, by db.delete(getChunkDataKey(x, z, type, dimension, subChunk, asSubChunk)); } - public String[] getPlayers(){ + public String[] getPlayers() { List players = getDBKeysStartingWith("player_"); return players.toArray(new String[players.size()]); } - public List getDBKeysStartingWith(String startWith){ + public List getDBKeysStartingWith(String startWith) { Iterator it = db.iterator(); ArrayList items = new ArrayList<>(); - for(it.seekToFirst(); it.isValid(); it.next()){ + for (it.seekToFirst(); it.isValid(); it.next()) { byte[] key = it.getKey(); - if(key == null) continue; + if (key == null) continue; String keyStr = new String(key); - if(keyStr.startsWith(startWith)) items.add(keyStr); + if (keyStr.startsWith(startWith)) items.add(keyStr); } it.close(); return items; } - public static byte[] getChunkDataKey(int x, int z, ChunkTag type, Dimension dimension, byte subChunk, boolean asSubChunk){ - if(dimension == Dimension.OVERWORLD) { + public static byte[] getChunkDataKey(int x, int z, ChunkTag type, Dimension dimension, byte subChunk, boolean asSubChunk) { + if (dimension == Dimension.OVERWORLD) { byte[] key = new byte[asSubChunk ? 10 : 9]; System.arraycopy(getReversedBytes(x), 0, key, 0, 4); System.arraycopy(getReversedBytes(z), 0, key, 4, 4); key[8] = type.dataID; - if(asSubChunk) key[9] = subChunk; + if (asSubChunk) key[9] = subChunk; return key; } else { byte[] key = new byte[asSubChunk ? 14 : 13]; @@ -176,7 +198,7 @@ public static byte[] getChunkDataKey(int x, int z, ChunkTag type, Dimension dime System.arraycopy(getReversedBytes(z), 0, key, 4, 4); System.arraycopy(getReversedBytes(dimension.id), 0, key, 8, 4); key[12] = type.dataID; - if(asSubChunk) key[13] = subChunk; + if (asSubChunk) key[13] = subChunk; return key; } } From 2f64da22e0f58eac1e079f2d6c15fef929de6e8f Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Fri, 11 Jan 2019 11:02:05 +0800 Subject: [PATCH 04/83] 2 --- .../blocktopograph/WorldData.java | 12 ++- .../blocktopograph/chunk/Chunk.java | 25 +++-- .../chunk/terrain/MeowTeChData.java | 93 +++++++++++++++++++ 3 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java index 402b7d13..49785ae8 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java @@ -8,7 +8,6 @@ import com.litl.leveldb.DB; import java.io.File; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -42,7 +41,7 @@ public WorldDBLoadException(String msg) { } ///Meow - private boolean isMeow; + private boolean mIsMeow; private World world; @@ -52,8 +51,13 @@ public WorldData(World world, boolean isMeow) { this.world = world; //Meow - this.isMeow = isMeow; - android.util.Log.e("233", "isMeow=" + isMeow); + this.mIsMeow = isMeow; + android.util.Log.e("233", "mIsMeow=" + isMeow); + } + + ///Meow + public boolean isMeow() { + return mIsMeow; } //load db when needed (does not load it!) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java index e473b1d5..f2c650f2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java @@ -1,6 +1,7 @@ package com.mithrilmania.blocktopograph.chunk; import com.mithrilmania.blocktopograph.WorldData; +import com.mithrilmania.blocktopograph.chunk.terrain.MeowTeChData; import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; @@ -16,8 +17,9 @@ public class Chunk { private Version version; - private AtomicReferenceArray - terrain = new AtomicReferenceArray<>(256); + ///Meow + private AtomicReferenceArray terrain; + private MeowTeChData meowTeChData; private volatile NBTChunkData entity, blockEntity; @@ -26,11 +28,14 @@ public Chunk(WorldData worldData, int x, int z, Dimension dimension) { this.x = x; this.z = z; this.dimension = dimension; + + ///Meow + terrain = new AtomicReferenceArray<>(256); } public TerrainChunkData getTerrain(byte subChunk) throws Version.VersionException { TerrainChunkData data = terrain.get(subChunk & 0xff); - if(data == null){ + if (data == null) { data = this.getVersion().createTerrainChunkData(this, subChunk); terrain.set(subChunk & 0xff, data); } @@ -38,18 +43,18 @@ public TerrainChunkData getTerrain(byte subChunk) throws Version.VersionExceptio } public NBTChunkData getEntity() throws Version.VersionException { - if(entity == null) entity = this.getVersion().createEntityChunkData(this); + if (entity == null) entity = this.getVersion().createEntityChunkData(this); return entity; } public NBTChunkData getBlockEntity() throws Version.VersionException { - if(blockEntity == null) blockEntity = this.getVersion().createBlockEntityChunkData(this); + if (blockEntity == null) blockEntity = this.getVersion().createBlockEntityChunkData(this); return blockEntity; } - public Version getVersion(){ - if(this.version == null) try { + public Version getVersion() { + if (this.version == null) try { byte[] data = this.worldData.getChunkData(x, z, ChunkTag.VERSION, dimension, (byte) 0, false); this.version = Version.getVersion(data); } catch (WorldData.WorldDBLoadException | WorldData.WorldDBException e) { @@ -65,7 +70,7 @@ public Version getVersion(){ public int getHighestBlockYAt(int x, int z) throws Version.VersionException { Version cVersion = getVersion(); TerrainChunkData data; - for(int subChunk = cVersion.subChunks - 1; subChunk >= 0; subChunk--) { + for (int subChunk = cVersion.subChunks - 1; subChunk >= 0; subChunk--) { data = this.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) continue; @@ -83,7 +88,7 @@ public int getHighestBlockYUnderAt(int x, int z, int y) throws Version.VersionEx int subChunk = y / cVersion.subChunkHeight; TerrainChunkData data; - for(; subChunk >= 0; subChunk--) { + for (; subChunk >= 0; subChunk--) { data = this.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) continue; @@ -104,7 +109,7 @@ public int getCaveYUnderAt(int x, int z, int y) throws Version.VersionException int subChunk = y / cVersion.subChunkHeight; TerrainChunkData data; - for(; subChunk >= 0; subChunk--) { + for (; subChunk >= 0; subChunk--) { data = this.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) continue; for (y = offset; y >= 0; y--) { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java new file mode 100644 index 00000000..164a58df --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java @@ -0,0 +1,93 @@ +package com.mithrilmania.blocktopograph.chunk.terrain; + +import com.mithrilmania.blocktopograph.WorldData; +import com.mithrilmania.blocktopograph.chunk.Chunk; + +import java.io.IOException; + +public class MeowTeChData extends TerrainChunkData { + + public MeowTeChData(Chunk chunk) { + super(chunk, (byte) 0); + } + + @Override + public boolean loadTerrain() { + return true; + } + + @Override + public boolean load2DData() { + return true; + } + + @Override + public byte getBlockTypeId(int x, int y, int z) { + return 9; + } + + @Override + public byte getBlockData(int x, int y, int z) { + return 0; + } + + @Override + public byte getSkyLightValue(int x, int y, int z) { + return 0; + } + + @Override + public byte getBlockLightValue(int x, int y, int z) { + return 0; + } + + @Override + public boolean supportsBlockLightValues() { + return false; + } + + @Override + public void setBlockTypeId(int x, int y, int z, int type) { + + } + + @Override + public void setBlockData(int x, int y, int z, int newData) { + + } + + @Override + public byte getBiome(int x, int z) { + return 1; + } + + @Override + public byte getGrassR(int x, int z) { + return -128; + } + + @Override + public byte getGrassG(int x, int z) { + return 0; + } + + @Override + public byte getGrassB(int x, int z) { + return 0; + } + + @Override + public int getHeightMapValue(int x, int z) { + return 1; + } + + @Override + public void createEmpty() { + + } + + @Override + public void write() throws IOException, WorldData.WorldDBException { + + } +} From 9fee5f4b16a4a788a7efd9a55c3f197894febb75 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Fri, 11 Jan 2019 17:35:21 +0800 Subject: [PATCH 05/83] Add support for Minecraft BE 1.2~1.9 levels. --- .../blocktopograph/chunk/Chunk.java | 19 +- .../chunk/terrain/MeowTeChData.java | 24 +- .../blocktopograph/map/MapFragment.java | 478 +++++++++--------- .../map/renderer/SatelliteRenderer.java | 58 ++- 4 files changed, 307 insertions(+), 272 deletions(-) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java index f2c650f2..fc91f599 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java @@ -30,10 +30,15 @@ public Chunk(WorldData worldData, int x, int z, Dimension dimension) { this.dimension = dimension; ///Meow - terrain = new AtomicReferenceArray<>(256); + if (worldData.isMeow()) + meowTeChData = new MeowTeChData(this); + else + terrain = new AtomicReferenceArray<>(256); } public TerrainChunkData getTerrain(byte subChunk) throws Version.VersionException { + ///Meow + if (worldData.isMeow()) return meowTeChData; TerrainChunkData data = terrain.get(subChunk & 0xff); if (data == null) { data = this.getVersion().createTerrainChunkData(this, subChunk); @@ -54,6 +59,9 @@ public NBTChunkData getBlockEntity() throws Version.VersionException { } public Version getVersion() { + ///Meow + if (worldData.isMeow()) return Version.V1_1; + if (this.version == null) try { byte[] data = this.worldData.getChunkData(x, z, ChunkTag.VERSION, dimension, (byte) 0, false); this.version = Version.getVersion(data); @@ -68,6 +76,9 @@ public Version getVersion() { //TODO: should we use the heightmap??? public int getHighestBlockYAt(int x, int z) throws Version.VersionException { + ///Meow + if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, 255, z); + Version cVersion = getVersion(); TerrainChunkData data; for (int subChunk = cVersion.subChunks - 1; subChunk >= 0; subChunk--) { @@ -83,6 +94,9 @@ public int getHighestBlockYAt(int x, int z) throws Version.VersionException { } public int getHighestBlockYUnderAt(int x, int z, int y) throws Version.VersionException { + ///Meow + if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); + Version cVersion = getVersion(); int offset = y % cVersion.subChunkHeight; int subChunk = y / cVersion.subChunkHeight; @@ -104,6 +118,9 @@ public int getHighestBlockYUnderAt(int x, int z, int y) throws Version.VersionEx } public int getCaveYUnderAt(int x, int z, int y) throws Version.VersionException { + ///Meow + if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); + Version cVersion = getVersion(); int offset = y % cVersion.subChunkHeight; int subChunk = y / cVersion.subChunkHeight; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java index 164a58df..73149c77 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java @@ -4,26 +4,42 @@ import com.mithrilmania.blocktopograph.chunk.Chunk; import java.io.IOException; +import java.lang.ref.WeakReference; public class MeowTeChData extends TerrainChunkData { + private com.litl.leveldb.Chunk nativeChunk; + public MeowTeChData(Chunk chunk) { - super(chunk, (byte) 0); + super(chunk, (byte) 1); } @Override public boolean loadTerrain() { + if (nativeChunk == null) + nativeChunk = new com.litl.leveldb.Chunk(this.chunk.worldData.db, this.chunk.x << 4, this.chunk.z << 4, this.chunk.dimension.id); return true; } @Override public boolean load2DData() { - return true; + return loadTerrain(); } @Override public byte getBlockTypeId(int x, int y, int z) { - return 9; + int val = nativeChunk.getBlock(x, y, z); + byte b = (byte) (val >> 8); + return b; + } + + public int getHighestBlockYUnderAt(int x, int y, int z) { + for (int yy = y; yy >= 0; yy--) { + int val = nativeChunk.getBlock(x, y, z); + if (val != 0) + return yy; + } + return 0; } @Override @@ -78,7 +94,7 @@ public byte getGrassB(int x, int z) { @Override public int getHeightMapValue(int x, int z) { - return 1; + return getHighestBlockYUnderAt(x, 255, z); } @Override diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java index 8ab1368f..98853209 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java @@ -11,6 +11,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; + import com.mithrilmania.blocktopograph.Log; import android.support.v7.widget.RecyclerView; @@ -92,8 +93,6 @@ public class MapFragment extends Fragment { public AbstractMarker localPlayerMarker; - - @Override public void onPause() { super.onPause(); @@ -132,7 +131,7 @@ public DimensionVector3 getMultiPlayerPos(String dbKey) throws Exception WorldData wData = worldProvider.getWorld().getWorldData(); wData.openDB(); byte[] data = wData.db.get(dbKey.getBytes(NBTConstants.CHARSET)); - if(data == null) throw new Exception("no data!"); + if (data == null) throw new Exception("no data!"); final CompoundTag player = (CompoundTag) DataConverter.read(data).get(0); ListTag posVec = (ListTag) player.getChildTagByKey("Pos"); @@ -142,10 +141,10 @@ public DimensionVector3 getMultiPlayerPos(String dbKey) throws Exception throw new Exception("\"Pos\" value is invalid. value: " + posVec.getValue().toString()); IntTag dimensionId = (IntTag) player.getChildTagByKey("DimensionId"); - if(dimensionId == null || dimensionId.getValue() == null) + if (dimensionId == null || dimensionId.getValue() == null) throw new Exception("No \"DimensionId\" specified"); Dimension dimension = Dimension.getDimension(dimensionId.getValue()); - if(dimension == null) dimension = Dimension.OVERWORLD; + if (dimension == null) dimension = Dimension.OVERWORLD; return new DimensionVector3<>( (float) posVec.getValue().get(0).getValue(), @@ -156,7 +155,7 @@ public DimensionVector3 getMultiPlayerPos(String dbKey) throws Exception } catch (Exception e) { Log.e(e.getMessage()); - Exception e2 = new Exception("Could not find "+dbKey); + Exception e2 = new Exception("Could not find " + dbKey); e2.setStackTrace(e.getStackTrace()); throw e2; } @@ -179,10 +178,10 @@ public DimensionVector3 getPlayerPos() throws Exception { throw new Exception("\"Pos\" value is invalid. value: " + posVec.getValue().toString()); IntTag dimensionId = (IntTag) player.getChildTagByKey("DimensionId"); - if(dimensionId == null || dimensionId.getValue() == null) + if (dimensionId == null || dimensionId.getValue() == null) throw new Exception("No \"DimensionId\" specified"); Dimension dimension = Dimension.getDimension(dimensionId.getValue()); - if(dimension == null) dimension = Dimension.OVERWORLD; + if (dimension == null) dimension = Dimension.OVERWORLD; return new DimensionVector3<>( (float) posVec.getValue().get(0).getValue(), @@ -193,9 +192,11 @@ public DimensionVector3 getPlayerPos() throws Exception { } catch (Exception e) { Log.e(e.toString()); - Exception e2 = new Exception("Could not find player."); - e2.setStackTrace(e.getStackTrace()); - throw e2; + ///Meow + return new DimensionVector3<>(0f, 64f, 0f, Dimension.OVERWORLD); +// Exception e2 = new Exception("Could not find player."); +// e2.setStackTrace(e.getStackTrace()); +// throw e2; } } @@ -205,16 +206,19 @@ public DimensionVector3 getSpawnPos() throws Exception { int spawnX = ((IntTag) level.getChildTagByKey("SpawnX")).getValue(); int spawnY = ((IntTag) level.getChildTagByKey("SpawnY")).getValue(); int spawnZ = ((IntTag) level.getChildTagByKey("SpawnZ")).getValue(); - if(spawnY == 256){ + if (spawnY == 256) { TerrainChunkData data = new ChunkManager( - this.worldProvider.getWorld().getWorldData(), Dimension.OVERWORLD) + this.worldProvider.getWorld().getWorldData(), Dimension.OVERWORLD) .getChunk(spawnX >> 4, spawnZ >> 4) .getTerrain((byte) 0); - if(data.load2DData()) spawnY = data.getHeightMapValue(spawnX % 16, spawnZ % 16) + 1; + if (data.load2DData()) + spawnY = data.getHeightMapValue(spawnX % 16, spawnZ % 16) + 1; } - return new DimensionVector3<>( spawnX, spawnY, spawnZ, Dimension.OVERWORLD); - } catch (Exception e){ - throw new Exception("Could not find spawn"); + return new DimensionVector3<>(spawnX, spawnY, spawnZ, Dimension.OVERWORLD); + } catch (Exception e) { + ///Meow + return new DimensionVector3<>(0, 64, 0, Dimension.OVERWORLD); + //throw new Exception("Could not find spawn"); } } @@ -264,17 +268,17 @@ public enum MarkerTapOption { public final int stringId; - MarkerTapOption(int id){ + MarkerTapOption(int id) { this.stringId = id; } } - public String[] getMarkerTapOptions(){ + public String[] getMarkerTapOptions() { MarkerTapOption[] values = MarkerTapOption.values(); int len = values.length; String[] options = new String[len]; - for(int i = 0; i < len; i++){ + for (int i = 0; i < len; i++) { options[i] = getString(values[i].stringId); } return options; @@ -300,7 +304,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa @Override public void onClick(View view) { try { - if(tileView == null) throw new Exception("No map available."); + if (tileView == null) throw new Exception("No map available."); DimensionVector3 playerPos = getPlayerPos(); @@ -310,7 +314,7 @@ public void onClick(View view) { Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); - if(playerPos.dimension != worldProvider.getDimension()){ + if (playerPos.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); } @@ -318,7 +322,7 @@ public void onClick(View view) { frameTo((double) playerPos.x, (double) playerPos.z); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); Snackbar.make(view, R.string.failed_find_player, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); @@ -343,7 +347,7 @@ public void onClick(View view) { Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); - if(spawnPos.dimension != worldProvider.getDimension()){ + if (spawnPos.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(spawnPos.dimension.defaultMapType, spawnPos.dimension); } @@ -351,7 +355,7 @@ public void onClick(View view) { frameTo((double) spawnPos.x, (double) spawnPos.z); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); Snackbar.make(view, R.string.failed_find_spawn, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); @@ -362,14 +366,14 @@ public void onClick(View view) { final Activity activity = getActivity(); - if(activity == null){ + if (activity == null) { new Exception("MapFragment: activity is null, cannot set worldProvider!").printStackTrace(); return null; } try { //noinspection ConstantConditions worldProvider = (WorldActivityInterface) activity; - } catch (ClassCastException e){ + } catch (ClassCastException e) { new Exception("MapFragment: activity is not an worldprovider, cannot set worldProvider!", e).printStackTrace(); return null; } @@ -380,13 +384,12 @@ public void onClick(View view) { fabMenu.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() { @Override public void onMenuToggle(boolean opened) { - if(opened) worldProvider.showActionBar(); + if (opened) worldProvider.showActionBar(); else worldProvider.hideActionBar(); } }); - FloatingActionButton fabGPSMarker = (FloatingActionButton) rootView.findViewById(R.id.fab_menu_gps_marker); assert fabGPSMarker != null; fabGPSMarker.setOnClickListener(new View.OnClickListener() { @@ -395,11 +398,11 @@ public void onClick(View view) { try { Collection markers = worldProvider.getWorld().getMarkerManager().getMarkers(); - if(markers.isEmpty()){ + if (markers.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); TextView msg = new TextView(activity); float dpi = activity.getResources().getDisplayMetrics().density; - msg.setPadding((int)(19*dpi), (int)(5*dpi), (int)(14*dpi), (int)(5*dpi)); + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); msg.setMaxLines(20); msg.setText(R.string.no_custom_markers); builder.setView(msg) @@ -423,7 +426,7 @@ public void onClick(View view) { @Override public void onClick(DialogInterface dialog, int which) { AbstractMarker m = arrayAdapter.getItem(which); - if(m == null) return; + if (m == null) return; Snackbar.make(tileView, activity.getString(R.string.something_at_xyz_dim_int, @@ -432,7 +435,7 @@ public void onClick(DialogInterface dialog, int which) { Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); - if(m.dimension != worldProvider.getDimension()){ + if (m.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(m.dimension.defaultMapType, m.dimension); } @@ -444,7 +447,7 @@ public void onClick(DialogInterface dialog, int which) { markerDialogBuilder.show(); } - } catch (Exception e){ + } catch (Exception e) { Snackbar.make(view, e.getMessage(), Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } @@ -461,105 +464,104 @@ public void onClick(DialogInterface dialog, int which) { @Override public void onClick(final View view) { - if (tileView == null){ + if (tileView == null) { Snackbar.make(view, R.string.no_map_available, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); return; } Snackbar.make(tileView, - R.string.searching_for_players, - Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); + R.string.searching_for_players, + Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); //this can take some time... ...do it in the background - (new AsyncTask(){ - - @Override - protected String[] doInBackground(Void... arg0) { - try { - return worldProvider.getWorld().getWorldData().getPlayers(); - } catch (Exception e){ - return null; - } - } - - protected void onPostExecute(final String[] players) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - - if(players == null){ - Snackbar.make(view, R.string.failed_to_retrieve_player_data, Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - return; - } - - if(players.length == 0){ - Snackbar.make(view, R.string.no_multiplayer_data_found, Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - return; - } - + (new AsyncTask() { + @Override + protected String[] doInBackground(Void... arg0) { + try { + return worldProvider.getWorld().getWorldData().getPlayers(); + } catch (Exception e) { + return null; + } + } - //NBT tag type spinner - final Spinner spinner = new Spinner(activity); - ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(activity, - android.R.layout.simple_spinner_item, players); + protected void onPostExecute(final String[] players) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { - spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(spinnerArrayAdapter); + if (players == null) { + Snackbar.make(view, R.string.failed_to_retrieve_player_data, Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + return; + } + if (players.length == 0) { + Snackbar.make(view, R.string.no_multiplayer_data_found, Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + return; + } - //wrap layout in alert - new AlertDialog.Builder(activity) - .setTitle(R.string.go_to_player) - .setView(spinner) - .setPositiveButton(R.string.go_loud, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { + //NBT tag type spinner + final Spinner spinner = new Spinner(activity); + ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(activity, + android.R.layout.simple_spinner_item, players); - //new tag type - int spinnerIndex = spinner.getSelectedItemPosition(); - String playerKey = players[spinnerIndex]; + spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(spinnerArrayAdapter); - try { - DimensionVector3 playerPos = getMultiPlayerPos(playerKey); - - Snackbar.make(tileView, - getString(R.string.something_at_xyz_dim_float, - playerKey, - playerPos.x, - playerPos.y, - playerPos.z, - playerPos.dimension.name), - Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_MULTIPLAYER); + //wrap layout in alert + new AlertDialog.Builder(activity) + .setTitle(R.string.go_to_player) + .setView(spinner) + .setPositiveButton(R.string.go_loud, new DialogInterface.OnClickListener() { - if(playerPos.dimension != worldProvider.getDimension()){ - worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); - } + public void onClick(DialogInterface dialog, int whichButton) { - frameTo((double) playerPos.x, (double) playerPos.z); + //new tag type + int spinnerIndex = spinner.getSelectedItemPosition(); + String playerKey = players[spinnerIndex]; - } catch (Exception e){ - Snackbar.make(view, e.getMessage(), Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); + try { + DimensionVector3 playerPos = getMultiPlayerPos(playerKey); + + Snackbar.make(tileView, + getString(R.string.something_at_xyz_dim_float, + playerKey, + playerPos.x, + playerPos.y, + playerPos.z, + playerPos.dimension.name), + Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + + worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_MULTIPLAYER); + + if (playerPos.dimension != worldProvider.getDimension()) { + worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); } + frameTo((double) playerPos.x, (double) playerPos.z); + + } catch (Exception e) { + Snackbar.make(view, e.getMessage(), Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); } - }) - //or alert is cancelled - .setNegativeButton(android.R.string.cancel, null) - .show(); - } - }); - } - }).execute(); + } + }) + //or alert is cancelled + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + }); + } + + }).execute(); } }); @@ -574,7 +576,7 @@ public void onClick(DialogInterface dialog, int whichButton) { @Override public void onClick(View view) { try { - if(tileView == null) throw new Exception("No map available."); + if (tileView == null) throw new Exception("No map available."); View xzForm = LayoutInflater.from(activity).inflate(R.layout.xz_coord_form, null); final EditText xInput = (EditText) xzForm.findViewById(R.id.x_input); @@ -584,22 +586,22 @@ public void onClick(View view) { //wrap layout in alert new AlertDialog.Builder(activity) - .setTitle(R.string.go_to_coordinate) - .setView(xzForm) - .setPositiveButton(R.string.go_loud, new DialogInterface.OnClickListener() { + .setTitle(R.string.go_to_coordinate) + .setView(xzForm) + .setPositiveButton(R.string.go_loud, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { int inX, inZ; - try{ + try { inX = Integer.parseInt(xInput.getText().toString()); - } catch (NullPointerException | NumberFormatException e){ - Toast.makeText(activity, R.string.invalid_x_coordinate,Toast.LENGTH_LONG).show(); + } catch (NullPointerException | NumberFormatException e) { + Toast.makeText(activity, R.string.invalid_x_coordinate, Toast.LENGTH_LONG).show(); return; } - try{ + try { inZ = Integer.parseInt(zInput.getText().toString()); - } catch (NullPointerException | NumberFormatException e){ - Toast.makeText(activity, R.string.invalid_z_coordinate,Toast.LENGTH_LONG).show(); + } catch (NullPointerException | NumberFormatException e) { + Toast.makeText(activity, R.string.invalid_z_coordinate, Toast.LENGTH_LONG).show(); return; } @@ -608,11 +610,11 @@ public void onClick(DialogInterface dialog, int whichButton) { frameTo((double) inX, (double) inZ); } }) - .setCancelable(true) - .setNegativeButton(android.R.string.cancel, null) - .show(); + .setCancelable(true) + .setNegativeButton(android.R.string.cancel, null) + .show(); - } catch (Exception e){ + } catch (Exception e) { Snackbar.make(view, e.getMessage(), Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } @@ -620,7 +622,6 @@ public void onClick(DialogInterface dialog, int whichButton) { }); - try { Entity.loadEntityBitmaps(activity.getAssets()); } catch (IOException e) { @@ -632,9 +633,9 @@ public void onClick(DialogInterface dialog, int whichButton) { } catch (IOException e) { e.printStackTrace(); } - try{ + try { CustomIcon.loadCustomBitmaps(activity.getAssets()); - } catch (IOException e){ + } catch (IOException e) { e.printStackTrace(); } @@ -659,8 +660,8 @@ public void onLongPress(MotionEvent event) { float pixelsPerBlockScaledL = pixelsPerBlockL_unscaled * this.getScale(); - double worldX = ((( this.getScrollX() + event.getX()) / pixelsPerBlockScaledW) - MCTileProvider.HALF_WORLDSIZE) / dimension.dimensionScale; - double worldZ = ((( this.getScrollY() + event.getY()) / pixelsPerBlockScaledL) - MCTileProvider.HALF_WORLDSIZE) / dimension.dimensionScale; + double worldX = (((this.getScrollX() + event.getX()) / pixelsPerBlockScaledW) - MCTileProvider.HALF_WORLDSIZE) / dimension.dimensionScale; + double worldZ = (((this.getScrollY() + event.getY()) / pixelsPerBlockScaledL) - MCTileProvider.HALF_WORLDSIZE) / dimension.dimensionScale; MapFragment.this.onLongClick(worldX, worldZ); } @@ -710,12 +711,12 @@ Create tile(=bitmap) provider this.tileView.setSize(MCTileProvider.viewSizeW, MCTileProvider.viewSizeL); - for(MapType mapType : MapType.values()){ - this.tileView.addDetailLevel(0.0625f, "0.0625", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType);// 1/(1/16)=16 chunks per tile - this.tileView.addDetailLevel(0.125f, "0.125", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); - this.tileView.addDetailLevel(0.25f, "0.25", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); - this.tileView.addDetailLevel(0.5f, "0.5", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); - this.tileView.addDetailLevel(1f, "1", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType);// 1/1=1 chunk per tile + for (MapType mapType : MapType.values()) { + this.tileView.addDetailLevel(0.0625f, "0.0625", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType);// 1/(1/16)=16 chunks per tile + this.tileView.addDetailLevel(0.125f, "0.125", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); + this.tileView.addDetailLevel(0.25f, "0.25", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); + this.tileView.addDetailLevel(0.5f, "0.5", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); + this.tileView.addDetailLevel(1f, "1", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType);// 1/1=1 chunk per tile } @@ -729,22 +730,22 @@ Create tile(=bitmap) provider DimensionVector3 playerPos = getPlayerPos(); float x = playerPos.x, y = playerPos.y, z = playerPos.z; - Log.d("Placed player marker at: "+x+";"+y+";"+z+" ["+playerPos.dimension.name+"]"); + Log.d("Placed player marker at: " + x + ";" + y + ";" + z + " [" + playerPos.dimension.name + "]"); localPlayerMarker = new AbstractMarker((int) x, (int) y, (int) z, playerPos.dimension, new CustomNamedBitmapProvider(Entity.PLAYER, "~local_player"), false); this.staticMarkers.add(localPlayerMarker); addMarker(localPlayerMarker); - if(localPlayerMarker.dimension != worldProvider.getDimension()){ + if (localPlayerMarker.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(localPlayerMarker.dimension.defaultMapType, localPlayerMarker.dimension); } frameTo((double) x, (double) z); framedToPlayer = true; - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); - Log.d("Failed to place player marker. "+e.toString()); + Log.d("Failed to place player marker. " + e.toString()); } @@ -756,37 +757,33 @@ Create tile(=bitmap) provider this.staticMarkers.add(spawnMarker); addMarker(spawnMarker); - if(!framedToPlayer){ + if (!framedToPlayer) { - if(spawnMarker.dimension != worldProvider.getDimension()){ + if (spawnMarker.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(spawnMarker.dimension.defaultMapType, spawnMarker.dimension); } frameTo((double) spawnPos.x, (double) spawnPos.z); } - } catch (Exception e){ + } catch (Exception e) { //no spawn defined... - if(!framedToPlayer) frameTo(0.0, 0.0); + if (!framedToPlayer) frameTo(0.0, 0.0); } - - - - tileView.getMarkerLayout().setMarkerTapListener(new MarkerLayout.MarkerTapListener() { @Override public void onMarkerTap(View view, int tapX, int tapY) { - if(!(view instanceof MarkerImageView)){ - Log.d("Markertaplistener found a marker that is not a MarkerImageView! "+view.toString()); + if (!(view instanceof MarkerImageView)) { + Log.d("Markertaplistener found a marker that is not a MarkerImageView! " + view.toString()); return; } final AbstractMarker marker = ((MarkerImageView) view).getMarkerHook(); - if(marker == null){ - Log.d("abstract marker is null! "+view.toString()); + if (marker == null) { + Log.d("abstract marker is null! " + view.toString()); return; } @@ -800,7 +797,7 @@ public void onClick(DialogInterface dialog, int which) { final MarkerTapOption chosen = MarkerTapOption.values()[which]; - switch (chosen){ + switch (chosen) { case TELEPORT_LOCAL_PLAYER: { try { final EditableNBT playerEditable = worldProvider.getEditablePlayer(); @@ -816,7 +813,8 @@ public void onClick(DialogInterface dialog, int which) { ListTag posVec = (ListTag) playerTag.getChildTagByKey("Pos"); - if (posVec == null) throw new Exception("No \"Pos\" specified"); + if (posVec == null) + throw new Exception("No \"Pos\" specified"); final List playerPos = posVec.getValue(); if (playerPos == null) @@ -825,7 +823,7 @@ public void onClick(DialogInterface dialog, int which) { throw new Exception("\"Pos\" value is invalid. value: " + posVec.getValue().toString()); IntTag dimensionId = (IntTag) playerTag.getChildTagByKey("DimensionId"); - if(dimensionId == null || dimensionId.getValue() == null) + if (dimensionId == null || dimensionId.getValue() == null) throw new Exception("No \"DimensionId\" specified"); @@ -840,19 +838,19 @@ public void onClick(DialogInterface dialog, int which) { dimensionId.setValue(newDimension.id); - if(playerEditable.save()){ + if (playerEditable.save()) { - localPlayerMarker = moveMarker(localPlayerMarker,newX, newY, newZ, newDimension); + localPlayerMarker = moveMarker(localPlayerMarker, newX, newY, newZ, newDimension); //TODO could be improved for translation friendliness Snackbar.make(tileView, - activity.getString(R.string.teleported_player_to_xyz_dimension)+newX+";"+newY+";"+newZ+" ["+newDimension.name+"] ("+marker.getNamedBitmapProvider().getBitmapDisplayName()+")", + activity.getString(R.string.teleported_player_to_xyz_dimension) + newX + ";" + newY + ";" + newZ + " [" + newDimension.name + "] (" + marker.getNamedBitmapProvider().getBitmapDisplayName() + ")", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } else throw new Exception("Failed saving player"); - } catch (Exception e){ + } catch (Exception e) { Log.w(e.toString()); Snackbar.make(tileView, R.string.failed_teleporting_player, @@ -862,7 +860,7 @@ public void onClick(DialogInterface dialog, int which) { return; } case REMOVE_MARKER: { - if(marker.isCustom){ + if (marker.isCustom) { MapFragment.this.removeMarker(marker); MarkerManager mng = MapFragment.this.worldProvider.getWorld().getMarkerManager(); mng.removeMarker(marker, true); @@ -879,15 +877,14 @@ public void onClick(DialogInterface dialog, int which) { } } }) - .setCancelable(true) - .setNegativeButton(android.R.string.cancel, null) - .show(); + .setCancelable(true) + .setNegativeButton(android.R.string.cancel, null) + .show(); } }); - //do not loop the scale tileView.setShouldLoopScale(false); @@ -900,27 +897,27 @@ public void onClick(DialogInterface dialog, int which) { this.tileView.setSaveEnabled(true); - return rootView; } /** * Creates a new marker (looking exactly the same as the old one) on the new position, - * while removing the old marker. - * @param marker Marker to be recreated - * @param x new pos X - * @param y new pos Y - * @param z new pos Z + * while removing the old marker. + * + * @param marker Marker to be recreated + * @param x new pos X + * @param y new pos Y + * @param z new pos Z * @param dimension new pos Dimension * @return the newly created marker, which should replace the old one. */ - public AbstractMarker moveMarker(AbstractMarker marker, int x, int y, int z, Dimension dimension){ + public AbstractMarker moveMarker(AbstractMarker marker, int x, int y, int z, Dimension dimension) { AbstractMarker newMarker = marker.copy(x, y, z, dimension); - if(staticMarkers.remove(marker)) staticMarkers.add(newMarker); + if (staticMarkers.remove(marker)) staticMarkers.add(newMarker); this.removeMarker(marker); this.addMarker(newMarker); - if(marker.isCustom){ + if (marker.isCustom) { MarkerManager mng = this.worldProvider.getWorld().getMarkerManager(); mng.removeMarker(marker, true); mng.addMarker(newMarker, true); @@ -936,11 +933,12 @@ public AbstractMarker moveMarker(AbstractMarker marker, int x, int y, int z, Dim /** * Calculates viewport of tileview, expressed in blocks. + * * @param marginX horizontal viewport-margin, in pixels * @param marginZ vertical viewport-margin, in pixels * @return minimum_X, maximum_X, minimum_Z, maximum_Z, dimension. (min and max are expressed in blocks!) */ - public Object[] calculateViewPort(int marginX, int marginZ){ + public Object[] calculateViewPort(int marginX, int marginZ) { Dimension dimension = this.worldProvider.getDimension(); @@ -954,18 +952,18 @@ public Object[] calculateViewPort(int marginX, int marginZ){ double pixelsPerBlockW = pixelsPerBlockW_unscaled * scale; double pixelsPerBlockL = pixelsPerBlockL_unscaled * scale; - long blockX = Math.round( (tileView.getScrollX() - marginX) / pixelsPerBlockW ) - MCTileProvider.HALF_WORLDSIZE; - long blockZ = Math.round( (tileView.getScrollY() - marginZ) / pixelsPerBlockL ) - MCTileProvider.HALF_WORLDSIZE; - long blockW = Math.round( (tileView.getWidth() + marginX + marginX) / pixelsPerBlockW ); - long blockH = Math.round( (tileView.getHeight() + marginZ + marginZ) / pixelsPerBlockL ); + long blockX = Math.round((tileView.getScrollX() - marginX) / pixelsPerBlockW) - MCTileProvider.HALF_WORLDSIZE; + long blockZ = Math.round((tileView.getScrollY() - marginZ) / pixelsPerBlockL) - MCTileProvider.HALF_WORLDSIZE; + long blockW = Math.round((tileView.getWidth() + marginX + marginX) / pixelsPerBlockW); + long blockH = Math.round((tileView.getHeight() + marginZ + marginZ) / pixelsPerBlockL); - return new Object[]{ blockX, blockX + blockW, blockZ, blockH, dimension }; + return new Object[]{blockX, blockX + blockW, blockZ, blockH, dimension}; } - public AsyncTask retainViewPortMarkers(final Runnable callback){ + public AsyncTask retainViewPortMarkers(final Runnable callback) { DisplayMetrics displayMetrics = this.getActivity().getResources().getDisplayMetrics(); - return new AsyncTask(){ + return new AsyncTask() { @Override protected Void doInBackground(Object... params) { long minX = (long) params[0], @@ -974,12 +972,12 @@ protected Void doInBackground(Object... params) { maxY = (long) params[3]; Dimension reqDim = (Dimension) params[4]; - for (AbstractMarker p : MapFragment.this.proceduralMarkers){ + for (AbstractMarker p : MapFragment.this.proceduralMarkers) { // do not remove static markers - if(MapFragment.this.staticMarkers.contains(p)) continue; + if (MapFragment.this.staticMarkers.contains(p)) continue; - if(p.x < minX || p.x > maxX || p.y < minY || p.y > maxY || p.dimension != reqDim){ + if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY || p.dimension != reqDim) { this.publishProgress(p); } } @@ -988,7 +986,7 @@ protected Void doInBackground(Object... params) { @Override protected void onProgressUpdate(final AbstractMarker... values) { - for(AbstractMarker v : values) { + for (AbstractMarker v : values) { MapFragment.this.removeMarker(v); } } @@ -1006,24 +1004,26 @@ protected void onPostExecute(Void aVoid) { /** * Important: this method should be run from the UI thread. + * * @param marker The marker to remove from the tile view. */ - public void removeMarker(AbstractMarker marker){ + public void removeMarker(AbstractMarker marker) { staticMarkers.remove(marker); proceduralMarkers.remove(marker); - if(marker.view != null) tileView.removeMarker(marker.view); + if (marker.view != null) tileView.removeMarker(marker.view); } /** * Important: this method should be run from the UI thread. + * * @param marker The marker to add to the tile view. */ - public void addMarker(AbstractMarker marker){ + public void addMarker(AbstractMarker marker) { - if(proceduralMarkers.contains(marker)) return; + if (proceduralMarkers.contains(marker)) return; - if(shrinkProceduralMarkersTask == null - && ++proceduralMarkersInterval > MARKER_INTERVAL_CHECK){ + if (shrinkProceduralMarkersTask == null + && ++proceduralMarkersInterval > MARKER_INTERVAL_CHECK) { //shrink set of markers to viewport every so often shrinkProceduralMarkersTask = retainViewPortMarkers(new Runnable() { @Override @@ -1046,7 +1046,7 @@ public void run() { this.filterMarker(marker); - if(tileView.getMarkerLayout().indexOfChild(markerView) >= 0){ + if (tileView.getMarkerLayout().indexOfChild(markerView) >= 0) { tileView.getMarkerLayout().removeMarker(markerView); } @@ -1057,9 +1057,9 @@ public void run() { -0.5f, -0.5f); } - public void toggleMarkers(){ + public void toggleMarkers() { int visibility = tileView.getMarkerLayout().getVisibility(); - tileView.getMarkerLayout().setVisibility( visibility == View.VISIBLE ? View.GONE : View.VISIBLE); + tileView.getMarkerLayout().setVisibility(visibility == View.VISIBLE ? View.GONE : View.VISIBLE); } @@ -1074,25 +1074,25 @@ public enum LongClickOption { public final int stringId; public final ChunkTag dataType; - LongClickOption(int id, ChunkTag dataType){ + LongClickOption(int id, ChunkTag dataType) { this.stringId = id; this.dataType = dataType; } } - public String[] getLongClickOptions(){ + public String[] getLongClickOptions() { LongClickOption[] values = LongClickOption.values(); int len = values.length; String[] options = new String[len]; - for(int i = 0; i < len; i++){ + for (int i = 0; i < len; i++) { options[i] = getString(values[i].stringId); } return options; } - public void onLongClick(final double worldX, final double worldZ){ + public void onLongClick(final double worldX, final double worldZ) { final Activity activity = MapFragment.this.getActivity(); @@ -1108,9 +1108,8 @@ public void onLongClick(final double worldX, final double worldZ){ final int chunkZint = chunkZ < 0 ? (((int) chunkZ) - 1) : ((int) chunkZ); - final View container = activity.findViewById(R.id.world_content); - if(container == null){ + if (container == null) { Log.w("CANNOT FIND MAIN CONTAINER, WTF"); return; } @@ -1124,15 +1123,17 @@ public void onClick(DialogInterface dialog, int which) { final LongClickOption chosen = LongClickOption.values()[which]; - switch (chosen){ - case TELEPORT_LOCAL_PLAYER:{ + switch (chosen) { + case TELEPORT_LOCAL_PLAYER: { try { final EditableNBT playerEditable = MapFragment.this.worldProvider.getEditablePlayer(); - if(playerEditable == null) throw new Exception("Player is null"); + if (playerEditable == null) + throw new Exception("Player is null"); Iterator playerIter = playerEditable.getTags().iterator(); - if(!playerIter.hasNext()) throw new Exception("Player DB entry is empty!"); + if (!playerIter.hasNext()) + throw new Exception("Player DB entry is empty!"); //db entry consists of one compound tag final CompoundTag playerTag = (CompoundTag) playerIter.next(); @@ -1142,13 +1143,13 @@ public void onClick(DialogInterface dialog, int which) { if (posVec == null) throw new Exception("No \"Pos\" specified"); final List playerPos = posVec.getValue(); - if(playerPos == null) + if (playerPos == null) throw new Exception("No \"Pos\" specified"); if (playerPos.size() != 3) throw new Exception("\"Pos\" value is invalid. value: " + posVec.getValue().toString()); final IntTag dimensionId = (IntTag) playerTag.getChildTagByKey("DimensionId"); - if(dimensionId == null || dimensionId.getValue() == null) + if (dimensionId == null || dimensionId.getValue() == null) throw new Exception("No \"DimensionId\" specified"); @@ -1172,11 +1173,11 @@ public void onClick(DialogInterface dialog, int which) { float newY; Editable value = yInput.getText(); - if(value == null) newY = 64f; + if (value == null) newY = 64f; else { try { newY = (float) Float.parseFloat(value.toString());//removes excessive precision - } catch (Exception e){ + } catch (Exception e) { newY = 64f; } } @@ -1186,11 +1187,11 @@ public void onClick(DialogInterface dialog, int which) { ((FloatTag) playerPos.get(2)).setValue(newZ); dimensionId.setValue(dim.id); - if(playerEditable.save()){ + if (playerEditable.save()) { MapFragment.this.localPlayerMarker = MapFragment.this.moveMarker( MapFragment.this.localPlayerMarker, - (int) newX, (int) newY, (int) newZ, dim); + (int) newX, (int) newY, (int) newZ, dim); Snackbar.make(container, String.format(getString(R.string.teleported_player_to_xyz_dim), newX, newY, newZ, dim.name), @@ -1209,7 +1210,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); return; - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); Snackbar.make(container, R.string.failed_to_find_or_edit_local_player_data, Snackbar.LENGTH_LONG) @@ -1217,7 +1218,7 @@ public void onClick(DialogInterface dialog, int which) { return; } } - case CREATE_MARKER:{ + case CREATE_MARKER: { View createMarkerForm = LayoutInflater.from(activity).inflate(R.layout.create_marker_form, null); @@ -1238,7 +1239,7 @@ public void onClick(DialogInterface dialog, int which) { .setView(createMarkerForm) .setPositiveButton("Create marker", new DialogInterface.OnClickListener() { - public void failParseSnackbarReport(int msg){ + public void failParseSnackbarReport(int msg) { Snackbar.make(container, msg, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); @@ -1292,7 +1293,7 @@ public void onClick(DialogInterface dialog, int which) { mng.save(); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); failParseSnackbarReport(R.string.failed_to_create_marker); } @@ -1303,9 +1304,6 @@ public void onClick(DialogInterface dialog, int which) { .show(); - - - return; } /* TODO multi player teleporting @@ -1314,13 +1312,13 @@ public void onClick(DialogInterface dialog, int which) { break; }*/ case ENTITY: - case TILE_ENTITY:{ + case TILE_ENTITY: { final World world = MapFragment.this.worldProvider.getWorld(); final ChunkManager chunkManager = new ChunkManager(world.getWorldData(), dim); final Chunk chunk = chunkManager.getChunk(chunkXint, chunkZint); - if(!chunkDataNBT(chunk, chosen == LongClickOption.ENTITY)){ + if (!chunkDataNBT(chunk, chosen == LongClickOption.ENTITY)) { Snackbar.make(container, String.format(getString(R.string.failed_to_load_x), getString(chosen.stringId)), Snackbar.LENGTH_LONG) .setAction("Action", null).show(); @@ -1342,11 +1340,11 @@ public void onClick(DialogInterface dialog, int which) { /** * Opens the chunk data nbt editor. * - * @param chunk the chunk to edit + * @param chunk the chunk to edit * @param entity if it is entity data (True) or block-entity data (False) * @return false when the chunk data could not be loaded. */ - private boolean chunkDataNBT(Chunk chunk, boolean entity){ + private boolean chunkDataNBT(Chunk chunk, boolean entity) { NBTChunkData chunkData; try { chunkData = entity ? chunk.getEntity() : chunk.getBlockEntity(); @@ -1381,7 +1379,7 @@ public static class NamedBitmapChoice { boolean enabledTemp; boolean enabled; - public NamedBitmapChoice(NamedBitmapProviderHandle namedBitmap, boolean enabled){ + public NamedBitmapChoice(NamedBitmapProviderHandle namedBitmap, boolean enabled) { this.namedBitmap = namedBitmap; this.enabled = this.enabledTemp = enabled; } @@ -1396,11 +1394,11 @@ public BitmapChoiceListAdapter(Context context, List objects) public View getView(final int position, View v, @NonNull ViewGroup parent) { final NamedBitmapChoice m = getItem(position); - if(m == null) return new RelativeLayout(getContext()); + if (m == null) return new RelativeLayout(getContext()); if (v == null) v = LayoutInflater - .from(getContext()) - .inflate(R.layout.img_name_check_list_entry, parent, false); + .from(getContext()) + .inflate(R.layout.img_name_check_list_entry, parent, false); ImageView img = (ImageView) v.findViewById(R.id.entry_img); @@ -1420,10 +1418,10 @@ public View getView(final int position, View v, @NonNull ViewGroup parent) { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { Object tag = compoundButton.getTag(); - if(tag == null) return; + if (tag == null) return; int position = (int) tag; final NamedBitmapChoice m = getItem(position); - if(m == null) return; + if (m == null) return; m.enabledTemp = b; } }; @@ -1439,7 +1437,7 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { for (Entity v : Entity.values()) { //skip things without a bitmap (dropped items etc.) //skip entities with placeholder ids (900+) - if(v.sheetPos < 0 || v.id >= 900) continue; + if (v.sheetPos < 0 || v.id >= 900) continue; markerFilter.put(v.getNamedBitmapProvider(), new BitmapChoiceListAdapter.NamedBitmapChoice(v, true)); } @@ -1468,7 +1466,6 @@ public int compare(BitmapChoiceListAdapter.NamedBitmapChoice a, BitmapChoiceList }); - new AlertDialog.Builder(activity) .setTitle(R.string.filter_markers) .setAdapter(new BitmapChoiceListAdapter(activity, choices), null) @@ -1477,7 +1474,7 @@ public int compare(BitmapChoiceListAdapter.NamedBitmapChoice a, BitmapChoiceList @Override public void onClick(DialogInterface dialogInterface, int i) { //save all the temporary states. - for(BitmapChoiceListAdapter.NamedBitmapChoice choice : choices){ + for (BitmapChoiceListAdapter.NamedBitmapChoice choice : choices) { choice.enabled = choice.enabledTemp; } MapFragment.this.updateMarkerFilter(); @@ -1487,7 +1484,7 @@ public void onClick(DialogInterface dialogInterface, int i) { @Override public void onClick(DialogInterface dialogInterface, int i) { //reset all the temporary states. - for(BitmapChoiceListAdapter.NamedBitmapChoice choice : choices){ + for (BitmapChoiceListAdapter.NamedBitmapChoice choice : choices) { choice.enabledTemp = choice.enabled; } } @@ -1496,7 +1493,7 @@ public void onClick(DialogInterface dialogInterface, int i) { @Override public void onCancel(DialogInterface dialogInterface) { //reset all the temporary states. - for(BitmapChoiceListAdapter.NamedBitmapChoice choice : choices){ + for (BitmapChoiceListAdapter.NamedBitmapChoice choice : choices) { choice.enabledTemp = choice.enabled; } } @@ -1504,27 +1501,27 @@ public void onCancel(DialogInterface dialogInterface) { .show(); } - public void updateMarkerFilter(){ - for(AbstractMarker marker : this.proceduralMarkers){ + public void updateMarkerFilter() { + for (AbstractMarker marker : this.proceduralMarkers) { filterMarker(marker); } } - public void filterMarker(AbstractMarker marker){ + public void filterMarker(AbstractMarker marker) { BitmapChoiceListAdapter.NamedBitmapChoice choice = markerFilter.get(marker.getNamedBitmapProvider()); - if(choice != null){ + if (choice != null) { marker.getView(this.getActivity()).setVisibility( (choice.enabled && marker.dimension == worldProvider.getDimension()) ? View.VISIBLE : View.INVISIBLE); } else { marker.getView(this.getActivity()) .setVisibility(marker.dimension == worldProvider.getDimension() - ? View.VISIBLE : View.INVISIBLE); + ? View.VISIBLE : View.INVISIBLE); } } - public void resetTileView(){ - if(this.tileView != null){ + public void resetTileView() { + if (this.tileView != null) { worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_RESET); updateMarkerFilter(); @@ -1535,7 +1532,7 @@ public void resetTileView(){ } } - public void invalidateTileView(){ + public void invalidateTileView() { MapType redo = worldProvider.getMapType(); DetailLevelManager manager = tileView.getDetailLevelManager(); //just swap mapType twice; it is not rendered, but it invalidates all tiles. @@ -1545,17 +1542,16 @@ public void invalidateTileView(){ } - /** * This is a convenience method to scrollToAndCenter after layout (which won't happen if called directly in onCreate * see https://github.com/moagrius/TileView/wiki/FAQ */ - public void frameTo( final double worldX, final double worldZ ) { + public void frameTo(final double worldX, final double worldZ) { this.tileView.post(new Runnable() { @Override public void run() { Dimension dimension = worldProvider.getDimension(); - if(tileView != null) tileView.scrollToAndCenter( + if (tileView != null) tileView.scrollToAndCenter( dimension.dimensionScale * worldX / (double) MCTileProvider.HALF_WORLDSIZE, dimension.dimensionScale * worldZ / (double) MCTileProvider.HALF_WORLDSIZE); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java index c63e2ab6..a3db6887 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java @@ -1,6 +1,7 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; + import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.chunk.Chunk; @@ -15,21 +16,21 @@ public class SatelliteRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param bX begin block X coordinate, relative to chunk edge + * @param bZ begin block Z coordinate, relative to chunk edge + * @param eX end block X coordinate, relative to chunk edge + * @param eZ end block Z coordinate, relative to chunk edge + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { @@ -37,35 +38,38 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in Chunk chunk = cm.getChunk(chunkX, chunkZ); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData data = chunk.getTerrain((byte) 0); - if(data == null || !data.load2DData()) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (data == null || !data.load2DData()) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); TerrainChunkData dataW = cm.getChunk(chunkX - 1, chunkZ).getTerrain((byte) 0); - TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ-1).getTerrain((byte) 0); + TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ - 1).getTerrain((byte) 0); boolean west = dataW != null && dataW.load2DData(), north = dataN != null && dataN.load2DData(); int x, y, z, color, i, j, tX, tY; - for (z = bZ, tY = pY ; z < eZ; z++, tY += pL) { + for (z = bZ, tY = pY; z < eZ; z++, tY += pL) { for (x = bX, tX = pX; x < eX; x++, tX += pW) { y = data.getHeightMapValue(x, z); color = getColumnColour(chunk, data, x, y, z, (x == 0) ? (west ? dataW.getHeightMapValue(dimension.chunkW - 1, z) : y)//chunk edge - : data.getHeightMapValue(x - 1, z),//within chunk + : data.getHeightMapValue(x - 1, z),//within chunk (z == 0) ? (north ? dataN.getHeightMapValue(x, dimension.chunkL - 1) : y)//chunk edge - : data.getHeightMapValue(x, z - 1)//within chunk + : data.getHeightMapValue(x, z - 1)//within chunk ); - for(i = 0; i < pL; i++){ - for(j = 0; j < pW; j++){ + for (i = 0; i < pL; i++) { + for (j = 0; j < pW; j++) { bm.setPixel(tX + j, tY + i, color); } } @@ -104,16 +108,18 @@ private static int getColumnColour(Chunk chunk, TerrainChunkData floorData, int TerrainChunkData data; - subChunkLoop: for(; subChunk >= 0; subChunk--) { + subChunkLoop: + for (; subChunk >= 0; subChunk--) { data = chunk.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()){ + if (data == null || !data.loadTerrain()) { //start at the top of the next chunk! (current offset might differ) offset = cVersion.subChunkHeight - 1; continue; } - for (y = offset; y >= 0; y--) { + ///Meow + for (y = 255; y >= 0; y--) { id = data.getBlockTypeId(x, y, z) & 0xff; @@ -158,7 +164,7 @@ private static int getColumnColour(Chunk chunk, TerrainChunkData floorData, int a *= 1f - blockA; // break when an opaque block is encountered - if (block.color.alpha == 0xff){ + if (block.color.alpha == 0xff) { break subChunkLoop; } } From 68fd5bcdbc32a969af6ca343cf2014ab57d6d49b Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Wed, 16 Jan 2019 20:21:03 +0800 Subject: [PATCH 06/83] * Embedded leveldb as a git submodule --- .gitmodules | 3 + app/build.gradle | 8 +- app/libs/android-leveldb.aar | Bin 200457 -> 0 bytes .../chunk/terrain/MeowTeChData.java | 23 +- leveldb/build.gradle | 39 +++ leveldb/proguard-rules.pro | 22 ++ .../java/com/litl/leveldb/test/DBTests.java | 261 ++++++++++++++ leveldb/src/main/AndroidManifest.xml | 5 + .../src/main/java/com/litl/leveldb/Chunk.java | 35 ++ .../src/main/java/com/litl/leveldb/DB.java | 169 ++++++++++ .../leveldb/DatabaseCorruptException.java | 12 + .../main/java/com/litl/leveldb/Iterator.java | 73 ++++ .../com/litl/leveldb/LevelDBException.java | 16 + .../java/com/litl/leveldb/NativeObject.java | 86 +++++ .../com/litl/leveldb/NotFoundException.java | 12 + .../java/com/litl/leveldb/WriteBatch.java | 50 +++ leveldb/src/main/jni/Android.mk | 68 ++++ leveldb/src/main/jni/Application.mk | 2 + leveldb/src/main/jni/Chunk.cc | 88 +++++ leveldb/src/main/jni/Chunk.h | 44 +++ leveldb/src/main/jni/blocknames.cc | 277 +++++++++++++++ leveldb/src/main/jni/blocknames.h | 17 + .../src/main/jni/com_litl_leveldb_Chunk.cc | 45 +++ leveldb/src/main/jni/com_litl_leveldb_DB.cc | 318 ++++++++++++++++++ .../src/main/jni/com_litl_leveldb_Iterator.cc | 128 +++++++ .../main/jni/com_litl_leveldb_WriteBatch.cc | 144 ++++++++ leveldb/src/main/jni/debug_conf.h | 34 ++ leveldb/src/main/jni/leveldb-mcpe | 1 + leveldb/src/main/jni/leveldbjni.cc | 46 +++ leveldb/src/main/jni/leveldbjni.h | 15 + leveldb/src/main/jni/mapkey.h | 51 +++ leveldb/src/main/jni/qstr.h | 23 ++ leveldb/src/main/jni/subchunk.cc | 163 +++++++++ leveldb/src/main/jni/subchunk.h | 48 +++ settings.gradle | 1 + 35 files changed, 2318 insertions(+), 9 deletions(-) create mode 100644 .gitmodules delete mode 100644 app/libs/android-leveldb.aar create mode 100644 leveldb/build.gradle create mode 100644 leveldb/proguard-rules.pro create mode 100644 leveldb/src/androidTest/java/com/litl/leveldb/test/DBTests.java create mode 100644 leveldb/src/main/AndroidManifest.xml create mode 100644 leveldb/src/main/java/com/litl/leveldb/Chunk.java create mode 100644 leveldb/src/main/java/com/litl/leveldb/DB.java create mode 100644 leveldb/src/main/java/com/litl/leveldb/DatabaseCorruptException.java create mode 100644 leveldb/src/main/java/com/litl/leveldb/Iterator.java create mode 100644 leveldb/src/main/java/com/litl/leveldb/LevelDBException.java create mode 100644 leveldb/src/main/java/com/litl/leveldb/NativeObject.java create mode 100644 leveldb/src/main/java/com/litl/leveldb/NotFoundException.java create mode 100644 leveldb/src/main/java/com/litl/leveldb/WriteBatch.java create mode 100644 leveldb/src/main/jni/Android.mk create mode 100644 leveldb/src/main/jni/Application.mk create mode 100644 leveldb/src/main/jni/Chunk.cc create mode 100644 leveldb/src/main/jni/Chunk.h create mode 100644 leveldb/src/main/jni/blocknames.cc create mode 100644 leveldb/src/main/jni/blocknames.h create mode 100644 leveldb/src/main/jni/com_litl_leveldb_Chunk.cc create mode 100644 leveldb/src/main/jni/com_litl_leveldb_DB.cc create mode 100644 leveldb/src/main/jni/com_litl_leveldb_Iterator.cc create mode 100644 leveldb/src/main/jni/com_litl_leveldb_WriteBatch.cc create mode 100644 leveldb/src/main/jni/debug_conf.h create mode 160000 leveldb/src/main/jni/leveldb-mcpe create mode 100644 leveldb/src/main/jni/leveldbjni.cc create mode 100644 leveldb/src/main/jni/leveldbjni.h create mode 100644 leveldb/src/main/jni/mapkey.h create mode 100644 leveldb/src/main/jni/qstr.h create mode 100644 leveldb/src/main/jni/subchunk.cc create mode 100644 leveldb/src/main/jni/subchunk.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9f6d02b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "leveldb/src/main/jni/leveldb-mcpe"] + path = leveldb/src/main/jni/leveldb-mcpe + url = https://github.com/Mojang/leveldb-mcpe.git diff --git a/app/build.gradle b/app/build.gradle index 68f00b04..a0745857 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 28 defaultConfig { - applicationId 'com.sirkut.blocktopograph' + applicationId 'com.rbq2012.blocktopograph' minSdkVersion 16 targetSdkVersion 28 versionCode 10 @@ -21,14 +21,15 @@ android { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } + } } } dependencies { - implementation fileTree(include: ['*.jar', '*.so','*.aar'], dir: 'libs') + implementation fileTree(include: ['*.jar', '*.so', '*.aar'], dir: 'libs') testImplementation 'junit:junit:4.12' + implementation project(':leveldb') implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.github.clans:fab:1.6.4' @@ -38,5 +39,4 @@ dependencies { implementation 'com.google.firebase:firebase-core:16.0.6' } - //apply plugin: 'com.google.gms.google-services' diff --git a/app/libs/android-leveldb.aar b/app/libs/android-leveldb.aar deleted file mode 100644 index 0d736020c863c4e1249da5f1e5e00b5bb276abdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 200457 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 #}hOv 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 + + + + \ No newline at end of file diff --git a/leveldb/src/main/java/com/litl/leveldb/Chunk.java b/leveldb/src/main/java/com/litl/leveldb/Chunk.java new file mode 100644 index 00000000..ca665a95 --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/Chunk.java @@ -0,0 +1,35 @@ +package com.litl.leveldb; + +import java.io.File; +import java.nio.ByteBuffer; + +public class Chunk extends NativeObject { + + public Chunk(DB db, int x, int z, int dim) { + super(); + mPtr = nativeOpen(db.getPtr(), x, z, dim); + } + + public int getBlock(int x, int y, int z) { + return nativeGetBlock(mPtr, x, y, z); + } + + public void close() { + closeNativeObject(mPtr); + } + + @Override + protected void closeNativeObject(long ptr) { + nativeClose(ptr); + } + + private static native long nativeOpen(long dbptr, int x, int z, int dim); + + private static native int nativeGetBlock(long ckptr, int x, int y, int z); + + private static native void nativeClose(long ckPtr); + + { + System.loadLibrary("leveldbjni"); + } +} diff --git a/leveldb/src/main/java/com/litl/leveldb/DB.java b/leveldb/src/main/java/com/litl/leveldb/DB.java new file mode 100644 index 00000000..cde3cece --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/DB.java @@ -0,0 +1,169 @@ +package com.litl.leveldb; + +import java.io.File; +import java.nio.ByteBuffer; + +public class DB extends NativeObject { + public abstract static class Snapshot extends NativeObject { + Snapshot(long ptr) { + super(ptr); + } + } + + private final File mPath; + private boolean mDestroyOnClose = false; + + public DB(File path) { + super(); + + if (path == null) { + throw new NullPointerException(); + } + mPath = path; + } + + public void open() { + mPtr = nativeOpen(mPath.getAbsolutePath()); + } + + @Override + protected void closeNativeObject(long ptr) { + nativeClose(ptr); + + if (mDestroyOnClose) { + destroy(mPath); + } + } + + public void put(byte[] key, byte[] value) { + assertOpen("Database is closed"); + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + nativePut(mPtr, key, value); + } + + public byte[] get(byte[] key) { + return get(null, key); + } + + public byte[] get(Snapshot snapshot, byte[] key) { + assertOpen("Database is closed"); + if (key == null) { + throw new NullPointerException(); + } + + return nativeGet(mPtr, snapshot != null ? snapshot.getPtr() : 0, key); + } + + public byte[] get(ByteBuffer key) { + return get(null, key); + } + + public byte[] get(Snapshot snapshot, ByteBuffer key) { + assertOpen("Database is closed"); + if (key == null) { + throw new NullPointerException(); + } + + return nativeGet(mPtr, snapshot != null ? snapshot.getPtr() : 0, key); + } + + public void delete(byte[] key) { + assertOpen("Database is closed"); + if (key == null) { + throw new NullPointerException(); + } + + nativeDelete(mPtr, key); + } + + public void write(WriteBatch batch) { + assertOpen("Database is closed"); + if (batch == null) { + throw new NullPointerException(); + } + + nativeWrite(mPtr, batch.getPtr()); + } + + public Iterator iterator() { + return iterator(null); + } + + public Iterator iterator(final Snapshot snapshot) { + assertOpen("Database is closed"); + + ref(); + + if (snapshot != null) { + snapshot.ref(); + } + + return new Iterator(nativeIterator(mPtr, snapshot != null ? snapshot.getPtr() : 0)) { + @Override + protected void closeNativeObject(long ptr) { + super.closeNativeObject(ptr); + if (snapshot != null) { + snapshot.unref(); + } + + DB.this.unref(); + } + }; + } + + public Snapshot getSnapshot() { + assertOpen("Database is closed"); + ref(); + return new Snapshot(nativeGetSnapshot(mPtr)) { + protected void closeNativeObject(long ptr) { + nativeReleaseSnapshot(DB.this.getPtr(), getPtr()); + DB.this.unref(); + } + }; + } + + public void destroy() { + mDestroyOnClose = true; + if (getPtr() == 0) { + destroy(mPath); + } + } + + public static void destroy(File path) { + nativeDestroy(path.getAbsolutePath()); + } + + private static native long nativeOpen(String dbpath); + + private static native void nativeClose(long dbPtr); + + private static native void nativePut(long dbPtr, byte[] key, byte[] value); + + private static native byte[] nativeGet(long dbPtr, long snapshotPtr, byte[] key); + + private static native byte[] nativeGet(long dbPtr, long snapshotPtr, ByteBuffer key); + + private static native void nativeDelete(long dbPtr, byte[] key); + + private static native void nativeWrite(long dbPtr, long batchPtr); + + private static native void nativeDestroy(String dbpath); + + private static native long nativeIterator(long dbPtr, long snapshotPtr); + + private static native long nativeGetSnapshot(long dbPtr); + + private static native void nativeReleaseSnapshot(long dbPtr, long snapshotPtr); + + public static native String stringFromJNI(); + + { + System.loadLibrary("leveldbjni"); + } +} diff --git a/leveldb/src/main/java/com/litl/leveldb/DatabaseCorruptException.java b/leveldb/src/main/java/com/litl/leveldb/DatabaseCorruptException.java new file mode 100644 index 00000000..4a1c83dc --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/DatabaseCorruptException.java @@ -0,0 +1,12 @@ +package com.litl.leveldb; + +public class DatabaseCorruptException extends LevelDBException { + private static final long serialVersionUID = -2110293580518875321L; + + public DatabaseCorruptException() { + } + + public DatabaseCorruptException(String error) { + super(error); + } +} diff --git a/leveldb/src/main/java/com/litl/leveldb/Iterator.java b/leveldb/src/main/java/com/litl/leveldb/Iterator.java new file mode 100644 index 00000000..b02d6b48 --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/Iterator.java @@ -0,0 +1,73 @@ +package com.litl.leveldb; + +public class Iterator extends NativeObject { + Iterator(long iterPtr) { + super(iterPtr); + } + + @Override + protected void closeNativeObject(long ptr) { + nativeDestroy(ptr); + } + + public void seekToFirst() { + assertOpen("Iterator is closed"); + nativeSeekToFirst(mPtr); + } + + public void seekToLast() { + assertOpen("Iterator is closed"); + nativeSeekToLast(mPtr); + } + + public void seek(byte[] target) { + assertOpen("Iterator is closed"); + if (target == null) { + throw new IllegalArgumentException(); + } + nativeSeek(mPtr, target); + } + + public boolean isValid() { + assertOpen("Iterator is closed"); + return nativeValid(mPtr); + } + + public void next() { + assertOpen("Iterator is closed"); + nativeNext(mPtr); + } + + public void prev() { + assertOpen("Iterator is closed"); + nativePrev(mPtr); + } + + public byte[] getKey() { + assertOpen("Iterator is closed"); + return nativeKey(mPtr); + } + + public byte[] getValue() { + assertOpen("Iterator is closed"); + return nativeValue(mPtr); + } + + private static native void nativeDestroy(long ptr); + + private static native void nativeSeekToFirst(long ptr); + + private static native void nativeSeekToLast(long ptr); + + private static native void nativeSeek(long ptr, byte[] key); + + private static native boolean nativeValid(long ptr); + + private static native void nativeNext(long ptr); + + private static native void nativePrev(long ptr); + + private static native byte[] nativeKey(long dbPtr); + + private static native byte[] nativeValue(long dbPtr); +} diff --git a/leveldb/src/main/java/com/litl/leveldb/LevelDBException.java b/leveldb/src/main/java/com/litl/leveldb/LevelDBException.java new file mode 100644 index 00000000..d576a253 --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/LevelDBException.java @@ -0,0 +1,16 @@ +package com.litl.leveldb; + +public class LevelDBException extends RuntimeException { + private static final long serialVersionUID = 2903013251786326801L; + + public LevelDBException() { + } + + public LevelDBException(String error) { + super(error); + } + + public LevelDBException(String error, Throwable cause) { + super(error, cause); + } +} diff --git a/leveldb/src/main/java/com/litl/leveldb/NativeObject.java b/leveldb/src/main/java/com/litl/leveldb/NativeObject.java new file mode 100644 index 00000000..1e3cd6bd --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/NativeObject.java @@ -0,0 +1,86 @@ +package com.litl.leveldb; + +import java.io.Closeable; + +import android.text.TextUtils; +import android.util.Log; + +abstract class NativeObject implements Closeable { + private static final String TAG = NativeObject.class.getSimpleName(); + protected long mPtr; + private int mRefCount = 0; + + protected NativeObject() { + // The Java wrapper counts as one reference, will + // be released when closed + ref(); + } + + protected NativeObject(long ptr) { + this(); + + if (ptr == 0) { + throw new OutOfMemoryError("Failed to allocate native object"); + } + + mPtr = ptr; + } + + synchronized protected long getPtr() { + return mPtr; + } + + synchronized public boolean isClosed(){ + return getPtr() == 0; + } + + protected void assertOpen(String message) { + if (getPtr() == 0) { + throw new IllegalStateException(message); + } + } + + synchronized void ref() { + mRefCount++; + } + + synchronized void unref() { + if (mRefCount <= 0) { + throw new IllegalStateException("Reference count is already 0"); + } + + mRefCount--; + + if (mRefCount == 0) { + closeNativeObject(mPtr); + mPtr = 0; + } + } + + protected abstract void closeNativeObject(long ptr); + + @Override + public synchronized void close() { + if (mPtr != 0) { + unref(); + } + } + + @Override + protected void finalize() throws Throwable { + if (mPtr != 0) { + Class clazz = getClass(); + String name = clazz.getSimpleName(); + while (TextUtils.isEmpty(name)) { + clazz = clazz.getSuperclass(); + name = clazz.getSimpleName(); + } + + Log.w(TAG, "NativeObject " + name + " refcount: " + mRefCount + + " id: " + System.identityHashCode(this) + + " was finalized before native resource was closed, did you forget to call close()?"); + } + + super.finalize(); + } +} diff --git a/leveldb/src/main/java/com/litl/leveldb/NotFoundException.java b/leveldb/src/main/java/com/litl/leveldb/NotFoundException.java new file mode 100644 index 00000000..ccd20e0b --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/NotFoundException.java @@ -0,0 +1,12 @@ +package com.litl.leveldb; + +public class NotFoundException extends LevelDBException { + private static final long serialVersionUID = 6207999645579440001L; + + public NotFoundException() { + } + + public NotFoundException(String error) { + super(error); + } +} diff --git a/leveldb/src/main/java/com/litl/leveldb/WriteBatch.java b/leveldb/src/main/java/com/litl/leveldb/WriteBatch.java new file mode 100644 index 00000000..674ca7d6 --- /dev/null +++ b/leveldb/src/main/java/com/litl/leveldb/WriteBatch.java @@ -0,0 +1,50 @@ +package com.litl.leveldb; + +import java.nio.ByteBuffer; + +public class WriteBatch extends NativeObject { + public WriteBatch() { + super(nativeCreate()); + } + + @Override + protected void closeNativeObject(long ptr) { + nativeDestroy(ptr); + } + + public void delete(ByteBuffer key) { + assertOpen("WriteBatch is closed"); + if (key == null) { + throw new NullPointerException("key"); + } + + nativeDelete(mPtr, key); + } + + public void put(ByteBuffer key, ByteBuffer value) { + assertOpen("WriteBatch is closed"); + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + nativePut(mPtr, key, value); + } + + public void clear() { + assertOpen("WriteBatch is closed"); + nativeClear(mPtr); + } + + private static native long nativeCreate(); + + private static native void nativeDestroy(long ptr); + + private static native void nativeDelete(long ptr, ByteBuffer key); + + private static native void nativePut(long ptr, ByteBuffer key, ByteBuffer val); + + private static native void nativeClear(long ptr); +} diff --git a/leveldb/src/main/jni/Android.mk b/leveldb/src/main/jni/Android.mk new file mode 100644 index 00000000..37bf2171 --- /dev/null +++ b/leveldb/src/main/jni/Android.mk @@ -0,0 +1,68 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := leveldbjni + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/leveldb-mcpe\ + $(LOCAL_PATH)/leveldb-mcpe/include\ + $(LOCAL_PATH)/leveldb-mcpe/include/leveldb + +LOCAL_CPP_EXTENSION := .cc + +LOCAL_CFLAGS := -DLEVELDB_PLATFORM_ANDROID -std=gnu++11 -DDLLX= + +LOCAL_SRC_FILES := com_litl_leveldb_DB.cc\ + com_litl_leveldb_Iterator.cc\ + com_litl_leveldb_WriteBatch.cc\ + leveldbjni.cc\ + com_litl_leveldb_Chunk.cc\ + subchunk.cc Chunk.cc\ + blocknames.cc + +LOCAL_STATIC_LIBRARIES += leveldb-mcpe + +LOCAL_LDLIBS += -llog -ldl -lz + +include $(BUILD_SHARED_LIBRARY) + +#################################################################################################### + +include $(CLEAR_VARS) + +LOCAL_MODULE := leveldb-mcpe + +LOCAL_CFLAGS := -O3 -D_REENTRANT -DOS_ANDROID -DLEVELDB_PLATFORM_POSIX\ + -DNDEBUG -std=gnu++11 -DDLLX= + +LOCAL_CPP_EXTENSION := .cc + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/leveldb-mcpe/include\ + $(LOCAL_PATH)/leveldb-mcpe/include/leveldb\ + $(LOCAL_PATH)/leveldb-mcpe + +LOCAL_SRC_FILES := \ + leveldb-mcpe/db/builder.cc leveldb-mcpe/db/c.cc\ + leveldb-mcpe/db/dbformat.cc leveldb-mcpe/db/db_impl.cc\ + leveldb-mcpe/db/db_iter.cc leveldb-mcpe/db/dumpfile.cc\ + leveldb-mcpe/db/filename.cc leveldb-mcpe/db/log_reader.cc\ + leveldb-mcpe/db/log_writer.cc leveldb-mcpe/db/memtable.cc\ + leveldb-mcpe/db/repair.cc leveldb-mcpe/db/table_cache.cc\ + leveldb-mcpe/db/version_edit.cc leveldb-mcpe/db/version_set.cc\ + leveldb-mcpe/db/write_batch.cc leveldb-mcpe/db/zlib_compressor.cc\ + leveldb-mcpe/table/block_builder.cc leveldb-mcpe/table/block.cc\ + leveldb-mcpe/table/filter_block.cc leveldb-mcpe/table/format.cc\ + leveldb-mcpe/table/iterator.cc leveldb-mcpe/table/merger.cc\ + leveldb-mcpe/table/table_builder.cc leveldb-mcpe/table/table.cc\ + leveldb-mcpe/table/two_level_iterator.cc leveldb-mcpe/util/arena.cc\ + leveldb-mcpe/util/bloom.cc leveldb-mcpe/util/cache.cc\ + leveldb-mcpe/util/coding.cc leveldb-mcpe/util/comparator.cc\ + leveldb-mcpe/util/crc32c.cc leveldb-mcpe/util/env_boost.cc\ + leveldb-mcpe/util/env.cc leveldb-mcpe/util/env_posix.cc\ + leveldb-mcpe/util/env_win.cc leveldb-mcpe/util/filter_policy.cc\ + leveldb-mcpe/util/hash.cc leveldb-mcpe/util/histogram.cc\ + leveldb-mcpe/util/logging.cc leveldb-mcpe/util/options.cc\ + leveldb-mcpe/util/status.cc leveldb-mcpe/util/win_logger.cc\ + leveldb-mcpe/port/port_posix.cc leveldb-mcpe/port/port_posix_sse.cc + +include $(BUILD_STATIC_LIBRARY) diff --git a/leveldb/src/main/jni/Application.mk b/leveldb/src/main/jni/Application.mk new file mode 100644 index 00000000..8d557f97 --- /dev/null +++ b/leveldb/src/main/jni/Application.mk @@ -0,0 +1,2 @@ +APP_PLATFORM=android-16 +APP_STL := c++_static diff --git a/leveldb/src/main/jni/Chunk.cc b/leveldb/src/main/jni/Chunk.cc new file mode 100644 index 00000000..304c9d83 --- /dev/null +++ b/leveldb/src/main/jni/Chunk.cc @@ -0,0 +1,88 @@ +// +// Created by barco on 2018/3/23. +// + + +#include +#include "Chunk.h" +#include "mapkey.h" +#include "qstr.h" +#include +#include +#include + +#ifdef LOG_CHUNK_LOADSAVE +#define LOGE_LS(x, ...) LOGE(CAT("Chunk: ", x), ##__VA_ARGS__); +#else +#define LOGE_LS(x, ...) +#endif + +#ifdef LOG_CHUNK_OPERATION +#define LOGE_OP(x, ...) LOGE(CAT("Chunk: ", x), ##__VA_ARGS__); +#else +#define LOGE_OP(x, ...) +#endif + +//Init constants. + +const int32_t Chunk::msk[] = {0b1, 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, + 0b11111111, + 0b111111111, 0b1111111111, 0b11111111111, + 0b111111111111, + 0b1111111111111, 0b11111111111111, 0b11111111111111}; + +const char Chunk::pattern_name[] = {0x0a, 0x00, 0x00, 0x08, 0x04, 0x00, 'n', 'a', 'm', + 'e'}; + +const char Chunk::pattern_val[] = {0x02, 0x03, 0x00, 'v', 'a', 'l'}; + +Chunk::Chunk(leveldb::DB *db, mapkey_t key) + : key(key) { + std::string str; + memset(subchunks, 0, sizeof(subchunks)); + for (int i = 0; i < 16; i++) { + loadSubChunk(db, i); + } +} + +void Chunk::loadSubChunk(leveldb::DB *db, unsigned char which) { + LDBKEY_SUBCHUNK(this->key, which) + leveldb::Slice slice(key, 0 == this->key.dimension ? 10 : 14); + std::string val; + leveldb::ReadOptions readOptions; + readOptions.decompress_allocator = new leveldb::DecompressAllocator; + bool hit = db->Get(readOptions, slice, &val).ok(); + if (hit) {//Found, detect version. + switch (val[0]) { + case 0://Aligned subchunk. + case 2://All + case 3://The + case 4://Same + case 5://Really + case 6://Wierd + case 7://Ahhh + break; + case 8://Current paletted format + subchunks[which] = new SubChunk(val, true); + break; + case 1: //Previous paletted version + subchunks[which] = new SubChunk(val, false); + break; + default: + //Unsupported format. + break; + } + } +} + +uint16_t Chunk::getBlock(unsigned char x, unsigned char y, unsigned char z) { + unsigned char sub = y >> 4; + if (subchunks[sub] == nullptr)return 0; + return subchunks[sub]->getBlock(x, static_cast(y & 0xf), z); +} + +Chunk::~Chunk() { + for (int i = 0; i < 16; i++) { + delete subchunks[i]; + } +} diff --git a/leveldb/src/main/jni/Chunk.h b/leveldb/src/main/jni/Chunk.h new file mode 100644 index 00000000..78b711e2 --- /dev/null +++ b/leveldb/src/main/jni/Chunk.h @@ -0,0 +1,44 @@ +// +// Created by barco on 2018/3/23. +// + +#ifndef CONVARTER_CHUNK_H +#define CONVARTER_CHUNK_H + +#include +#include +#include +#include +#include "mapkey.h" +#include "debug_conf.h" +#include "qstr.h" + +class Chunk { +private: + + //Constants. + + static const int32_t msk[]; + + static const char pattern_name[]; + + static const char pattern_val[]; + + //Member vars. + + mapkey_t key; + + SubChunk *subchunks[16]; + + void loadSubChunk(leveldb::DB *db, unsigned char which); + +public: + + Chunk(leveldb::DB *db, mapkey_t key); + + ~Chunk(); + + uint16_t getBlock(unsigned char x, unsigned char y, unsigned char z); +}; + +#endif //CONVARTER_CHUNK_H diff --git a/leveldb/src/main/jni/blocknames.cc b/leveldb/src/main/jni/blocknames.cc new file mode 100644 index 00000000..a6e27e7e --- /dev/null +++ b/leveldb/src/main/jni/blocknames.cc @@ -0,0 +1,277 @@ +// +// Created by barco on 2018/3/30. +// + +#include +#include +#include "blocknames.h" + +char BlockNames::names[256][32] = {"air", + "stone", + "grass", + "dirt", + "cobblestone", + "planks", + "sapling", + "bedrock", + "flowing_water", + "water", + "flowing_lava", + "lava", + "sand", + "gravel", + "gold_ore", + "iron_ore", + "coal_ore", + "log", + "leaves", + "sponge", + "glass", + "lapis_ore", + "lapis_block", + "dispenser", + "sandstone", + "noteblock", + "bed", + "golden_rail", + "detector_rail", + "sticky_piston", + "web", + "tallgrass", + "deadbush", + "piston", + "pistonarmcollision", + "wool", + "air", + "yellow_flower", + "red_flower", + "brown_mushroom", + "red_mushroom", + "gold_block", + "iron_block", + "double_stone_slab", + "stone_slab", + "brick_block", + "tnt", + "bookshelf", + "mossy_cobblestone", + "obsidian", + "torch", + "fire", + "mob_spawner", + "oak_stairs", + "chest", + "redstone_wire", + "diamond_ore", + "diamond_block", + "crafting_table", + "wheat", + "farmland", + "furnace", + "lit_furnace", + "standing_sign", + "wooden_door", + "ladder", + "rail", + "stone_stairs", + "wall_sign", + "lever", + "stone_pressure_plate", + "iron_door", + "oak_pressure_plate", + "redstone_ore", + "lit_redstone_ore", + "unlit_redstone_torch", + "lit_redstone_torch", + "stone_button", + "snow_layer", + "ice", + "snow", + "cactus", + "clay", + "reeds", + "jukebox", + "fence", + "pumpkin", + "netherrack", + "soul_sand", + "glowstone", + "portal", + "lit_pumpkin", + "cake", + "unpowered_repeater", + "powered_repeater", + "invisiblebedrock", + "oak_trapdoor", + "monster_egg", + "stonebrick", + "brown_mushroom_block", + "red_mushroom_block", + "iron_bars", + "glass_pane", + "melon_block", + "pumpkin_stem", + "melon_stem", + "vine", + "fence_gate", + "brick_stairs", + "stone_brick_stairs", + "mycelium", + "waterlily", + "nether_brick", + "nether_brick_fence", + "nether_brick_stairs", + "nether_wart", + "enchanting_table", + "brewing_stand", + "cauldron", + "end_portal", + "end_portal_frame", + "end_stone", + "dragon_egg", + "redstone_lamp", + "lit_redstone_lamp", + "dropper", + "activator_rail", + "cocoa", + "sandstone_stairs", + "emerald_ore", + "ender_chest", + "tripwire_hook", + "tripwire", + "emerald_block", + "spruce_stairs", + "birch_stairs", + "jungle_stairs", + "command_block", + "beacon", + "cobblestone_wall", + "flower_pot", + "carrots", + "potatoes", + "oak_button", + "skull", + "anvil", + "trapped_chest", + "light_weighted_pressure_plate", + "heavy_weighted_pressure_plate", + "unpowered_comparator", + "powered_comparator", + "daylight_detector", + "redstone_block", + "quartz_ore", + "hopper", + "quartz_block", + "quartz_stairs", + "double_wooden_slab", + "wooden_slab", + "stained_hardened_clay", + "stained_glass_pane", + "leaves2", + "log2", + "acacia_stairs", + "dark_oak_stairs", + "slime", + "air", + "iron_trapdoor", + "prismarine", + "sealantern", + "hay_block", + "carpet", + "terracotta", + "coal_block", + "packed_ice", + "double_plant", + "standing_banner", + "wall_banner", + "daylight_detector_inverted", + "red_sandstone", + "red_sandstone_stairs", + "double_stone_slab2", + "stone_slab2", + "spruce_fence_gate", + "birch_fence_gate", + "jungle_fence_gate", + "dark_oak_fence_gate", + "acacia_fence_gate", + "repeating_command_block", + "chain_command_block", + "air", + "air", + "air", + "spruce_door", + "birch_door", + "jungle_door", + "acacia_door", + "dark_oak_door", + "grass_path", + "frame", + "chorus_flower", + "purpur_block", + "purpur_stairs", + "air", + "air", + "undyed_shulker_box", + "end_bricks", + "frosted_ice", + "end_rod", + "end_gateway", + "air", + "air", + "air", + "magma", + "nether_wart_block", + "red_nether_brick", + "bone_block", + "air", + "shulker_box", + "purple_glazed_terracotta", + "white_glazed_terracotta", + "orange_glazed_terracotta", + "magenta_glazed_terracotta", + "light_blue_glazed_terracotta", + "yellow_glazed_terracotta", + "lime_glazed_terracotta", + "pink_glazed_terracotta", + "gray_glazed_terracotta", + "silver_glazed_terracotta", + "cyan_glazed_terracotta", + "air", + "blue_glazed_terracotta", + "brown_glazed_terracotta", + "green_glazed_terracotta", + "red_glazed_terracotta", + "black_glazed_terracotta", + "concrete", + "concretepowder", + "air", + "air", + "chorus_plant", + "stained_glass", + "air", + "podzol", + "beetroot", + "stonecutter", + "glowingobsidian", + "netherreactor", + "info_update", + "info_update2", + "movingblock", + "observer", + "structure_block", + "air", + "air", + "reserved6"}; + +unsigned char BlockNames::resolve(qstr name) { + if (name.str[9] == ':') { + name.str += 10; + name.length -= 10; + } + for (int i = 0; i < 256; i++) { + char *nam = names[i]; + if (memcmp(nam, name.str, name.length) != 0)continue; + return static_cast(i); + } + return 0; +} diff --git a/leveldb/src/main/jni/blocknames.h b/leveldb/src/main/jni/blocknames.h new file mode 100644 index 00000000..663e61a8 --- /dev/null +++ b/leveldb/src/main/jni/blocknames.h @@ -0,0 +1,17 @@ +// +// Created by barco on 2018/3/29. +// + +#ifndef CONVARTER_BLOCKNAMES_H +#define CONVARTER_BLOCKNAMES_H + +#include "qstr.h" + +class BlockNames { +public: + static char names[256][32]; + + static unsigned char resolve(qstr name); +}; + +#endif //CONVARTER_BLOCKNAMES_H diff --git a/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc b/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc new file mode 100644 index 00000000..3197044b --- /dev/null +++ b/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc @@ -0,0 +1,45 @@ +#include +#include + +#include +#include +#include +#include + +#include "leveldbjni.h" +#include "Chunk.h" + +static jlong nativeOpen(JNIEnv *env, jclass clazz, jlong dbptr, jint x, jint z, jint dim) { + leveldb::DB *db = reinterpret_cast(dbptr); + Chunk *chunk = new Chunk(db, LDBKEY_STRUCT(x, z, dim)); + return reinterpret_cast(chunk); +} + +static jint nativeGetBlock(JNIEnv *env, jclass clazz, jlong ckptr, jint x, jint y, jint z) { + Chunk *chunk = reinterpret_cast(ckptr); + return chunk->getBlock(static_cast(x), static_cast(y), + static_cast(z)); +} + +static void nativeClose(JNIEnv *env, jclass clazz, jlong ckptr) { + Chunk *chunk = reinterpret_cast(ckptr); + delete chunk; +} + +static JNINativeMethod sMethods[] = + { + {"nativeOpen", "(JIII)J", (void *) nativeOpen}, + {"nativeGetBlock", "(JIII)I", (void *) nativeGetBlock}, + {"nativeClose", "(J)V", (void *) nativeClose} + }; + +int +register_com_litl_leveldb_Chunk(JNIEnv *env) { + jclass clazz = env->FindClass("com/litl/leveldb/Chunk"); + if (!clazz) { + LOGE("Can't find class com.litl.leveldb.Chunk"); + return 0; + } + + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); +} diff --git a/leveldb/src/main/jni/com_litl_leveldb_DB.cc b/leveldb/src/main/jni/com_litl_leveldb_DB.cc new file mode 100644 index 00000000..dfd319c4 --- /dev/null +++ b/leveldb/src/main/jni/com_litl_leveldb_DB.cc @@ -0,0 +1,318 @@ +#include +#include + +#include +#include +#include + +#include "leveldbjni.h" + +#include "leveldb/db.h" +#include "leveldb/write_batch.h" +#include "leveldb/zlib_compressor.h" +#include "leveldb/filter_policy.h" +#include "leveldb/cache.h" +#include "leveldb/options.h" +#include "leveldb/decompress_allocator.h" +#include "leveldb/env.h" + +static jmethodID gByteBuffer_isDirectMethodID; +static jmethodID gByteBuffer_positionMethodID; +static jmethodID gByteBuffer_limitMethodID; +static jmethodID gByteBuffer_arrayMethodID; + +static leveldb::ZlibCompressor* zlibCompressorInstance; + +class NullLogger : public leveldb::Logger { + public: + void Logv(const char*, va_list) override { + } + }; + +static jlong +nativeOpen(JNIEnv* env, + jclass clazz, + jstring dbpath) +{ + static bool gInited; + + if (!gInited) { + jclass byteBuffer_Clazz = env->FindClass("java/nio/ByteBuffer"); + gByteBuffer_isDirectMethodID = env->GetMethodID(byteBuffer_Clazz, + "isDirect", "()Z"); + gByteBuffer_positionMethodID = env->GetMethodID(byteBuffer_Clazz, + "position", "()I"); + gByteBuffer_limitMethodID = env->GetMethodID(byteBuffer_Clazz, + "limit", "()I"); + gByteBuffer_arrayMethodID = env->GetMethodID(byteBuffer_Clazz, + "array", "()[B"); + gInited = true; + } + + const char *path = env->GetStringUTFChars(dbpath, 0); + LOGI("Opening database %s", path); + + leveldb::DB* db; + leveldb::Options options; + options.create_if_missing = true; + options.paranoid_checks = true; + if (zlibCompressorInstance == NULL) { + zlibCompressorInstance = new leveldb::ZlibCompressor(); + } + + //create a bloom filter to quickly tell if a key is in the database or not + options.filter_policy = leveldb::NewBloomFilterPolicy(10); + + //create a 40 mb cache (we use this on ~1gb devices) + options.block_cache = leveldb::NewLRUCache(40 * 1024 * 1024); + + //create a 4mb write buffer, to improve compression and touch the disk less + options.write_buffer_size = 4 * 1024 * 1024; + + //disable internal logging. The default logger will still print out things to a file + options.info_log = new NullLogger(); + + //use the new raw-zip compressor to write (and read) + options.compressors[0] = new leveldb::ZlibCompressorRaw(-1); + + options.compressors[1] = zlibCompressorInstance; + leveldb::Status status = leveldb::DB::Open(options, path, &db); + env->ReleaseStringUTFChars(dbpath, path); + + if (!status.ok()) { + throwException(env, status); + } else { + LOGI("Opened database"); + } + + return reinterpret_cast(db); +} + +static void +nativeClose(JNIEnv* env, + jclass clazz, + jlong dbPtr) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + if (db) { + delete db; + } + + LOGI("Database closed"); +} + +static jbyteArray +nativeGet(JNIEnv * env, + jclass clazz, + jlong dbPtr, + jlong snapshotPtr, + jbyteArray keyObj) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + leveldb::ReadOptions options = leveldb::ReadOptions(); + options.decompress_allocator = new leveldb::DecompressAllocator(); + options.snapshot = reinterpret_cast(snapshotPtr); + + size_t keyLen = env->GetArrayLength(keyObj); + jbyte *buffer = env->GetByteArrayElements(keyObj, NULL); + jbyteArray result; + + leveldb::Slice key = leveldb::Slice((const char *)buffer, keyLen); + leveldb::Iterator* iter = db->NewIterator(options); + iter->Seek(key); + if (iter->Valid() && key == iter->key()) { + leveldb::Slice value = iter->value(); + size_t len = value.size(); + result = env->NewByteArray(len); + env->SetByteArrayRegion(result, 0, len, (const jbyte *) value.data()); + } else { + result = NULL; + } + + env->ReleaseByteArrayElements(keyObj, buffer, JNI_ABORT); + delete iter; + + return result; +} + +static jbyteArray +nativeGetBB(JNIEnv * env, + jclass clazz, + jlong dbPtr, + jlong snapshotPtr, + jobject keyObj) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + leveldb::ReadOptions options = leveldb::ReadOptions(); + options.snapshot = reinterpret_cast(snapshotPtr); + + jint keyPos = env->CallIntMethod(keyObj, gByteBuffer_positionMethodID); + jint keyLimit = env->CallIntMethod(keyObj, gByteBuffer_limitMethodID); + jboolean keyIsDirect = env->CallBooleanMethod(keyObj, gByteBuffer_isDirectMethodID); + jbyteArray keyArray; + void* key; + if (keyIsDirect) { + key = env->GetDirectBufferAddress(keyObj); + keyArray = NULL; + } else { + keyArray = (jbyteArray) env->CallObjectMethod(keyObj, gByteBuffer_arrayMethodID); + key = (void*) env->GetByteArrayElements(keyArray, NULL); + } + + jbyteArray result; + leveldb::Slice keySlice = leveldb::Slice((const char *) key + keyPos, keyLimit - keyPos); + leveldb::Iterator* iter = db->NewIterator(options); + iter->Seek(keySlice); + if (iter->Valid() && keySlice == iter->key()) { + leveldb::Slice value = iter->value(); + size_t len = value.size(); + result = env->NewByteArray(len); + env->SetByteArrayRegion(result, 0, len, (const jbyte *) value.data()); + } else { + result = NULL; + } + + if (keyArray) { + env->ReleaseByteArrayElements(keyArray, (jbyte*) key, JNI_ABORT); + } + + delete iter; + + return result; +} + +static void +nativePut(JNIEnv *env, + jclass clazz, + jlong dbPtr, + jbyteArray keyObj, + jbyteArray valObj) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + + size_t keyLen = env->GetArrayLength(keyObj); + jbyte *keyBuf = env->GetByteArrayElements(keyObj, NULL); + + size_t valLen = env->GetArrayLength(valObj); + jbyte *valBuf = env->GetByteArrayElements(valObj, NULL); + + leveldb::Status status = db->Put(leveldb::WriteOptions(), + leveldb::Slice((const char *) keyBuf, keyLen), + leveldb::Slice((const char *) valBuf, valLen)); + + env->ReleaseByteArrayElements(keyObj, keyBuf, JNI_ABORT); + env->ReleaseByteArrayElements(valObj, valBuf, JNI_ABORT); + + if (!status.ok()) { + throwException(env, status); + } +} + +static void +nativeDelete(JNIEnv *env, + jclass clazz, + jlong dbPtr, + jbyteArray keyObj) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + + size_t keyLen = env->GetArrayLength(keyObj); + jbyte *buffer = env->GetByteArrayElements(keyObj, NULL); + + leveldb::Status status = db->Delete(leveldb::WriteOptions(), leveldb::Slice((const char *) buffer, keyLen)); + env->ReleaseByteArrayElements(keyObj, buffer, JNI_ABORT); + + if (!status.ok()) { + throwException(env, status); + } +} + +static void +nativeWrite(JNIEnv *env, + jclass clazz, + jlong dbPtr, + jlong batchPtr) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + + leveldb::WriteBatch *batch = (leveldb::WriteBatch *) batchPtr; + leveldb::Status status = db->Write(leveldb::WriteOptions(), batch); + if (!status.ok()) { + throwException(env, status); + } +} + +static jlong +nativeIterator(JNIEnv* env, + jclass clazz, + jlong dbPtr, + jlong snapshotPtr) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + leveldb::ReadOptions options = leveldb::ReadOptions(); + options.snapshot = reinterpret_cast(snapshotPtr); + + leveldb::Iterator *iter = db->NewIterator(options); + return reinterpret_cast(iter); +} + +static jlong +nativeGetSnapshot(JNIEnv *env, + jclass clazz, + jlong dbPtr) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + const leveldb::Snapshot* snapshot = db->GetSnapshot(); + return reinterpret_cast(snapshot); +} + +static void +nativeReleaseSnapshot(JNIEnv *env, + jclass clazz, + jlong dbPtr, + jlong snapshotPtr) +{ + leveldb::DB* db = reinterpret_cast(dbPtr); + const leveldb::Snapshot *snapshot = reinterpret_cast(snapshotPtr); + db->ReleaseSnapshot(snapshot); +} + +static void +nativeDestroy(JNIEnv *env, + jclass clazz, + jstring dbpath) +{ + const char* path = env->GetStringUTFChars(dbpath,0); + leveldb::Options options; + options.create_if_missing = true; + leveldb::Status status = DestroyDB(path, options); + if (!status.ok()) { + throwException(env, status); + } +} + +static JNINativeMethod sMethods[] = +{ + { "nativeOpen", "(Ljava/lang/String;)J", (void*) nativeOpen }, + { "nativeClose", "(J)V", (void*) nativeClose }, + { "nativeGet", "(JJ[B)[B", (void*) nativeGet }, + { "nativeGet", "(JJLjava/nio/ByteBuffer;)[B", (void*) nativeGetBB }, + { "nativePut", "(J[B[B)V", (void*) nativePut }, + { "nativeDelete", "(J[B)V", (void*) nativeDelete }, + { "nativeWrite", "(JJ)V", (void*) nativeWrite }, + { "nativeIterator", "(JJ)J", (void*) nativeIterator }, + { "nativeGetSnapshot", "(J)J", (void*) nativeGetSnapshot }, + { "nativeReleaseSnapshot", "(JJ)V", (void*) nativeReleaseSnapshot }, + { "nativeDestroy", "(Ljava/lang/String;)V", (void*) nativeDestroy } +}; + +int +register_com_litl_leveldb_DB(JNIEnv *env) { + jclass clazz = env->FindClass("com/litl/leveldb/DB"); + if (!clazz) { + LOGE("Can't find class com.litl.leveldb.DB"); + return 0; + } + + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); +} diff --git a/leveldb/src/main/jni/com_litl_leveldb_Iterator.cc b/leveldb/src/main/jni/com_litl_leveldb_Iterator.cc new file mode 100644 index 00000000..fd39b38f --- /dev/null +++ b/leveldb/src/main/jni/com_litl_leveldb_Iterator.cc @@ -0,0 +1,128 @@ +#include +#include + +#include "leveldbjni.h" + +#include "leveldb/iterator.h" + +static void +nativeDestroy(JNIEnv* env, + jclass clazz, + jlong ptr) +{ + leveldb::Iterator* iter = reinterpret_cast(ptr); + + delete iter; +} + +static void +nativeSeekToFirst(JNIEnv* env, + jclass clazz, + jlong iterPtr) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + iter->SeekToFirst(); +} + +static void +nativeSeekToLast(JNIEnv* env, + jclass clazz, + jlong iterPtr) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + iter->SeekToLast(); +} + +static void +nativeSeek(JNIEnv* env, + jclass clazz, + jlong iterPtr, + jbyteArray keyObj) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + + size_t keyLen = env->GetArrayLength(keyObj); + jbyte *buffer = env->GetByteArrayElements(keyObj, NULL); + + iter->Seek(leveldb::Slice((const char *)buffer, keyLen)); + env->ReleaseByteArrayElements(keyObj, buffer, JNI_ABORT); +} + +static jboolean +nativeValid(JNIEnv* env, + jclass clazz, + jlong iterPtr) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + return iter->Valid(); +} + +static void +nativeNext(JNIEnv* env, + jclass clazz, + jlong iterPtr) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + iter->Next(); +} + +static void +nativePrev(JNIEnv* env, + jclass clazz, + jlong iterPtr) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + iter->Prev(); +} + +static jbyteArray +nativeKey(JNIEnv* env, + jclass clazz, + jlong iterPtr) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + leveldb::Slice key = iter->key(); + + size_t len = key.size(); + jbyteArray result = env->NewByteArray(len); + env->SetByteArrayRegion(result, 0, len, (const jbyte *) key.data()); + return result; +} + +static jbyteArray +nativeValue(JNIEnv* env, + jclass clazz, + jlong iterPtr) +{ + leveldb::Iterator* iter = reinterpret_cast(iterPtr); + leveldb::Slice value = iter->value(); + + size_t len = value.size(); + jbyteArray result = env->NewByteArray(len); + env->SetByteArrayRegion(result, 0, len, (const jbyte *) value.data()); + return result; +} + +static JNINativeMethod sMethods[] = +{ + { "nativeDestroy", "(J)V", (void*) nativeDestroy }, + { "nativeSeekToFirst", "(J)V", (void*) nativeSeekToFirst }, + { "nativeSeekToLast", "(J)V", (void*) nativeSeekToLast }, + { "nativeSeek", "(J[B)V", (void*) nativeSeek }, + { "nativeValid", "(J)Z", (void*) nativeValid }, + { "nativeNext", "(J)V", (void*) nativeNext }, + { "nativePrev", "(J)V", (void*) nativePrev }, + { "nativeKey", "(J)[B", (void*) nativeKey }, + { "nativeValue", "(J)[B", (void*) nativeValue } +}; + +int +register_com_litl_leveldb_Iterator(JNIEnv *env) { + jclass clazz = env->FindClass("com/litl/leveldb/Iterator"); + if (!clazz) { + LOGE("Can't find class com.litl.leveldb.Iterator"); + return 0; + } + + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); +} diff --git a/leveldb/src/main/jni/com_litl_leveldb_WriteBatch.cc b/leveldb/src/main/jni/com_litl_leveldb_WriteBatch.cc new file mode 100644 index 00000000..abd23dbe --- /dev/null +++ b/leveldb/src/main/jni/com_litl_leveldb_WriteBatch.cc @@ -0,0 +1,144 @@ +#include + +#include +#include +#include + +#include "leveldbjni.h" + +#include "leveldb/write_batch.h" + +static jmethodID gByteBuffer_isDirectMethodID; +static jmethodID gByteBuffer_positionMethodID; +static jmethodID gByteBuffer_limitMethodID; +static jmethodID gByteBuffer_arrayMethodID; + +static jlong +nativeCreate(JNIEnv* env, + jclass clazz) +{ + static bool gInited; + + if (!gInited) { + jclass byteBuffer_Clazz = env->FindClass("java/nio/ByteBuffer"); + gByteBuffer_isDirectMethodID = env->GetMethodID(byteBuffer_Clazz, + "isDirect", "()Z"); + gByteBuffer_positionMethodID = env->GetMethodID(byteBuffer_Clazz, + "position", "()I"); + gByteBuffer_limitMethodID = env->GetMethodID(byteBuffer_Clazz, + "limit", "()I"); + gByteBuffer_arrayMethodID = env->GetMethodID(byteBuffer_Clazz, + "array", "()[B"); + gInited = true; + } + + leveldb::WriteBatch* batch = new leveldb::WriteBatch(); + return reinterpret_cast(batch); +} + +static void +nativeDestroy(JNIEnv* env, + jclass clazz, + jlong ptr) +{ + leveldb::WriteBatch* batch = reinterpret_cast(ptr); + + delete batch; +} + +static void +nativeDelete(JNIEnv* env, + jclass clazz, + jlong ptr, + jobject buffer) +{ + leveldb::WriteBatch* batch = reinterpret_cast(ptr); + + jint pos = env->CallIntMethod(buffer, gByteBuffer_positionMethodID); + jint limit = env->CallIntMethod(buffer, gByteBuffer_limitMethodID); + jboolean isDirect = env->CallBooleanMethod(buffer, gByteBuffer_isDirectMethodID); + if (isDirect) { + const char *bytes = (const char *) env->GetDirectBufferAddress(buffer); + batch->Delete(leveldb::Slice(bytes + pos, limit - pos)); + } else { + jbyteArray array = (jbyteArray) env->CallObjectMethod(buffer, gByteBuffer_arrayMethodID); + jbyte *bytes = env->GetByteArrayElements(array, NULL); + batch->Delete(leveldb::Slice((const char *) bytes + pos, limit - pos)); + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + } +} + +static void +nativePut(JNIEnv* env, + jclass clazz, + jlong ptr, + jobject keyObj, + jobject valObj) +{ + leveldb::WriteBatch* batch = reinterpret_cast(ptr); + + jint keyPos = env->CallIntMethod(keyObj, gByteBuffer_positionMethodID); + jint keyLimit = env->CallIntMethod(keyObj, gByteBuffer_limitMethodID); + jboolean keyIsDirect = env->CallBooleanMethod(keyObj, gByteBuffer_isDirectMethodID); + jbyteArray keyArray; + void* key; + if (keyIsDirect) { + key = env->GetDirectBufferAddress(keyObj); + keyArray = NULL; + } else { + keyArray = (jbyteArray) env->CallObjectMethod(keyObj, gByteBuffer_arrayMethodID); + key = (void*) env->GetByteArrayElements(keyArray, NULL); + } + + jint valPos = env->CallIntMethod(valObj, gByteBuffer_positionMethodID); + jint valLimit = env->CallIntMethod(valObj, gByteBuffer_limitMethodID); + jboolean valIsDirect = env->CallBooleanMethod(valObj, gByteBuffer_isDirectMethodID); + jbyteArray valArray; + void* val; + if (valIsDirect) { + val = env->GetDirectBufferAddress(valObj); + valArray = NULL; + } else { + valArray = (jbyteArray) env->CallObjectMethod(valObj, gByteBuffer_arrayMethodID); + val = (void*) env->GetByteArrayElements(valArray, NULL); + } + + batch->Put(leveldb::Slice((const char *) key + keyPos, keyLimit - keyPos), + leveldb::Slice((const char *) val + valPos, valLimit - valPos)); + + if (keyArray) { + env->ReleaseByteArrayElements(keyArray, (jbyte*) key, JNI_ABORT); + } + if (valArray) { + env->ReleaseByteArrayElements(valArray, (jbyte*) val, JNI_ABORT); + } +} + +static void +nativeClear(JNIEnv* env, + jclass clazz, + jlong ptr) +{ + leveldb::WriteBatch* batch = reinterpret_cast(ptr); + batch->Clear(); +} + +static JNINativeMethod sMethods[] = +{ + { "nativeCreate", "()J", (void*) nativeCreate }, + { "nativeDestroy", "(J)V", (void*) nativeDestroy }, + { "nativeDelete", "(JLjava/nio/ByteBuffer;)V", (void*) nativeDelete }, + { "nativePut", "(JLjava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)V", (void*) nativePut }, + { "nativeClear", "(J)V", (void*) nativeClear } +}; + +int +register_com_litl_leveldb_WriteBatch(JNIEnv *env) { + jclass clazz = env->FindClass("com/litl/leveldb/WriteBatch"); + if (!clazz) { + LOGE("Can't find class com.litl.leveldb.WriteBatch"); + return 0; + } + + return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); +} diff --git a/leveldb/src/main/jni/debug_conf.h b/leveldb/src/main/jni/debug_conf.h new file mode 100644 index 00000000..a183c6ac --- /dev/null +++ b/leveldb/src/main/jni/debug_conf.h @@ -0,0 +1,34 @@ +// +// Created by barco on 2018/4/12. +// + +#ifndef CONVARTER_DEBUG_CONF_H +#define CONVARTER_DEBUG_CONF_H + +//Chunk +//#define LOG_CHUNK_OPERATION +#define LOG_CHUNK_LOADSAVE + +//World +//#define LOG_SAVDB_OPERATION +#define LOG_SAVDB_LOADSAVE +#define LOG_SAVDB_LRU + +#ifdef LOG_CHUNK_OPERATION +#ifndef LOG_CHUNK_LOADSAVE +#define LOG_CHUNK_LOADSAVE +#endif +#endif + +#ifdef LOG_SAVDB_OPERATION +#ifndef LOG_SAVDB_LOADSAVE +#define LOG_SAVDB_LOADSAVE +#endif +#ifndef LOG_SAVDB_LRU +#define LOG_SAVDB_LRU +#endif +#endif + +#define CAT(x, y) x y + +#endif //CONVARTER_DEBUG_CONF_H diff --git a/leveldb/src/main/jni/leveldb-mcpe b/leveldb/src/main/jni/leveldb-mcpe new file mode 160000 index 00000000..f9309ee7 --- /dev/null +++ b/leveldb/src/main/jni/leveldb-mcpe @@ -0,0 +1 @@ +Subproject commit f9309ee705e4fdd3895b7b616d9d4deadb109987 diff --git a/leveldb/src/main/jni/leveldbjni.cc b/leveldb/src/main/jni/leveldbjni.cc new file mode 100644 index 00000000..0fe69826 --- /dev/null +++ b/leveldb/src/main/jni/leveldbjni.cc @@ -0,0 +1,46 @@ +#include "leveldbjni.h" + +extern int register_com_litl_leveldb_DB(JNIEnv *env); + +extern int register_com_litl_leveldb_WriteBatch(JNIEnv *env); + +extern int register_com_litl_leveldb_Iterator(JNIEnv *env); + +extern int register_com_litl_leveldb_Chunk(JNIEnv *env); + +jint +throwException(JNIEnv *env, leveldb::Status status) { + const char *exceptionClass; + + if (status.IsNotFound()) { + exceptionClass = "com/litl/leveldb/NotFoundException"; + } else if (status.IsCorruption()) { + exceptionClass = "com/litl/leveldb/DatabaseCorruptException"; + } else if (status.IsIOError()) { + exceptionClass = "java/io/IOException"; + } else { + return 0; + } + + jclass clazz = env->FindClass(exceptionClass); + if (!clazz) { + LOGE("Can't find exception class %s", exceptionClass); + return -1; + } + + return env->ThrowNew(clazz, status.ToString().c_str()); +} + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + + register_com_litl_leveldb_DB(env); + register_com_litl_leveldb_WriteBatch(env); + register_com_litl_leveldb_Iterator(env); + register_com_litl_leveldb_Chunk(env); + + return JNI_VERSION_1_6; +} diff --git a/leveldb/src/main/jni/leveldbjni.h b/leveldb/src/main/jni/leveldbjni.h new file mode 100644 index 00000000..4e56fd5f --- /dev/null +++ b/leveldb/src/main/jni/leveldbjni.h @@ -0,0 +1,15 @@ +#ifndef LEVELDBJNI_H_ +#define LEVELDBJNI_H_ + +#include +#include +#include "leveldb/status.h" + +# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) +#define LOG_TAG "LevelDB" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + +jint throwException(JNIEnv* env, leveldb::Status status); + +#endif /* LEVELDBJNI_H_ */ diff --git a/leveldb/src/main/jni/mapkey.h b/leveldb/src/main/jni/mapkey.h new file mode 100644 index 00000000..5f05e60b --- /dev/null +++ b/leveldb/src/main/jni/mapkey.h @@ -0,0 +1,51 @@ +// +// Created by barco on 2018/3/27. +// + +#ifndef CONVARTER_MAPKEY_H +#define CONVARTER_MAPKEY_H + +#include + +typedef struct { + int32_t x_div16; + int32_t z_div16; + int32_t dimension; +} mapkey_t; + +#define LDBKEY_STRUCT(x, z, dim) mapkey_t{x >> 4, z >> 4, dim} + +#define LDBKEY_SUBCHUNK(k, ydiv) \ + char key[14];\ + if(true){\ + char* ptr = key;\ + *(int32_t*)ptr = k.x_div16;\ + ptr+=4;\ + *(int32_t*)ptr = k.z_div16;\ + ptr += 4;\ + if(k.dimension != 0){\ + *(int32_t*)ptr = k.dimension;\ + ptr += 4;\ + }\ + *ptr = 0x2f;\ + ptr++;\ + *ptr = ydiv;\ + } + +#define LDBKEY_VERSION(k) \ + char key_db[14];\ + if(true){\ + char* ptr = key_db;\ + *(int32_t*)ptr = k.x_div16;\ + ptr+=4;\ + *(int32_t*)ptr = k.z_div16;\ + ptr += 4;\ + if(k.dimension != 0){\ + *(int32_t*)ptr = k.dimension;\ + ptr += 4;\ + }\ + *ptr = 0x76;\ + ptr++;\ + } + +#endif //CONVARTER_MAPKEY_H diff --git a/leveldb/src/main/jni/qstr.h b/leveldb/src/main/jni/qstr.h new file mode 100644 index 00000000..5f436712 --- /dev/null +++ b/leveldb/src/main/jni/qstr.h @@ -0,0 +1,23 @@ +// +// Created by barco on 2018/3/24. +// + +#ifndef CONVARTER_QSTR_H +#define CONVARTER_QSTR_H + +typedef struct { + unsigned int length; + char *str; +} qstr; + +typedef struct { + unsigned int length; + const char *str; +} qcstr; + +typedef struct { + unsigned int length; + unsigned char *str; +} qustr; + +#endif //CONVARTER_QSTR_H diff --git a/leveldb/src/main/jni/subchunk.cc b/leveldb/src/main/jni/subchunk.cc new file mode 100644 index 00000000..9d92a464 --- /dev/null +++ b/leveldb/src/main/jni/subchunk.cc @@ -0,0 +1,163 @@ +// +// Created by barco on 2018/12/26. +// + +#include +#include +#include +#include +#include + +//////////////// +// + +char SubChunk::pattern_name[] = {0x0a, 0x00, 0x00, 0x08, 0x04, 0x00, 'n', 'a', 'm', + 'e'}; +char SubChunk::pattern_val[] = {0x02, 0x03, 0x00, 'v', 'a', 'l'}; + +const int32_t SubChunk::msk[] = {0b1, 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, + 0b11111111, + 0b111111111, 0b1111111111, 0b11111111111, + 0b111111111111, + 0b1111111111111, 0b11111111111111, 0b11111111111111}; + +SubChunk::SubChunk(const std::string &buf, bool hasMultiStorage) { + + storages[0].storage = nullptr; + storages[0].palette = nullptr; + + const char *cbuf = buf.c_str(); + const char *ptr = cbuf + 1; + if (hasMultiStorage) { + ptr++; + loadStorage(ptr, cbuf + buf.length(), 0); + } else { + loadStorage(ptr, cbuf + buf.length(), 0); + } +} + +SubChunk::~SubChunk() { + delete[] storages[0].storage; + delete[] storages[0].palette; +} + +const char *SubChunk::loadStorage(const char *ptr, const char *max, int which) { + +#ifdef DEBUG_SUBCHUNK + //lowest bit should be 0. + if (((*ptr) & 1) != 0 || max - ptr < 2) { + // + } +#endif + + //Length of each block, in bits. + storages[which].blen = static_cast(*ptr >> 1); + + ptr++; + + //How many uint32s do it need to store all blocks? + div_t res = div(4096, (32 / storages[which].blen)); + size_t bufsize = static_cast(res.quot); + if (res.rem != 0)bufsize++; + +#ifdef DEBUG_SUBCHUNK + if (max - ptr < (bufsize << 2)) { + // + } +#endif + + //Copy 'em up. bufsize is 4-bytes long and memcpy requires count of bytes so x4. + storages[which].storage = new uint32_t[bufsize]; + memcpy(storages[which].storage, ptr, bufsize << 2); + + //Move the pointer to the end of uint32s. + ptr += bufsize << 2; + + //Here records how many types of blocks are in this subchunk. + storages[which].types = *(uint16_t *) ptr; + ptr += 4; + + storages[which].palette = new uint16_t[storages[which].types]; + + for (uint16_t i = 0; i < storages[which].types; i++) { +#ifdef DEBUG_SUBCHUNK + if (max - ptr < sizeof(pattern_name)) { + // + } + if (memcmp(ptr, pattern_name, sizeof(pattern_name)) != 0) { + //Something has gone wrong. + } +#endif + ptr += sizeof(pattern_name); + qstr name; +#ifdef DEBUG_SUBCHUNK + if (max - ptr < 2) { + // + } +#endif + name.length = *(uint16_t *) ptr; + ptr += 2; +#ifdef DEBUG_SUBCHUNK + if (max - ptr < name.length) { + // + } +#endif + name.str = new char[name.length]; + memcpy(name.str, ptr, name.length); + ptr += name.length; +#ifdef DEBUG_SUBCHUNK + if (max - ptr < sizeof(pattern_val) + 3) { + // + } + if (memcmp(ptr, pattern_val, sizeof(pattern_val)) != 0) { + // + } +#endif + ptr += sizeof(pattern_val); + storages[which].palette[i] = BlockNames::resolve(name); + delete[] name.str; + storages[which].palette[i] <<= 8; + storages[which].palette[i] |= *ptr; + ptr += 3; + } + return ptr; +} + +uint16_t +SubChunk::getBlockCode(unsigned char x, unsigned char y, unsigned char z, uint8_t which) { + + //If there's only one storage than getBlockCode or other storages can just return 0. + if (which == 0 && storages[which].storage == nullptr)return 0; + + BlockStorage &thiz = storages[which]; + + //Get the index among all blocks. + int index = x; + index <<= 4; + index |= z; + index <<= 4; + index |= y; + + //How many blox can each stick hold. + int capa = (32 / thiz.blen); + + //Stick that hold this block. + uint32_t stick = *(thiz.storage + (index / capa)); + + //The bits for this block is index in palette. + //No need care about endian but not very efficiency, i guess? + uint32_t ind = (stick >> (index % capa * thiz.blen)) & msk[thiz.blen - 1]; + + //Return the record. + return thiz.palette[ind]; +} + +uint16_t SubChunk::getBlock(unsigned char x, unsigned char y, unsigned char z) { + return getBlockCode(x, y, z, 0); +} + +uint16_t SubChunk::getBlock3(unsigned char x, unsigned char y, unsigned char z, + unsigned char layer) { + if (layer != 0 && layer != 1)return 0; + return getBlockCode(x, y, z, layer); +} diff --git a/leveldb/src/main/jni/subchunk.h b/leveldb/src/main/jni/subchunk.h new file mode 100644 index 00000000..887a3e88 --- /dev/null +++ b/leveldb/src/main/jni/subchunk.h @@ -0,0 +1,48 @@ +// +// Created by barco on 2018/12/24. +// + +#ifndef CONVARTER_SUBCHUNK_H +#define CONVARTER_SUBCHUNK_H + +#include +#include +#include + +#define DEBUG_SUBCHUNK + +struct BlockStorage { + uint8_t blen;//Length per block + uint16_t types;//Types of blocks + uint16_t *palette;//Palette + uint32_t *storage;//Storage +}; + +class SubChunk { +private: + + static const int32_t msk[15]; + + static char pattern_name[10]; + + static char pattern_val[6]; + + BlockStorage storages[1]; + + const char *loadStorage(const char *ptr, const char *max, int which); + + uint16_t getBlockCode(unsigned char x, unsigned char y, unsigned char z, uint8_t which); + +public: + SubChunk(const std::string &buf, bool hasMultiStorage); + + ~SubChunk(); + + uint16_t getBlock(unsigned char x, unsigned char y, unsigned char z); + + uint16_t + getBlock3(unsigned char x, unsigned char y, unsigned char z, unsigned char layer); + +}; + +#endif //CONVARTER_SUBCHUNK_H diff --git a/settings.gradle b/settings.gradle index e7b4def4..228ed22a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ include ':app' +include ':leveldb' From 248282700deb41f873705d41637cf3f0f37c814a Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Thu, 17 Jan 2019 10:00:19 +0800 Subject: [PATCH 07/83] * Embedded tileview --- app/build.gradle | 1 + app/libs/tileview.aar | Bin 77325 -> 0 bytes .../blocktopograph/map/MapFragment.java | 2 +- settings.gradle | 1 + tileview/.gitignore | 1 + tileview/aar-release.gradle | 55 + tileview/build.gradle | 28 + tileview/proguard-rules.pro | 20 + .../com/qozix/tileview/ApplicationTest.java | 13 + tileview/src/main/AndroidManifest.xml | 6 + .../java/com/qozix/tileview/TileView.java | 1000 +++++++++++++++++ .../qozix/tileview/detail/DetailLevel.java | 195 ++++ .../tileview/detail/DetailLevelManager.java | 236 ++++ .../tileview/geom/CoordinateTranslater.java | 233 ++++ .../qozix/tileview/geom/FloatMathHelper.java | 13 + .../tileview/graphics/BitmapProvider.java | 19 + .../graphics/BitmapProviderAssets.java | 52 + .../com/qozix/tileview/hotspots/HotSpot.java | 38 + .../tileview/hotspots/HotSpotManager.java | 65 ++ .../qozix/tileview/markers/CalloutLayout.java | 15 + .../qozix/tileview/markers/MarkerLayout.java | 269 +++++ .../tileview/paths/CompositePathView.java | 110 ++ .../java/com/qozix/tileview/tiles/Tile.java | 297 +++++ .../tileview/tiles/TileCanvasViewGroup.java | 489 ++++++++ .../tileview/tiles/TileRenderHandler.java | 77 ++ .../tiles/TileRenderPoolExecutor.java | 110 ++ .../tileview/tiles/TileRenderRunnable.java | 129 +++ .../tileview/view/TouchUpGestureDetector.java | 29 + .../qozix/tileview/widgets/ScalingLayout.java | 83 ++ .../qozix/tileview/widgets/ZoomPanLayout.java | 977 ++++++++++++++++ .../com/qozix/tileview/ExampleUnitTest.java | 15 + 31 files changed, 4577 insertions(+), 1 deletion(-) delete mode 100644 app/libs/tileview.aar create mode 100644 tileview/.gitignore create mode 100644 tileview/aar-release.gradle create mode 100644 tileview/build.gradle create mode 100644 tileview/proguard-rules.pro create mode 100644 tileview/src/androidTest/java/com/qozix/tileview/ApplicationTest.java create mode 100644 tileview/src/main/AndroidManifest.xml create mode 100644 tileview/src/main/java/com/qozix/tileview/TileView.java create mode 100644 tileview/src/main/java/com/qozix/tileview/detail/DetailLevel.java create mode 100644 tileview/src/main/java/com/qozix/tileview/detail/DetailLevelManager.java create mode 100644 tileview/src/main/java/com/qozix/tileview/geom/CoordinateTranslater.java create mode 100644 tileview/src/main/java/com/qozix/tileview/geom/FloatMathHelper.java create mode 100644 tileview/src/main/java/com/qozix/tileview/graphics/BitmapProvider.java create mode 100644 tileview/src/main/java/com/qozix/tileview/graphics/BitmapProviderAssets.java create mode 100644 tileview/src/main/java/com/qozix/tileview/hotspots/HotSpot.java create mode 100644 tileview/src/main/java/com/qozix/tileview/hotspots/HotSpotManager.java create mode 100644 tileview/src/main/java/com/qozix/tileview/markers/CalloutLayout.java create mode 100644 tileview/src/main/java/com/qozix/tileview/markers/MarkerLayout.java create mode 100644 tileview/src/main/java/com/qozix/tileview/paths/CompositePathView.java create mode 100644 tileview/src/main/java/com/qozix/tileview/tiles/Tile.java create mode 100644 tileview/src/main/java/com/qozix/tileview/tiles/TileCanvasViewGroup.java create mode 100644 tileview/src/main/java/com/qozix/tileview/tiles/TileRenderHandler.java create mode 100644 tileview/src/main/java/com/qozix/tileview/tiles/TileRenderPoolExecutor.java create mode 100644 tileview/src/main/java/com/qozix/tileview/tiles/TileRenderRunnable.java create mode 100644 tileview/src/main/java/com/qozix/tileview/view/TouchUpGestureDetector.java create mode 100644 tileview/src/main/java/com/qozix/tileview/widgets/ScalingLayout.java create mode 100644 tileview/src/main/java/com/qozix/tileview/widgets/ZoomPanLayout.java create mode 100644 tileview/src/test/java/com/qozix/tileview/ExampleUnitTest.java diff --git a/app/build.gradle b/app/build.gradle index a0745857..6cc14830 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation fileTree(include: ['*.jar', '*.so', '*.aar'], dir: 'libs') testImplementation 'junit:junit:4.12' implementation project(':leveldb') + implementation project(':tileview') implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.github.clans:fab:1.6.4' diff --git a/app/libs/tileview.aar b/app/libs/tileview.aar deleted file mode 100644 index 3afdaa310b8a38dea85ec0f61f062878e647c373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77325 zcmZU)1B@;{&^P+5_gU+#ZQHiJXLZ)LZJ)Jmp0#b;wr!i=|NU~4d*9q-(oTOfopjoy zX{IxZGGO2^001;J000mIyo+Vdj{e7~fB*of|AW{XJK9+o%Nf{On3^~_)4SVP+r)L) z4lu%rxq-bw1$Y&)pCMs3vpCNv=kX9RAsAdJsd(h+>47M10O+{xwnYMsTYBC!b7Cu3n|wABHaMG)KDf3s6_U_w3oRe+rGtM|Qm&tOK#Dhk`7iW8HdjkunV&SP_+^sh(^xoTHtD*=svY)wn@|Q_|w&f{VZO@i|@bxWC7O;zz;PV;GQdNV+OhM<`T% zA(cOdAKQQFp7?Si5$XTevy)f#d2HHCxuML?UFJn+MqKfac_aw|R;QWjM|?Ix2}NWX zFC52`?Y+?KqwRt{M;5&VJJVit7`nN3<7ptjs=z;*&@QEQl-BPk2e^aosLOFuxK7(p zhZoe2&uP4iD~;$)OE~ro{mRkTy*ERaY5%PyjW|tdzLfi>w5Y+bW>dm{S;Bl_Jy?>+ zfzTmZ8bE`>l^86>GEdAl%-BoMe?>VUs1-*}+aU?Pd?25b_%!q?^?GB}3HF)8=aTzY zpg$(9&wX&%fxAL~2>NXfJB$b>CA`hudvW<{vc^7Hr@DkYGlXT6HfCDAW*!gzD2(-Qlikk_>|vcwR14{UpU&e{qAL zFT;%(xwPFQgSoYzoY8f#8~Gt;2ZY=(ZB8{f0`!BER<;vYkMjp3qX+JqPCLtilma*AhacZ}XQMxMGMd)rMX4_F?zKCuDlINb zMcW4qMtVB2a39I)ejvtO~kmO1!2wk7Kfh>fLt&gT>*OEBgVd&>;X(9e65 zJpMcWJoK(YlPkWSo^((c%`KGAte+$og8$T6PZe4$EU|~SKA{z-K&6vzS=@yB?fPGA zbr9_A42?B7kB7fJ{i=%%eg`_aXbF0C=)8%p9iIam!68@btK0EPep>79-uNE5$rxMI z;IsbTO%4lGH218H)2k2+^W4TnlUViqwt@YnX3D>a$C=FK?c0T0aUwu(yO07A+=_Zr zFFLc0X!K*J+tzD2K_{H=c#U*u$G?1mlLl8?rxx3loiC@^24L?_F=8y48pjS?oP03y zBkOcrYaLZ1ku!l6_{`xN)&fN}vJn9KDXeOYF-~TOrQnIT4sC!&?xU98R9D#rP}Sw8 zo^N(98~M1&2a+>s>1*o<=z84MT(0J>4cszncdl%KKAc19k8N!F+*WfxNnBvGgnYQ? zOm%k4DFm@JZl@@v0w0IijDwvTV3Myx?Dk?pEPu~YtNvJ3=bid0g%s}oLi#^ z8q0~9w+LxVsD51n)1FBVJN9;#P3>T6kE~g%7Z-Y|h$kr*9-!jg6WU29EN;DFo+bVv zAH7)ZSJI!eSglvh^n#NQ>l`z7$?DCF`edC8zNDceJoE7wMK zjlo{+J_iMX7j7TRVD!!*Ug$%7JYE-av3ZwZ>U`$S$)@s3!ZX1e*ffk%v0c zU=A@19#j3Nuzy5<*Y&>=b>1jXwp+MUs`A^e1CNAa+=1F{mG;1S`94fMRVEr>AoZ*s zNhz6rN8*A(1D^Lh$IkTZT0Nh)C(oD$!j78eQILMinB{&Mi?7kFs0Wb0&%|||5d|VNK}Segi?cfvzZ z?8E6MvF+4hfCY>Ke9%i!>G!7g?$!`E#(jGtG}NcrAU%X;iSTKi#{7*)6<+O%^;!8b zuF5F%E&p}`kd>z7ePIb(p-_A2GR&ERoE5LVkG3(qC>sl9W$KL8Z@vKPvG42H_>Ei%lIkvN8BEH||BJV&~ziukILi|myB7lGx_(|dO7~;_m)&4g4IvdF{w%i@r!6L_BgQ# zW&u~sxAu@)2mgRj@CQTh!^r`EZivB6e*E+KK#fm@9+cV0y4QtiTMAJ0bPN@XM2Ur^ zE^~A&h{gG=VL4c(QEAWf2agL>?!s%*rMSSJuLvcg(N8NxL6$mK3%unhld`Dr1Qm2L zP8H#k4aoAjw-^Y!JcCoW=f1!n6H-nvFz)CVGl_oU{bmh&>U&)l} zHg_;`#BKOO3`~HZM~2^0ZqY$GmKxbJc$Aj7zimLT%ix}m!I6C4g{F$NL>!nV30+~z z`gGO}9E|25c1#T$CYoI08!kXM16HH@o1=8x=iX}2iZdCEV8iGrD<}B_YMG%Y2(58~ z``p$bl*nYQ34*!UbVCdn7r3FF*1`W=dZfL}Ta$$;<>m-cdfmJqfVYoT!qKY z`ZG?>2QQWjOoK5Pl!7!&wNq}ht$B+eoi;ug1JB7aQ8d_ORR@w4@z*SKQVsA)>Aki) zH54A}gNyl}Qzw`f>5exbic%?AJ?x;d%G$qR({ziMK-rXip8m&gMd1fUr6|6_^yZ6j z-4xnExWh%UJ6p&ha#2OpbU}Wx`jlf6C>QqJN^lZcjznv$OR_4orSV%II}_2+{c=Sd zJN|AK8bq4XPr2N|s5RvUMwe#8UzOs2Fx8uH`>UG4jq)NewjC+~j zRd5T&Gq*aSV;P?gC>!=DK?so1o!{3GRKeSR1oB3@W}TX6WST7AmST=ZGCm zEi4nLtSUEDX9rVRbj!Qp!W=a7X@6xn1K8vIa~SO9XU#Q}sP)qHU1`#buM|-{%fLv9 zn!6FGgK=ri-lviY15i7c@jutnLl9)YYh2;6pmA|Aj+_q<)E zgZ)Vqyfvp^^Dv~nxhykh$*_-*p#qO-6c@Pb6!e>nngOn+N@T)y9UwdnwJ3R$%udXA zY~oB=yvlmY8APDSa!nR~&iIWwI&@p==wcYvVNQMMXVaBAgyfHew@b=;YM3|OX$%z6 zh#A7?R-+`8q34O-Z{`X2eeaXdLJ!GCkb(D=3~cA?>B)(l7pLw$i|TXU)}o^371!%a zF6R@YM5QKHI#d#&yCT7cS zwDMOQk3Z;O9nWuZNKzjW50q3wcSDer*Y!33UYQ$`irZ&Nz9!Yvj!qLj4nJP!`)SXU zy)6vX{%W?>Fi(yHQyAzcJPlQ8rhg-m>KUduqs`fa52U7=WVLp*A@kF{G(k{lzbi0n zNAJGf-@B*nR*az>`p4?OzJo+*(ZPt@doOX~E-7Th$b?>NIBH1KFEIB@M;t*wER7Pb zJ_UVuhQ|$%+;+k%uRNk~Mb2Z~Uo&#CQ!=op9niwlP>ni3FuBktq|R;e7x}|zvzb%Z z|AKStJ)|XD<3xxpEEJ}#&=_!>P^->&582?(av}}Y0ly;ae%a&ZH{eRNB%7FkgNa#B zh-0^BGb?jD_V!6LT(qUK2~&P3vs{Rcc7|c+buvTPK+W>tQmm=ew#ho0#_gmq?=9xz zO3M6&s}5#@Qjk@yYkRmUZ^mg;H_1ABn62c(Ik6G&xlY-DBx9p@U`qX9GOVgSBY|EE zjjS45qN57&?T7k8-lpVkf%NBYu5vay~L;mR(!guj7 z0eeY(A#K^$Fh=YN3g%x#s)T=#otQI725O<*yTNn+2Hk?}Pq9Db+-=(;3`$8ENSXK- z1yMT8HM90vgGS)GgH5KoUR{m%D8!Fkhv7cU+|Ev@TS?s>S!J-=#dB%epiEfl_R1(v zSTWV?J;PL1VYqE(i+v6i{;Q*eukpFOLh+3XC1bAHmbB7+WeFhD)&AbD z-WJKw+3IUUGCcoxdl8|mB3vr9YjQ({-sLQyP5{9-wGsIbC1d|LKXln=-Sc+!^oO~} z*nH=yvW{o{I8evo`e64~F($Z?Q)ZelbML;=iTf=b8HJ^BLo_88X;l_z4RUoA$T!p; zHX$eiL5jxy@OY-obU+p%B{_*?(jpiS9q=^eIE1>6g2vy!=$e$&8>1|&NYCrE8yutU z-W_BwEWq+v#_ehxZjSth+oI7J=N+TP{zvE@e8LdEBq^=4I~`F5lCf=VN8iCX7={jc z0QjWI+0@?Fa`7DJY%{E-(=Z38_F5T$Vki!c?C{2Diw=TqYZ}b1PO2Z8gGHp8=nwQG zm6ktUx?Svs86rlrVmHe2XOST)vY_n3HH{wBprJ56wf)eCA>#J|kTPy-^J&n2g~ zr@5mKfx=JrMmi#oNEi#yy5bCRk089buK9)UXYp=sH2olr%oO|q8);jFWwrBINyvae zA&Ar)JbQkV3^Zw~&@A95Or&G-Le2;F;(_Ey$SqjQTWxw>kWK2rUA6*r zk8U>9U6GaftSA5cd$?uvvp3k@t~Dp70+xF^Wz;i8#?xElLdGe|6K08|M{C+W3QmjB zL~H6hE8!bo2!#>fK!@G7$(vm9#W^k58=(W~1a-$O2v)X@xSGYoS<$3*y-j;$7t@CL z{c-*rr=&t4d5im0&{Y=d-=ny7xmf3UZcPhc(Tf?WolGuN`0QiG)YCn&A^svc#Q9@L zS88*mSG5%8zXeqik7`A?a^ht4V|!W1kFe{66RnAY8rv)P-&&qf5HHDop39hm-o$-A zO6y~X_jt1~t?LFG1w40#z4njHGydr8+7K|l1W2&JZ`7OChGNh!L9-}upcl8RFG%V> zloSUXouBnwR`o?|lA<8aG*2lnB%BAHa9!o-D4n;#{yG46+E6l5^L%x3Fu&(1U0rOf zJEfwRR4%hHcL=EYYOpZ%aRQf8ry z!?Kq45An|IIv3^R1>u{I#cziF`9|HIOx9=__mQMc(j+&%nfvY=vs=P+T}F4?gKHgf zTJ|F=ltmxydB#<8Hf^oT8vq5S8@io#))@=_v4Y+nDFnR9Nk>;Lz8NQFk+UR*6vOp@ z^^F=@gnl^OBHBrl;*c1#A^LDzSS6foVp=$UyMY>kYnb+-bRC--*lgs<&0;at^Z><& z8!2ofRn|r~SF!*+r8YhCIx+1BN_@<&2I6b3sKQoKGRzujrFxtRIDZLtKjmChn}fz> zL&bkf#`w~j-WcFRaYT=MO+Q^W3+j47bBQ*_;@sT1SZ^XQ+3^McNp_X)qXmb|g_-ho zv)oJ@NY>h3+_Yv7Vf!ny1n6L>WD4`bM;rvy)~58z=%t@V4o^lu7H+4sjqU}3c<%SJ zcSeU;OoB^%eyPFM=&otu#fnKusI8S0H*=!KgD2wvetW8USGV{Ot=3#^>j&hO1D{+ zjFenY{dW_?pUYmAlbiSQg_(hDo^nbSC{uC?bbUC$x{RiOPdj}x>y#hQOXmxuM|uj> zm1+j$50e@OEB7K1>*tSbWBN944nr{t7>1AB_eQ4e-14+@f00){O=tpllK7^z8>>TB z!AGbSQdRcrBzSUnPF5HHO9|A1bX7LEMPoa;D#FZ`1u=m_*zCx|m2p{l`4iuZu79{C z>&A}T@sY*n)xKMLwujqfTmy330fgP!&Ey0WCO;=)K(>@@%Wb)S3UTB?^_8=!=_%9P z;lO@N#VwQ_=TiC_i7})+@xJgiMj56a-F_M4>GUKo$OPYt1c&)SB5`^n|2Ec0;bA&r z>MyIvgXwB+7A1HV>zl3gHcQN_WC|hdyIXzPm2@gP{|a~p%p?WVAY)K>QV=OI__too z$7A3~Q5hEl=L215(f2U<>lUrzZNSGC_?J4JLTZKraT{RDOdp6z*XG+f zr*dmtO&xnAJHj61%=`Yc@Wr_n!_VD-l&5KHHJigd)epx7Z*$mm2F`?tx)gFDR7{d^ zapky|{bT+1pqpz{m{RWSlr3JDcIZ-U7_c|%Nl+tsxs}N(Q>m$##oFsoSRiSMUJzm1 zkPWXa>;x0>(M5xLkX?B6izuhK>>uCThoa6aTv6F3b3uf4d87sf^2RxO6MTW7G7kYM zKc~y-Ax77zlWRg#Y`t=zDqVfpf@^{*L)|EtTf#E!9H`kfX`PNpk#1}iktukcmdGTN z(#s+UvCp^seOwVCVyYfQX3M6g(!Q|QZByvL#RP|2ri~KMi^C;%4zQYND*S6mi zone*x&lWX`67Yjf^fzkqOa{X;vYXs!lhB7IXu0V*Wv(F`|HRiin3%v zZmmN!r_K$gqVOuxidTrfVQb2A2V_#|ivEQTL^cZjhUdy&5hc4{__@}NqP^vIMfD3E zH;d^x{3TUyOY|h!EL*q z7_q|)qZ5?fj)p~Co;b3c40AHQs6g^mt zRnol+RGtocq7h;8bl)L^w|Uk#zP67y{`5iJP=XR}f=Ibbn^#!bn)KRt%1&0PeqxI$ zgXR^q#(PlK0wMD^h*#f+S!%d=hXb@mI1ykTIdvTFk9=aXGJPB-e5#^a0IOu461rpz z+E`NoXgJfT&>E5EcXsg}hp;l~tnXeN!jF7|J%Xes`1%Y;`1MUA+=)M_MBW!}+$=c7 zBFi6al=Yo6dTmBh8qg~YxU#dGD`_qI_!v`arDl{!$N(is4cfGWZu#(EsCjvlwbsq8 z0lGWILZ4_IP7nH1Opq<2~y3darg34ecy&U!CU#wXYt#P=}7C-oN%eA!=K9TtkV z|Hi={3f@bfIv%OeGsuM>!#ZoU7`6&@?3kIB}RFQg9k&M7-qFDv#)^M zb(Sfwy{SvR4eo=Z{(0dW!>d=Ks)%t|4?n4M{{BDl4%9R{< zXzXacR7Ej$nNU7xe<^KsS|9T1m2fDCPuwyb31M(&HfyS|DRXFoEzi`^-ennqo=P|X zBhW7-6&24Bpus3Gtj+ThbC_$2+&%Kr6j zMzB&;jQh3edl9j9Eekv_qzwTLJBvW zhxRQ>J0c%2k%1hraJ7-))8W$t^5DcbMz4$5T(9|fj@ zzq&#?UtD-6)PGR@RF_!YEU;-39Id-7)yNB^i(YJS%$u&p(PTLe(EYz)vO5f14DN1@ zH6mpzKmH9~@6B1n%XhssIWH0&uN)&a$kM$1Ys_LY%>Mp^JuR-0T_p?djGtRBQBrsD zDQ2Iphnp^8?aPqamgHvZ_wSizLUT7_RrMa> z5s_}}_GEKWv#m-&uf8mojufyz5mo!{s`FJuZ*!Sbrtq*3w(MiD=FdYJj}WXl)oaEK zd25nW>qlvV^6JeP5kMSuTl-- z$rphlnW_m-`@)&38Sufu>=lC-vRav{^E$ReuE}v2>oq!L7A>wDs-DFp92=?y&q3@P zsu>7F|5+`%TAH9XSYYzKq7L`~$+)7nU>GrNt`NmToUm3e^Yjrl?T}uN*PnDvE>#~k zXs#ISCI)ZNA^Wr!ZZ)ph6sFBeta5}4_^p_}O3ffN5>l_aZRjFNsdXfnQYf@W?N*Nq zJ+8);{0~#Hd3e4E4-&O~zZHbistdfq&Z-#VdS3Q+v%2wpyIx0V|8Ou@mA( z__merbOM3Qqc2nOFxLCfVr0a+FGSJWDK&wVp%pd*ZcDUfms}SKb%X9T^l7hlE3iZZ z3%J3xZQub?J5i`%XwpWrPixjxOS6Jp+SyD~dY$9iq9t=@2-e|~by^MOnmm(7-G&e} zy1QJhN!VGHBhX=DuAT>moQ0<~^mN{$r64?)IcD0BbJeV{k82*I^wm2t$hC!Q?1!&N zM}|{Wns@hKOBqad4i2O?EFXiUf?=_TC!`C3<5?qw&wrz^P#S}@fJW`Y)|#DlB(<;C zR954J=3Ju%S~GcbtkITT8LbAV1dF5OjqcZZggeD}*l6hP;~MDV#iw$Xq^>|6kWG&r zJvq3CJ6U=xaa-);s`(l`>g;E!`L6X_?>KB0HsWjwI=!*rsxxB-}dVQV;#lf1-#Li6ye>w*6G7MY6Gi?d8FFva? zz>@@Lr6Bywu+0AeCQo3iQx>VtZxhBp^imiQZD@xnMUkG52OIw>MT?qiCD4~MA}#%g zfoj8<|2KgzLEG;-rzjFzCnog2Vf?(YcA(gh`2N{{`ZY8nX9?Cy_3@PLq*{x%{?QY) zlx7saR&MLX$c%{R=1e00kS7Rk=`@#`rv++ji7@m~TFW511gsUbv-y>gOrk$kj|9qm zlW7tC2r3#TRM~^}K@X5>?LjIQ=_gbn`Eved98;4dfNCIHgQ=UT(>2|}^_rqB@Wf@w zI7AL#pi7h}x)P5d_YfV(C!ZuiA>S8C^j2zU?RyXlp=WjJE|sfaMqiQ~3?zRm_)HlP z{7>jf3CcQ#any|TnSL0l3_IxapJ)*}`7m<-PsFqAy;y)pK#o**X`y12nKG^A*h|z< z!C&ttt@VNwHO_EDrQdsD^FemA>ziqm+MCkRPSQ1hDs^;d#e)V)=J zV#rPGPCNMR76^|ft&a{clZ$ippAq&{t*hzKG&TS0Aqrz%GOIY$pE0py$h!CQcf=E# zOy7eyGpl&LkF&F;?^f;Zh({%F-3kqeFO_P)l#1Gy`v>-oCk-XXyaSyysvF-H#i}Bo zxI8%VuWK8r6FV0r{vRn}C0 z`HBEkcO;PJ-t0b5d5k86G~TG{ytea$;h-4@DZKovh-B{bu>V>^_a44TTpy~73igj; zfet@O5ghf20K*b;DTIJ_4mcBD`RSOdaL98-$hh9ta-BiwH9U5oT&i;loZn8@G6+CNSEI?7G#>n$YWVwLBJVms{$!4_T*BowX`XePr6Ike>Ry z`{F&rd223b*1tDv{Ma)4{0<<(McW-Xeau&InBs#!i|QOpCy~nHWJ#2)o z_1Xr~!MQzW4V?1^PEoYSD5X@wE>?HX2kj9S|>7`}WJ{aKMmG@l-=63}oKRwUbd1kBlXI zn4g7DY%M)^BO0OzU7tHmPW+LYQ-ZXg<6Y#kNATALbf2a6?ifU%el$JOPZ3lFO`7E$ zzy+2j4RHV>mJi)M5mlfZlGK33elxJ)GzChMvG`vHy}fd%xoj_ktVbejVt&y5K8J?PGM|e7H+^1)%FfiR>Cd zG$=8zaOMBEoyVl`;#Ty~v~>IKofY&Ce>8e`j|w8PuWkItYz5tFL5@2>5>^2!+x~0^ z@Qg?BX)$(!3>_c~HSlS^HobqIASG?ViV^_nWO9C=w~>C4vC3v@Oxiu-6ugkb+>1kYF$-{F$@gR~XjaTjTEaS!v1xk~$R zixLN%1?dj+&|zjHAVw4NjVOqgJ#})k_WqRpATEI6^7PWxqCE!O4d-OnAf+RoK z@taG3f7X{gy64*)!WzC&E5!Ttpib&Ib%^tF&>O9wo|W61JZrYnQ0zt>_;Hn>`^7w7 zHcADsc{{;(c*!0Rn??UeoH$?D%i900OhN|8?d$+IWBXp1@m@hJOiD1Yn~7aRLP{;D z@#TPf=Vo5uttar-`TtL2e*<<1cY@P;xO24qc9LR!pUkc>hC%6dC)Kai8_*%a=&xWk z()Fa`2mamydch(?mU^+x!VFiq)bmsobM}M1yattm?PFu1T_M;v7raO{EB30l#VX{? zhx#)0NTsT2&ZlxzulPo=&8N#DF(%%x-{=fStivh6Lc(oW5(dmXX2aC60tm;#1WSa~ z)&lc0Ol&$=&=28L&1AGp&=OoTX|0kSpqD$oH?gs|6JY1MZ|}-|I68$e4%gBsyZoA_ z)i|GYeY-Odb!?h1CdKTBR%q$$n#Lq4z%C(g?jh0xqPvm4X)0776X=epgB&=Nc5`6Q^+jjBYwc7E$DVyUz`c?|G{ro_|ju5;htZ zvbl};_oXgYv_*8`^YiMQG(jX*)JJ4RuUj-ncGTixI$QO3>*!DRmo@xin-9$Y9SmoF~;BH#TdF|ZMO-)=?O{pioT<2hX)w!T9 zp3x04-4eyxim(hSG-Fa!$97#Ph%Boo9P=0A`gU+Bb{lgm~pHytx*bqm&&Y-Bc zSKx0{;%27lP%2hlLwNd47%L1W@YIqhGYpHL3NHWr33Q9QiuE|vU7w=18b?sd9CsPz z56QyNu-&*venW6|mjaL+r-hK1v>zn(1# zn1C6sGp}g{zl^ec26;ifE&hCjm9=U=l)o3yoXjV2;%&Q_+hRJ}DtKsOBXS3X{qg{Y zy)@e#YZssxPSE2&Iu)N~S*fD0m2dh*kEl;qQIkq!H`Mh#PYbV-n;J*(6&f}Fv*q^Y zh>|T+VfgQn#s1I3$i-3Zl^xmZeL8zF)XD9zA@c58!+{`fP)*{AcL#S;$}qAJUuY-! zkvg0%PB=#cw|+1zoU-7PwD9~vK=p@;nyFbb!sAQxo){9;y`CkzAyNaf;B1qayukb+ zgvCi0`QuiSyClqka{ffuCxdg#<&t0L{!-e9u)i8(uJ zW9h=qR7a<#AxTU*=uw2q9+qAXb>o8w$kTZe@vCXKU|fT8)4@R(O6ODmU*$e~e_ZkN z0{|5VFwv-L=98iDm-OGzy%r$h(jMx*>Zg%+vxBRBF3Jcv@%bK(7y2HKdci0E<2cPH zx%|J^4u^?DPTZXZVJ_|26;d6Tfa+_O-{bza8X@J+SQ=AGz0WxK(!E zhF96V_C<55ZoG{KUCjy7xid!mc2PZ;d<9hIa!jJisv@Ax2RKFnGoXQj%9!AwY%t%t zpr7cF007*;+{kN%P4X+9xFf;Zn*%=(&1_RZb*qS~TS^~>Ai})T4uDzFnYOcyX-14b zG~Kk*iTVBiB*+#pv=*ohfirogX zWE$hzu|i^V7^L-`7Lq?re{Vn4VNA?4FgSAZ@hl{on4THcRGZU4yqC>RsB|1XxZK)c z6#80qi+C-}N2%LBk*E^?sD|Q`PCj7tikUq;1JS+uJE^umliXCO(}XX5W6t1& z+q0OI{^Pg>ZrIKo!5&Icjq_?rDCRk$T%i#Pi=O|SH9?$rfj;i&}V+!VvpleI;Hw7~*_S7*|shx?? zt3X@;ME;MctGEcklipKzq@5G%8TI~&>Y9(27U3sY`X=aWF6Cz!q{5fdR!PY5GDs)% zg#2ykvQS4!YkB`Vfva8E>C|uZ$fMoR>Vp9$!$G?1vz!uFOB4%OvqGmtXP%$U-@_vp zd6EzSJcdq_LU6yD-W?p?xk0hI1yVen@Ap-KaT*=pbkCPX0<9x&?=3pgdrV9p2F2Ib zfEI9{Rr`Qvkt^3tdC3@Xi|&WMR3l7Fc9w%E)Qaks`0x(8L(r<#lYN)a-%^T0RGM8? zE(QFW}jyF)bANMspEL&zK z2rwnT_yZ2+dizM54D?&deiUuN)i)hMAF7`Sh!{whNX z7O4qgs59ga7kpy9;qK3-Id~s-!CaTL_`67kQI~gDoS3BiOzsro-(Un*AKxGYN2W8O z4F<2%AT0e*QBx~8eQ%iVm$0J$O7ug>*vwo|O+!^D%biq|>6)cTqDN#Y5&}iS@}$!7 zqU+FD8|kol8SBBaClNloKp)fx_3#W4{WmI%A5Ivw2+I@vx?OqJuia2nzX)p0si;o1b(M~mFTeR zh@wa+S@lJ3AFc+)73Iu1z?uF&Y}MIy?*Sa59tR`X`H@6!_QW=%_pc%Oy}9(TkbyDK z4H$x%*pOum4W1L#Q>ztrlm%6AVCYl-Qu2dr*hLn5xmK;)zpHEtZ*9PS6^5^S?YK4a_@oF=Cn$s$jtoBUoHx|nVZ&@ zY7g=5-BuQK(TzdeTXKG)&NGrmM4?Z7l|jQ_p{ z6d|lergH~k-!8PN9P)=9~#IM0>r#Ly^2#!@uMwJY$&6898JqKJDtAze~I|-#NNNiz? z!vfAXBr&@d_6_J9*K7g~-hbcKbs-q|0FSN|iXu&Tb>FoI zFQP^D=s;qC*y0J{$fHd9QIB;3ej@Lhgk#sU>a!@i@+dxZA<*7=TdMFxl(B$Hu(RQ)ipec;O@&&%CF z510$L+e45yPwy3=B}m)d3YJmUATjbc%1YOrWQvrPkEAN}+vWBZs9N zD@MW={t}^vh*-x9&2isU=$ZmN&esC>(;uMBAf!~%uLk%b4f;b_)c%DS&Kms4kLy2F z9)3UVQK@jdsM!PGgpsROA(rqGAoFdO*ENu1{)e?`fR;@YmJKJZGSB4<%jmjTB65zu zzvnO6qAXI)N3$Z&nb)OA6%m17`S3`NMl8AD`@W2`7BtAONAdAXyv<9NwP(C$E6syN!Rq%wmz(Gqf5N^2KaFX!~i?2q36E~6~VaCO+b2LM2jK>)D- zl~Ed58#p%u_!^`woX8O}%`(@IZ`>b4E z{lB#2oAE!%s&otxaDi|D1Ox;CKoD@dgJ=jq1Ca3Ga6|DZz`Ajp!CIlJ!=iF<3t_-o z84>Ylslc-QA+Z9Zp!j8i=`Zl(vpWM0@Q>{;z9JUjohg3)^-e?0#cRVPhRRgRre9g_ z&JzJyIH;^NE?#~_)C63jUZu3rs3CmOd*R#VxX-7Ka6~aYxTvVKL1@5a>B)^H*yX2i zsSR)9f@p5PRY)r0`R+?X%0wDkR@Hsk>&ARUo9g?NF_R-{;No9FFi&17(x#~H`m)ED z5{b&9_j#h(^Evkk3i9$slh+o1wA!7s+SF)O#K%pG3(cB>s#TbVJ55-U)7Y1S0Q4qSm5$lUgOmevI*`7))_H<_)9jS|T zrIcoPsoW<-T(tU;PgrXmbfbrNM{ zzc`&A)>J^h*XxNdc>{7odRdxkH^Ggpr?8FJG+zQSwL_&M5RnlP9o3$Ai$kFQdMb{;`w{^+!mooX!Wy%=>(=2R7EwMfC#U5&Ses z$m(23y!b(KT6p_)yHMb1BoT2ux(b~eZVq(5m(bKMNO!a^0x!h`!_}_4+R!Eb;GOek zbG?GomnIW93h!nHhhyQDlI|wQGxvPY#snDBX+n9YflU@;drbqeh0`9zQTSa%q~uC_ zv*m2MqQR_M#@cSU*}0q)icQ>r>T(DTMar!j?yejX&vQoqu=c-1=h?_w=C?5DF7o%K zo|u+X)FLJNQTiEaTIHz4iHxNjd+lk@vM&civT^uiL8C|>vwBlAw2(m8rm&Ox=3)o+3{s*F|aOit=do^>uBt;{f-Rcx(H&TrVqgygD2^rJZ+Gw$;frQNv~ z=k`ngiGj^si1||-GHl}lZ=i6^8IcRMjj+noz4uWx%<;r4Xz&zF{|)M0x3T!PqcxC< z4eqTTT~DvoF7nE=XJ?!7y;m+d`(<+uiYj3Bgrrw2mVM;RWMb-L=FDt7qP5I08-Mi< zyoZXEHmwj-_);^`?8r1?c8TTQhWt;XQIl)y8a2d$SDw#$YRXKq&BGZBOIaM&L=XfQ z3k$dEfjm;kWyI40oZIe}Wu7NzGp^QneqiVe>-S` zJ)?@dB(TJ|VO_Os>o&CwH`Mxze8o7qOpYv~gk?7o^LuV&Jp1{CpA(#+B@7GT`e{-r zU@nQePQpzV#m(`u9b1JHYRT{aRBhyWaKa^wWG!EKn8xF@P%c2h9iuBt)>ESJSlwzJ z$khmI{k^XabA}4hEobFVdysvlENfIzp=$Tq6p1skPRQa&t)un&7m29M2-h@EBx!ep zNUP#XKpnX&44srNBtw$0USN$!P4a&LIY7q0Q3j3VA6=IzA|^wuMK^1uLynr5uZHjk zYe*9lY0;RU3giaaX%n0hADE@ZyY_!=^Ft)G7wCg6V1Ut=;s#7GPfI4)PL`2&pYo?^ zYQhTCZdEnp>+9VQ^q7Q7iEPz^FOT07%E~?l^fu=tq*g@~0k(-pS;u*tm6#-UZYH_l zi(a$WL{4oNj3r>_w$r}=Qoe%vqW$6IcZk|^%2Mw?-_%od+qhm>S3Dkwopfno-^$>8Y2)b@cY3Do10m)+#RC4l!JR)q5 z(`V=OVz>3f_Q?-|&_ya|QTPLz5bAEvV2LD9F^vN`WK>C+j0tOOJlyB zD~%)P{95rY=x;fM}Kc< ztj5&O)!UOCFib8@sH@@O$_f{h8Ojy^h-Xt0|C2QY!v(X; z0!!Sb}Bv#nGNtI;0EsOBQc!4ZIR{9H`wg6 z(%g6i^dxWkm>Kst$sT(xrlzEcI~d|7%##$}@zu#!G8uo=;Ct=5h}O1mqM;5x8fI<+ zuf_u}-cOac>PCQ*7)x(JS303XOSQ1CJL&xR8+5Kwc1Od?xIZMNOlU&4<3FUywOKfL znfF}aME1yKRKFwT7D~Gv&7J(Lkrb^cW7unlaLi5etAq%mH(Zc#52*yh13{i8Zd3ZQ|gcM!?Hme*H6etbwHTIP0?xvj@R|r zpF_Wc0-KjHa%Zc>1Y=Y#FZ-)!a?#6}8<1BxPchHo7CNOBg(8Y~QyHf(F&@+82Ko>Z zfd%eR97p+$GOmv(|E+8jrBR}Q4#X0V!=3BJe<>N5LHK1CdP9JBR8kR_Uva4d1y+&~ zTw$_IrcD2hSff5U1(kllBMr}N15H^kq-zzDPG*)&raYaEOFP!qmbaY9Kcjv~Eubj&*1MP z>lyl&V386z-Qzu4)FsT)V);aaPE42$m^a|UP$L-{lEwp z&`lm9tN67bi^F^If6F>;Bu zT?QIx>=gIJ3pg&`&PDw7M<@^+#C95{|1?iDoTGZk=5gK11ERvL9@O7Ld(Ki*${O~1 z$VXQ;qm^8)w%ziDgAZjKkv~GIxA4m)N?i0&tv*Z@D7nFVGDTqrlFLpbf%DK{RjR;v z_)AR-YS~&=LP1|6 zONx$%Srl5b>Sn+2;b5LcKsRxZG4D2b1L^!$m1YV?O072-xi=I!GqAiIU45j{nkeH! z@DZntIj#>bkOc-ncwm2M*|X8#+N$TZp4-w3+a=0+(t)1KT?6qgQI~%f@lLUX_9-*~ zqw|L`yQFgq8OtQl@oUPIu)_b7+{MI4HK=)n#y#MUT3}a-8K4s(Y7IkV5k~>wVc}V= zk47-1BuG~iy`d1ST&S7%z}`9xv7;RWJ9rCcK0tI&m_d{4VZI%7dOHx?XSjpl@#oe8 z1^f2tGrBkf$16WT30{UZtgJZvbxC{L4cg74+?}tnCM>ZgsQHQu5^^ci84$>d53%&`_P$THE5 zH#NGX%mFooL$#?Oqa+1D6#zv@A{OEA0YTnO5Ypx~wAbPnO_M6`bBWB4XSYI)le?Gzl8dhhjXL6w~@Qu>bkshEGOifDcB zGh?8(k&kk7JcNpR6hY}d3Un+yr9~0*mN#J*Rl$A~h@NlL8m9Zq+cDqz zXA#FV(+%yA8=-gKuice9io2Kd2jeJ`dtU(ejq=qeIRKR9vJtVVAXeMIJW&A9WB=B9t2I!@g50}H<<6Z z`zz}7J1bJp{?URqXa68o`%7|I@AR;dm;F!f+ zoYOsY-ds^fV^+KF}zfiC-h`Y33}+DrBda=~TDDqXIms}=34gN4mq zRVfM=ZDndAfp9Sq8lcKkCmPY(nS;&k+L@z;GTbh_uPSAR{&2SNrS^lh5y2JUWd#~2 zk?`I;{RTWsiLfOuAk|c;i-|Vz`jo|`@RY5W^>H~@IFcia=V3h*=uje0#DW%HVy7XG z9bT_5t}bp@*XJAC{(p7AgKh5BDRH8T-9U&ibO7^g$zxkC&-K|Cs^Ko#b z2hUIU9|`cJ>vYZ8I4H|XG;fqjPT2H|2VE#B&d}!2r>d%nIHj#9vLUD7*|m+kAoMQ_ zdxadNi;to2atJ@Rf*LVwP-*Ph29Y{_bqR-6^_l89Ufj5IQ? zzy*(|W0q`STnk*Yk`;k_^o`Egy;6KCTxhPEuse35C z=}7ZKodffn6Cq@Wjv0&}ZE^o9_g1FM9Dg!FOl*HT{gpZXEe1J>0*Kr;C)9$66B9Fj#!TFK5p``+~B- zuMabuRxh7PW-G)xtXs(gw&jpTJrQXV$X>wZ7nqtI@nOVd~4Y| zu5U=bmHW?^*RgFi?*$%s9vchh_m$8V4+SqBbk=qoa;GDJRQlP5uDw`~NoQujQvHm5 z5_1NQ5EK`gXx?0{S$z2%pEYlus2oI#PwM)i#lY(-dU}EvStsD(W*2Tz5T0mx=b{+g zUH8Ff$F`0JF$%2Kdc~Xjj9z0I41=}C013+;q5*xt|Fb3WI1aw04~+|{&d&wI>#e;@ z?4ck+6)i#Q8A}{C#OfA?1gR~9nqyEB@9>sP1|`fz+TNf+b};_LikQdU#_ikA#S(wL zr!F13IO0x-)+Q`?GNm&V@fjLLVaK$>1Wu_*3|0%9~g&XKLDI|fBb4@WC zy_sNz4_VGOGStYz(7b22By&cbTih(MQ7D3lJoTE8mn>SV(5DC^Aw%F#{wB#%m91Sa zm_Vuv@r{88ZBi$!qI=j#=ri>>CEG$5T&|C5GgsgPz==oq{nb>uEmLu9DuxzY!A*-k zBRWaBB~s>Osh1=3`f9y)@rz(*6{bYn95_0b;eOc^(*vSo3#BZP%?bEAk^_a7$}plP zzXs8tyeg9?Fr@8$`~DdN5=sh{)g5T4KRQmsT@&dZP>H5O517Y>7n_6s5LDS-;J` z(p)Xlt9&wx+Xg|NY(!c-G~j9_95XQO{3oFa{ZJaI73|psx?IibHJe2ni#1Pohvl>97l{!*y)^@HM>pe~F+wHurx2V&odQg@Ndp+A>!th;TS40XAD?6~;1ud7IQargNO zpS{A~TG9kD0a7k2-6=yn9;0Jo(DM`9uk>^h@G(VlG*MR9@|(jPJv^CVd=+VSj^8Y; z7AaT*GLl2v${vqHJ(NK_gf}#a~~Gw#{Up= z3u<)x{MehqNVK=0|8(y$L*3tLo8pw6j=2q~hd(0YCNc#~FNiL_U6x5@ZjTn-A=J&s zaZw+6gt=ZZD?ePtu$govtQq-6?hQ0sm~h3yp3Pe$uv8+_kO$2y0tWLdA^C$EZPwwQ z8;!0n67vhzx3(Xz{SE3XS+;{B%<>NLD-seCu`Evv2}@yF7;DiS4JYFa+04Qa2}K>m zZ@Dfi(vg<3tqfVGf?W&g^lr(vOA#q^7*)o)7zk^K73OqL8|F0JiKSlW34aQAcB*qM z{!bvnIVMi%=W`H3584atdGwTlQGnkWBuj`=biI2A1G?1`oRT-uGYjT}Y5$YrPI>qv z$op(VqC_|gwZE2fz0zR1VQNURePqa_eQ=0Xof?u)z|0iotFR!-sg+@@J5u&l4HC11 z|C*Eni5P7E)Z$&21$zqVOL$($={~HfRdoo@)TS@-$EQfIsF-~(sbHCvpjkDHJ%f0M zSj|2@TWFgr#OlDoUsBkE=8Q0VCP~aup|mCXz`=~L@HSFJZR`U=2~gdDRLeJ0+8X`u z5@Agg3>oOg5xOId(011#E3|qE?NRg9Ll|e34Ba7Og}v#FHgw(jArk0E(}jSxB&$@1 zYsW5u!OT0;jvZ-Gcg)BqNEvO49Yz=UG<#=7PS1u?>VXd=2pgF#Gxs-k!E`ONQ;2Rp zSkkl{ zBVEljXLt8Yeyrp2d~LanA>HC{h+eX6;&;jZN4qp6oiF5GC?s0Ap_^7}kIN*lb?KcJ ziCE&bfPW%%d4Qtp{0wpW*xR6?{=($apy6(K@}hU_jB#9L&@4t9wDAm*Ea&sSZ_%P z1(@(a7M*RF@Y|^pPd5{GxOI3bMj&UD8xE%NUeY}os7<=DVr((lmu;JUho53!Y|@Al zP^VXH4(_i8yO$~&5}GLShB@!(TanhI(ID;ln$AlN@8I=xE;7`c9n_Q_+oO^y^W9sq zoZ6`;LOw>5eX4(_i#|RH*4C=o?V%_k#_g(7xeRvJLjh^eh;d*%U}EGzS=T!{Umb#c zK1bHE^?-XW!;X=Z5}OPTeraa$L@PRDOw$*I<<@qqnIQuaNwc00*$3;Czll8&xjnRP zK&C*T=Ns`B5O^*S0AT9^-vqNsc7xb^ahALrv{0 z_sKF3>X z3_!_SY8Fo_4!ctbWhXtBg|#7{mSTA^pBGU*o;OzL{u*0sk)Jn&yrKrIgNv;U{KHw< z%ucp?`p_rQ{75cScxWTGh@|{!K@PA<0&L8Ergwv~>L8u^5ZFO3@cy!4{jOn)TSD{z z5|+jLgkycl_5@vjU!8j#*8Xz&=Mn!>ZB#Wb zG{04sd%1nHlLHv$Oq;yFXl$_qK&81qNnLklthID?(&YfYu)T3jxgloKZO5c^?;&2< z2f|({(BB+bQtzlzz0Sktn4rB#hjKypW@ylGUlRjo3=9+Fo2a^QydNO8#eJq9IfVNY zMRs3-d%FIF?#Ua!E&VB(b-gKb1!3QZ>)Kd0uJqxMWoHiCXHM<(npx;f;6hHr?!0It zgk+)^21%WUT>x|)Yy1@JeTHOX)?=e1*sOP0Z`H>8Zg*a6>=B98vg~~QG*f`De>hXo zZ(>2nwD)CQ2C`-CC!PAfB?i7VTHgkSe{wCqt$9M|vO?IB+#bFM`+Lw7JEMtlrpvkA z7-N~c*T8hm0nrgN`G^Fd4e*UC!`%64blxxgqG!D@)E4J2FhNdc@1eoJ=|c2OrE!C@ zul~@?g4BHDEpto5_Y29Rh)OQR2;(@XLhI8xEl-6MUD;W&nju*gY`u zPDuF8fbdI(@CzQn*LWj{s(ykvi?O?E2A_K|lVq~FS=-tYieg zq;7^#%PhW{Y249<)PvYdlVv3h+M2D<7-HArOyArp->-1)hItKbOir)Krf#S}IiOjc zj3QZ|XPMmA0CqdhCDWJTkVY;e`REKkD-_c%%dnd=M(`zlGtT@D)uD;4q>S`PO7Fn%OE zT&8qeQTd?eD@}}Gs!(tNxOV~igb0MPLbqtrhO)ZVd_{AMx|L1+$`;{)x3>OV=_l~E zu(@??dv@dSw7fg^%sJI1Zs7E*(rgM%ByLdk>R^TlT;GaH4(6;*UG(*bExTKLs@7&> zb=Suwg`}z{)#KQnT`-2J%lBKfCBsx6&QbvBMc5};8B(KuO&W~vJow}=wmF)UL1Nk; z9z)(Nx-Unw%rHJa>S3ib*}eQVc06F@!tOa)(k*G0lpS2FU9T#i+kRBeEztC5Fzuk~ zA;8_G3xVuA7!~b;1H~fHT5%D&K@<+;?L*}!Ma8EE>= zcI)<4gUN!xq-}`_UMNeEQ7q%hN*x;yRGWM5Q`FW)R4?7Zl+`^=B-=uW)5EE#Jy+)4 z^S(_f_fKngUZ~m7OZ)Ed*7>gWZb?p~_~(YC3<*iDR_sunq?Xs}2kMG@`O%w0d*0^P zDng+ntdY!^K~Nu-9fq||%3JoU!u&nTW>FnwA=5TLvuMfta=yL**lmk-^epI+2Vs5gut1@bw}Qv{fWaKbS?AMa3ihc31){HZQ*y5 z<1Za?xtu5c7VfaFiQ74_jSft<1jSW>lg4x4W3QVE zVIX4&1u@>jTW}Tb01-oh#*2E*`>N_~fv7j_q&k-xAN$%d<0WXd!f{CP|__}z(wQa1f<7@sm8o4kQTBX=X$1DvNU4U_vr4!hw}Jm zHH0}ug$HExTHGdu%jWZtTJ0tQ@JBWz@@zoCE~`Sm68F_{YLK-xCIw0DFkBB__f&+B zRvQzINAI)iv=lFwbAaijDemx9&fj3(qmFnT+*NgL_Hq#8aYo6i#`{=};^a+-bGU>y90uq_Mhb zf$xlRN54irau4c|$?QT8>OzbQ#Id+PRIrJSg`{TDAyFfHjEe}6(W|BJBbQ40TbwXC zU-Eg>#Grrj1T8{1O%SG3HEp1!>ZX;U>m$+i3c0Q=M$ujb!sy}`uws5#i@-DPacX^D5nY*-1~$2-Q9jJtqm--%{OjLMlvZxI5Zp+D+nOVA(lvnDPMaA(maaw>Q0VYMEW=PGVEziXhq zBRAXJ>qhNb4S3bO(}nuke#1rbor88mp@Ey!LhD5|O+F2Cnm{dRN$$GUe(1Vp?cd~~ zqk4msm<)4*F@^xQ8;ci5 z@WM$ogv(M(N5uh&aKyFK9&4!#&X74>)x@r0A$o#%Cv}NHoNQ^qW6gxuWN6&D6chQe(9(!f-S{k2 z+kF>yIRMxDG9aacrW?za235;AxRB6?-qrfaj(x1RxYODmj5LGLmePwh-}`!tOFb!3X%o!$0Dd6!1Gr9yV2cOLpc3jQ;x_ zDt*xF0a}M*83zliO)Z*4cD%7OjC13|9+8_mm#K@m_Q)ESq|1GxCvb z=e#^rRE+tlmCH;JC$*iwL5$-g((!VYPy_s#mw4;$D%D-Yd^mPZ66WuY?X8kVj+wp6 zTLqK3axo8|xi)JrwZHDCT5aE2P18C;^N3_+VPXA8kMIR9f^Ui$l!41O_o<&RJKAe4 z&TpQT#0NvuL71qc8T`ER#Fa{TXjP_i|!cQUtg{uc~nE6d0sDIoLIGF{IxdTa5) zH&KeG6E@=4&f;}WJKi2Ssi@k1RRykrPA#ZFt8!=2JKh#y z(tW)I@=3y-bUd?JUM143i!>m&lJZxpm9o|2OAiS*(n*49-@XH49XpZ2+KD*@(|w!k zZtWnby#IOvo~g#mr6MeT%w34-yC^iVle#>KC5)pGi`G=xc}b4#=QEzFcw`N+pB@1K zvoW5t8dRMt&BN+E^Jtd^giLBp&>iIhQ+9bzh3UY@>C(RJt80m)adsiFco z!$dr-x(`*wvqj+@(tLGA#4z{@z~hx>WJ=GJF_uUwkth?Dm{DYkG+0Ejpp2CAj$Ja( zsA+9&u2rb{;03!|+7ArLar6d9ekiUrQEv+piC#!T()%!}4SLvxEbzETvG@^F5WH3J zR#JPFFfz$LnP!kY{QH+_?7y&IrI`v+-Okbj3Y)&CUhe*to}`llE2GKQ~NV`t*F zw4flOc^j3$0GK+62xOSHprkptAUq_wOF}krU?-P@8!Nu5MY3WQ)o&h3M**#OqB@3{#8)ZHxk z$)N;}UNqrfGNQ13s|9Y7>D6Kn%?z_54(jPf1=iiL0}gMc47+o8-48o4h2UG^wJ!MK z$hR8t-NchH=-7xSpLk#VX*icJ@(3_&q+mYO^R^fy1?iDQ6!vf>gqbhlj9f`4%?tq? zvjX!WjdgojTW&i3naGY*XYIKD*`cIE!f)<~R0jr-+ft1Z-FXLzQ95R2*Nze^my+op z>)b<2`i%t(szyo+(?(TQp+LMfrTv6?IoTyyXx@ni4BZ^v#|{Z2Ch9rfn>}-R8v$*VM6fSF1ir+m71Z zh5L#Sao;in1+Uh`h~3Mpdbd#>O6~vPIYH6b%;^mc+nyoy8 z8y1S9jQVm{rc2cJ^OJcC3v7p|i4m$=W429=PAZG$Vzx)^-o*S7h8#1;CDhv{W4ofo z8<|X7S|PS6toDwkrDsCp!veo?MqD_$=Easy>A^smRa3{yWClyC&o+a!W(^_xaHf`R127v_rk1Gh@w`nlrbpA58&L@P0^u|v4LY_!h-5beH+HT< zTLDASgu(Wek6-&FbnRWM_F_jn-Nh=a>7EVSSoccD^r+;Q1$iNj#VVU4+Y!l(8Xo<3 z?kZ{HDV+o1-MNbV`?z`(Svk2C$8VECEEt*;xFaVA@6L2tV{9&j+v&7#A6GZlp7}yc zkCAl|=mrXD2NzC(>TOxZRLRdp1iKRjp& zOhf0qS%l%#m8e2>c44$y%yeCxihQ-mcBWo;Tjr?p8>U7fml-jRWj8;DG=m*RGUSUg!$qHcEXxYf$cao(G|UUMR`XwJ)@Ku@(u)twDfvbj;I6j_aU}=$4!1JUK4#1Rg5;!NNDQeYB1jLVXzeOMrbPNtymsTUyD68_r5+8ZvIJ0 z#O*#Scb%G#W`DLi^-(+MuJB#Vx9qZyZf3jjJ2=AbXgBCDgj;j~ zK<DyZJGQP;gOM_L zjJ3#FtT!Osg?l&P+e17s+!5Z9I(D(KK}CrIgZ)vX?2z?U$GF=8c@v2@7b9t3!!pw) zN7J$9!R8wO!xwR+C2ADIA+{3#-SRlL>@cpk-GwnncS~w(%H5Z(I{l zB%*1M-7fnSJ)8C<02>t*FLjg1L?wqBS#pEK#hW58wF*ii)#_oFFUDLrM2O?B$3W(j zQ%`>Jst*!hIn&CVBvl@cT2DvuXKIk8deLzkbSV!S-szTXCNl-F?Cam;4ySQwI!2Onz8{&^Zk{3k+CXyS%NP&_afr{c1nHHw% z81=(!2O>AHlX{GWY=-DR&rj7&xL@Z%yW($5;9p>pOc1>i0(vJp@u53e|LwdITD`-t zeg+&}pB$IJViqKOlppBfY@fMfeOt9y3%TAgxYm0zAUWNM%@z=eG-n4sRIRPm9_jJ^ zeaemuKqe#%qRq3cY~?(mu0RXLdb?$|oR~4V!nf>FsAj8p7IjIS$@hhK*)iWTX2C3# zy~e|nJL7aoc*~M@Z%|9emANSqsud}s?x2ctBqX}n>8FbhzNl^ z$Z!WsM8&KRi4c&kV#^7}<&Mx_XTVf1$!bW!fZ7sP+mdcXE6Cwa6ZD%UhO^B#Q%n4l zee_pimMdyOcPm6lJ?1o>lx!?mDrzC~Xkc@TB4+!PW7f?Y{PsZB(uiOW$rbGx)P3-B zO`tsLReQ*+9rMV>%$}_n{+4^7vje@Vduy02c%NN(+FLzMVipNd4^8m^{r_%NE$lGyVZsaSh32OFnu}QJE<9md- z`?-gh|H=eVH30}E@)>kPPmkLXp{TGbF1t_&^cY+ zI}m28Yy!=3!qC^U#1#fkfys==7JOfd58(BgnBDc_S`JV0pECsys_foq+FjXW>IP0izjo1g_{b)3x|5vV_c;uRPDxxjLFY7!2p^BFK$!LTT{bQJ&}Q9AYvb#?*O* zuQ#}PHnDMcv51RCWy6Qwe6S5)5)z(S?z!@BrT5Y_kwtl^QmjWb>woDQ?JX&1E8&;; zhAdYKkENX%&gQ-2xW+IT%(L7hQG9H14qhJ0|CFf8b^i7V4eCBN{8*T-GlAw{o$jhh zOvNIks&)tD@SKSm`s0O*OMf|mv7!FSFThVi&|3yyO?`j9g6}E#AbV5)yGv-|Jz->A z_H}9z6)P4LJLsg28}s$&KeEZ{d0?a?2mpY6lz*2^O8-x@Nf`rM12Ypx;{SF_$lSoz z%tYG4$=SsAU;g34^=!Aw3kC*e1g7f>2I~rDD+(6>#htp8Ff@goyVS6ReHt~?^|XX6 z3PuByd-SFKJ@8dA1^JyYm9^9`wdDAXTqo&646~A95CyH>|6VLIUT({85(RxA-p~{7 z5q@4S;;9jC5`G2E9R+PGi~+e^60lF7J9Ut-)U`w?3MRqr$erWCzrjIP-^SjY7B2u` z-02G|AKl}psA}m5K@KB}2e}rzrRTR?+|LD0|rdt1a*UM?qUxFxG#jBMMft_q9-Pq})1{ zr7Q~O@ZZGT9qJl-S|aSO8fp-B1)~)OYy3ZwBIaHE|B8D0NZU~A%Zi%-&MC_hv&Nd~ z>RQG-$r?I1STYa=0*pC-XAN~Z`}`JFEgd0>g5fAu8T^+PARNZF+pw-2?bXz4?9I-+uglet&CTxqkc}_cmL%0r%Lua0j%lSMR>zA?v!)M-M3p)U4V_ zgYp_3tK%+%po7JI@f$CLxA2cd3D_XoHO6QJZBZJ8U~iEr_>dn84HI+(LxQ8g*J(~) zg6h#)$E|biw{QiCe6a^`+L)q{>{F}0g$K7AykoJu)dqbCq(^-TUPJEyd?_2R8(#CK za1kHsviyWsd{IXV-KnE;4;={P#%_!nK8r$1nYpX?F+iJ3Gb{!pTrw`F*9LfrcC1`A z%bPIR5+R>r*hhmIh>{tVFmqJot#dCq?fR7EmvaX8{mPTFY{wCtr~zKTV+&R7z~JU=!a zhm4c)ikezLze@9JesQw&yv;+Rslq)2zNtK;(0$Z4P=Y?>7!*$^sWNU31w#Ysy6e`O zJU_v6ZnB~~{zz`kPPvNUo`vIn$WWM3kYDK83Kyz&rJBFi7F7d>6VIx-VcwfF8i;6l zCXVpuE+<-Y3|>hrmOcCghyVEZH(^Y3Tvu~wHgW|kP^I(Eo?uFoPP=oGA!zQ}fTJjz z4jpGH(I4U$iW{G5IZ$=;3*=dxNP=Dr2rngpG##8Y^#9m0t(!PQ-Jt|cgjF*qq6+RLy19`N5|Bb2ZR_iE=l<|kB5-4>)X&9McJP3@h^#Iu1l|g=K<$qGR>X{;%B4d z=Sch+(RU-vmVIjM=7=(iB;LwYkpj*%jnOhp=H^K0Wi@kgPG4s589`A)HR?V^k$l(p zp2|pDjh|=&GsVsn@>rOH!}L!lHhn|hmF9GwJ3iGXwwVxrePRH3iSg;)lDWc2;$CNw zxt86GVMw4Ub*n`g-O`tjBM9a+&0Qx~ZEHqI@F<~9`JD8kOUH{j!NiVgH&=PrWwU9B zU)3NVp-DvAJp>(|%vtby99YQ_H?>8Kq_7H{nU4^ghJg-1(X$((4j&t50NY0swh}R< zUpH#e8c^a{c`qCm#W4%WNj1CHu`c>hzvDbP4CIvU7c7aiQ@uDFQpA}I%A!4rR4+eN z_7)fGj1v(=g?gu-nXAVxJ!gj?yHqE%!aBaAV<10L?K!TH5r_tXO2&DGqqcKY5uF@~ui+}D8Gkf`#P5#~; z!?zd9`4hx<`3BVyd|S)O8xni@hV@gkkH`5F?uTH5^E(pE`4jki@syFei>B7l?fOp8jrFVpXOR%~12zR|;>9xBl zo2Q1Kee)+mPmKCPVu*r479ZHUA&4pRbwHSUL`hZ9bvenWR+;tKr7#Jp2;df>!Z%U+Ap5cv$CwL~S!vayQB34^eD^qR zYTK@L@Jrhij-1=*@eM}{zn3xAp}N@iRcBG0)ZTHS=S6 z&y-o=s)_FOVt}efkuSPukpVi%yS^&@fIcfKBa+Zmy`pJB826UJBLIyM9+Pst8LAm* zR_!pSTJ(8HWeBURc@ONxjQArxsqm6PlGziewU5dYh^ZR`Q)iNf&UjRfsjOL``r&z% zKol3`hmCD<+9Eq*%>-!8M4oxY7$OPWt5-K}`x$LcvGKc;XU*^aGL&qqmt6kPvCXDZ6yenWM+l zT3hPm=7_2`?JQu;d<|Q~j`a3;Nm6>-2MmB4R2?TJW%&;sS3SY3?=aIvFjDknj)AZr& z8zWk*sqXEhH!trcBivoq-iqW&9C+R-8e7fwk#uF_V#igJY6&7kgYh%Rcys*pX+V~+ z`E$YYMRPV(m-|$`l?Y<;vOFKB0$uTNrsEZy=ZoKrm|&mSfT`qLNNv#Up&;Jdh1hGH z-RFQ?eCZEyYJ1Yug)Z}*#?fMIYXfIXK1k}RIMs8J4Nb)W)?c=60 z2`|~0cF#1d={Ku%A&(Mg=4&Cr0=1%;{Ti$J1^m+AC%l)?afI`CM}+JC{lO>Uj<#0~ z(uI};FEq-RMk@z+1}_I;K#+$A>rc*<5kR&1#>(}M8iUJ@6cT%!nEvOkA3J@c&iL+A zQr$qdMwy+7fwglINfOdSMBU8;?Ex2A%}r?#DY&|I6!W))ksx_?TzLI0JB(zuyygny z?I5~Nb@U-=PZ9P7_Q-7!jQ*wh^8T%1ds!_AuW(FPBo$M=ezogG1Mg`YDg*CrTdD%v z#zNWjnLqyA5K}(p+Sg@OG+X~4XYU;3Nz=6rw%t8#W7@WD+s3bL+qUhVwr$(iv~63{ z+IgOLBQ|2=yYKz>i>R!VnSWGNoy^GVI(bf=j3GV&C#AK7eKFJmz8-k=(5h}BP_}Gd z--R-@K2qJ4$x>Uk^kQ&=Qhot&<>4Z6o?3H&ST=W?BN(WXwJ+iJz0@r-;o}en}V^mf| zVG{I9KWDw7v!F*NWz9^W*GRldA-cmhVo|{FP-YEWO)|CDO#QO&=Ox9TSWX9)Ok`c+ z3Dn$BD7%c0=)m)IG5{0b3K_whQ1YVeFu2!oN%Z7AW|_Pq)Rs1j*)^Bkq*ecAn#lD= zwPY`^dvJ}A|4Q`KHy0podUcDKJ!%Y&Um*UY5ID{N20x{DuMv@eJW{^SjCUN_FOq_9 zn#(J+{GG<@J*f*n??I_F=4Pv!PKXj?(NQlnFiMEBwC=Gp+=5zO&(lctXuua0Etrn`-7bbYlv=^gxK3XFury3F}c=SOk z-cgPE-@-nlYz4iNoex?IkLS*jB{j4B1VT_xzc7_{`Zy^Qq3-_Pd(P{zerdWc8e0z6 z*hU`FFy_K}klffB;10q za_HU!9`*g>KLosBR%QYb76?cZ`~QG|o0IGdp$?)eb3-bO5`Y}7-R_gP$9m6W&`%bZ}UyA3aK@li$OB)_}+6nU%0Gl zoo0Ft;`ra*=z($jodB1CSObXYLx)3$!y1O@!>E5rMg1^MSbd;jx}KzrW;Ah$;jS3w zhQV_|WM%-Thy77L-F8KK7KANUTeTp)NwuhYQ0i|)3SCDk?7(8q&blGa!emFC70Pjy zbv+&Lt}`99lgJ#mv~&*Y0A@Afqn?FmO=;~Y*R_e#Y)?*oqaoH%g67EDGp@W{b>j~wpuARzc`q5SLbW7QWIoKRd=y_Mm5QQ>2Eo^MmgI}^x zcu9{MVkU}gtiIdW>fh`tp-+d|4OZ(%nN;j}y(7$HD9Vgm#WKs3y3Up@R^0boNRxC~ zm8K0dHgcv{ncP0KxvZ0K6-FmXAr+dM4NlF6qGXO~UQ~Jo5IZi3%htA5CV18xVl>wV zHsj?O52cZEvax9lb9IduFrL$_l{~;fdA!-J)YG6HMuW)WwWV>g59olI*mne+{kVLN_p~RR-joMS6hR@+x z!*v1NNVua(NIb#iP;!xy2hLz-_F97i;(QQ2X#K5sIZ(L+Zp5$nxv{%gce$b}gZbYl zmX7kth@y=;5_%dMEsm({>+poi&N45!SW_CqktCJMSe59O~xKBg8=KQJvhxK2Xm%@Q;3s8N6xcg-QxC6a=qv&U+ zOIf(u{xnR@AV0hU`R?`EAy6nM(Ex0lU@wJz(s5bk?X&`h?E2wedAK!uAP_@i!QTXeFB3}M7u?Ey+4F0tW z#ga7-KfA+FJE6two6$QZM-Zr8lf2oh+T<&`#5PHIgKh|{%RRyvk@*vDVGsBKo8-}w z6EA`BB?o@}2ei!j$5UiIi#kUXZ+t>MqwX-VPW#bbdz>L5}v0dTd>}4`CX9$QAfi2hXwu0`KuSWEGfuMq#8dj;W`Z zX4M#N=bXTJH11v!6|Kz<^4S&@O({RpxMjQ<{3wTkrJQBVv!Oa7`|G7+EPb8C2>k^6 z*E&M(EvNnUSPqP2ZO(a$M~2TCytS&G3tG7$$v_snN*CWU4#9%HCp_^T z;la@gBO)HmT{%71(j+9%~l1*jDks7R~TI-B_Q`h+p7h~RShbuq6`j+ljP=+#Zx4Q0|AQ;~f zQ)s}0?-Qu&H7B9A2QWr?GDTCCM8oj}ecjbL@21<=uW#PR_giXfKMsb}RkT|I(?zdb zIsX~-+$_8RNgo0JS9}PTt_WdB$VWAyc<6ppp}Gu)*Q%QCr!xX^y#Pj64DQkGJx5?t z#XwrHSCrYoyn0Z-y$AiZ*Y6SSTQ{9jyM2PXTlv*jb?MwinhVBMtix6>kcUn0vjS?` zaW#8rebvP3T_*k&zJe3`5ahS^5wz!X&Kh6%Y5CY8{ZjHko1kx$Xsp-FfUEUhhoQce zWkT=3o8J5`yRZLlr+NmwW!^#n0qG+A8^F^33&4t5+X0+q0nX+UCf4@ zh@;=IO(E#uNOuROJW%+T{e@x(WHBUVknk@yTYZVe!wsSbpze$0cl+Z*K|gktUr9zR z_C&%R5FYOGGW5wFo*wR?ccC`e;8>=f*_j&aEC5mcJPG+rrEqVim7@wAQMTa1#ni$M z8PVQIJNEhsN*7|l>b&@qO}SCvyzwZZYV6@+*Se1s1Uj@S=~50i&)^ohk!?M@p7wN0 z8bhOSBf$(!mMq+QmBLVY@@2-!>xCi~oN7u#B<^r($TOu^7Zeeod*e-I^a?-1yeB3Z z0F0O456GdG9Y<97&s)@f6d*CWsHdXkNBv5;Z$zqeqGIzL?vP%EBw@%N%qX&}hm(!K z9LWJ4)EYVTt<{1_&|y!(cfA?9DBVoalutoNWO13XXQJ7RLWLUN$k@2m=g= zB4%$S!7vfhK;U!~ivc1bU`lQN4pqAptGnrl4lFPTeffycNT}horsAEM>~SYo2nL~j zVK;^1P{?P;6L_x)h%ssdxU1tjs^i%c#`%XwHjXXTEIJN&+J%N>(XL9|EoE+J19vY; z8ory<35^ig$bS+zwF%SE{28ogaI9d^q`751`k|BfoB4mq2Qri~(;RRCE_A6v9-xBJ z&+`%TW8|*ps++)X!2hEj@CnDsy1(!K@UOeIZ2!O1Bk1I0;_UQKgi%df9Y+=Q3!xpC zNv3!ce!U>&cd%X{Y6ZLoEb#hj{2WOjk`=Fv+`ybM86KJc7m8m%t&OfyDxL11{h;2l zqo9si5>yJwD9xnCm+UpKJUe}}uaA>bePEri!u~icc!C4;QF7c?H10|O0`%~-)h$49 zkm%aHEy@_m=T&8pw}>N^V*wWnek&asfb}9gWWwo)>j9CgIYZ8Tmc4ITW1+1L=ef{O zlvEa8O)J@&kX#oTYlCiNBEG~XV?@nn)}gAXjT&mjy%WubR*DtKTw>DEis_wj3KPT+ zz#tWNn`kob(9=zm)2mW9FJ(8uDQF%{i5_OrSk(P<=Vm2H2x zAD@ivwm~mumWI-Gu`i?98X*qVDM|q!wjRE3lIy)4t)o@fvZcvCt>MPAIibv%Gtp}9 z$a2D?tbhY8m#wEYEWwk;w@p&f-=bQRQ*EoEpN6&D+KiZE=8^)KP0V5I7rqDB+(jWoFPu z3P}|on2uBC%y6kyV)Ybj9KB~0e|Z|6W;0SvYt1>jZZRGfKr(Mq1*|M-at&aT1#$KC zVGpjQ3AUGta6#OXjfNtiWWf!JPc3c9 zSV*dsM`GZPmc+l{@#(Cx7OAhnrOB-TdzhmPsA4m!H|6SSqP7xVO{751R3Fd zX*ex9;U#&)=dhSg`Vi%EN&GGS_#-|9X(2{IVQkqH6BT1P9V3%fhs+C)q&=|GcuQOn zT=Bbif@73p6cQH&cZHLz+bZ$^0&_IXFKF7q`w?U9Y-k!oGlRtHeC3F60!M{^Vj;+eb3V-wc(cF~^o1eQBiGg2N96!sjE>(r_<^G5oE6;We!ep0N#k7= z>)rDL=3tH5jUYXwYh=hXILP-0T9l}TDbxy*exBAs>5<5fYD~eI22SiS9GPABkw5$AtioI@TV*iiQQuaoOJN|wJ{l7XcDgXDB_D}J#%DPr4f~dTUTi2P+R+fouz8k9} z-s?VyB2}pQKVixPp?z*`8nD=uleSno%l8}o3j750|NQ!e>W%30L>>|DHX@q8B}579 zbTgCfbi6+=kJg7*Wdg|042a0oI%mVSEVU>!Ibak1a=>&O0c;)}v_u;sg+m8D zko;=nJX9nhf^b?o;uGB9_Kxcm#(%wT+>X87cL^P!h}qTKfEB;CeHH6*HR43|QtEPq z1mGeuI3`6dAEbzw^Tr26vtveU`DA(8h5GczTTKe^U|?P20PQTwWhdxCKRyJlCCMS(ZaGi*@*Tm&XyI zA>C=$AQy0iDulP$$0Qs9`6)^9UcgDvJVr6YhS1Mvxf%i{k~Ow?PT~P98p*;-gGc=pobf|E@^vUXupP9GLKR!@;HG%{8~m|ap-cFh&!M(RnZ!;%+s+^hLNsU~b|# z2zX`3GE)xsVVg{K^7A?Wm8wuVXO#lqj{^)f>nc8D{^tEiJv+j}FadqG`MgG^|H&wq z%dxZ}_m{vi{cA?Ke;{%H?^*lr9_1WWT_+S1RNrmmB-xgYg<2KMpq8d&l423*s#O8e zy8Pv)GQpmYO){&7`0LP1^nSkq`rDZRENu}p+}gHV_E&-~H?c<#_N=BRz?6G}&#c#T z_6g64)x-Nko*%e9ge-iaMwAg4LD{Q7WGT*{2BVU42`49EGa&H}F z2i-v+Tl1z4%3^IFj>uUR@0xwsfK#<{>m^xWMMTUsEtf8(!Mhsln1UnE5*s#EZ%PJl z4flRZXiMfogLnyMSOEmnvI7F|wM;dj*5%I?S1=U64}4m6{};6=~)dp*AX?xJIgruu{{%Xk4eRD3VnK z7sA`BD@|3X0(u3gmX*~O^sAe5fjXtH-X%KpsJ(w2T1)7d<+i|K`&+Njk&5)3O!*MIPC#w!Zvwun$tf% z3l?tecw@Y8i&yp@VjXc`hz*J>-=V#)nRA&ex~S8pau6jMD>|Qbv>LB`@-9lvxHej& z(~KJ(4r6iAYM!MJCC7q|q^q~$%_R&m;^_yKWZmDvkU1YR6cU1>B!$>-_m$2b+C~++?YwUdA1YJF;G(X*Wi3-hy{YvY{s7v>i%aqRn;{X`=qJ zA6vYzWhPUNbsg%l^Wc_?in`Go-E#G!d^nYgx4}%a0bde~5MlWeq9gUxM6t!++F^8a zwl=uk!{BJmIP908_(dOrlqf7gNkTrw&*_fnKAom7p3P*IGJCM3QxdkDF~$=xcFGN9 z;w$)ij6%U3&hxBW@`n2h3o!^J2Iw<=Pu7leAI#LF!Ak=CmuBw0F3AJ25WllPL64(- zo>S35wiubcUWto@mRZTTX79634& z(Kt!l&Ts;etN^L+?)6cSruYwMFigLOX{y;2=(YB|U$o?)07Z-bXIjw_V)=Yno}r@T zKj6NDeM-(eo;3NF`(wfi-jARBe&OO^O5PxiG%y@^4lN$RF>(#6jn<1GuwJ<)?S07l zhMFYps>m9)b2QDKIhc#VM^idCv_r}GE3lxl$1kq%3is}+6YQE7Td+g zfwj>5;!03xH?m=`&qTzni95w^!De*K3EqMIF>hw+Sv$jXiA*&-!ryI7{0ZKX0Prf2 zORU(j!fMaU+T5up4p`V!6%)nxG57GQkukT#54prqET#mHV@#{?-#3j3k$!e385f5c zu=gsO@c=Eev;SwGM znwR~r{2#@W{EtuoI09_`$(+tn(YC`;Mf{4j!|AlOwNY{`Zi=v~&A({NpDQVv6Tl8y zOeF~}(P5uKrhoDr^js|YfXaPEktm}oP%8?IcNYQ~xMQ*2I31my30K4WWCAJ043wCi2ms5Equ!ZoCy zLj{(ouy5L%40P*tS6aXfe+Rbfm6Rh`%{XI3Be#=Bdb&*4PqW%=&9hIKmY3Xtkjhly z*w)$n%&}-^WHPG70?`$1wA9&XQiC^>)UTeCv74tmOEKAE^thB4+icK8kwAa)dM@!2 zc5{vj3$a|bH>L8dHl4fmt~b_RhkOn@PCd>%gM+sbc$(s=~A{-r2oJsM4@;w1O!s-Cez9` z134MKb_RylT{aRtfP1$rl22kA)GMWAe{WJ3Ub^*k%0apGFig#hNLHv`3=b+H{1=A@trZU z&t;B*I~=Ga9FuWAGWX>cPlOeXv|9|ElKNdP*K7s;Hflja8w|Bkqf6uMQ_3J=8Zcs9{n^7?j?$R zm)Lc3-a}fxvZ|P}ORU+MJY1}QL7hSV@nFH1CyG2^9JZ9dXY<%1{K~!OgG)GIOLR#* zAoxKDE$>+lGC+7J5gflAoIJSXk@v@F9gj52QnotwR)?Uu&`Nxm(i!nAF{P zoL_0WD2-Pk31+FtoAq+OmQ=O$^;<}U%6nb?m9*Mk{lSJ(1+svL8*xLGIYkSU@VOGr znNt+=fGDsk+)!w!=$tc-Q6cAt$_eQ5n}Ex<`t-JkrMHUbY&^!rbzfuKZk|Yz4P?PN zTfq+y2S&~}$-$%OeIgX)TU5HYer1v2rbsU5%Gj||*+E{DNDR8}6AgCKOxpws?>Uh) zZ4wwfmiY&FfW9t%3)kv)hmJ+Q5U$Fwj4~Q+MT7cqlx&c;ilN=D^k0c;e-#IG6e^F!kUr~eq|Hs<(KaKwlb6U1jYDfT;mwhDK z1Ip?I63X%bRXd{iCs>1ZzBNBtkc^D!W(El#-}Jzb)i|we5vX;KC?|xpht(_oULYEW zDf^*#WTis&0Y2&XpL$TG7$I{htQGq~iM8Gbdt|fp?7@fIk1fe=6$!yfc`}6CvOBuV zhKO)jdmSvaJDES8M!0XXsMg2NUT=ohcr+Yr0oqX#=Lj?mJC`xb$oT~Z{LKBnZqD13|azyt27Xad+RO$W`6ioiF0Pqiff&Y~f z|LGrObuV{q6RhtXmT}32#rF>6b?ZKeBFe%I%x3NUa9Um1Er^ZNmcTI!&-Gkd*$xss zoL^AU*h74w4LEKhi!g5!5WV?F+oCyU)wN!<5rg5m!8`~O62Ro+yMc4Q z00`YCH^qZDiD3y+=DQs~jF|qk#lM5?ej)M&$iut~P^#?;V)2rY^A#T~f;905&+axd z{mGW`(;FKk^ahR_yAuz=MZ8asr-ykCGR&97?`oBg?-8eIq0O|;cn;wpSHOwD?U&CjX^ZIV4x zbDA2ju(1oeO&u9>8X&U{%1P21r}6H$a%`+F7k95D;FVe<^|&pn2?+2Ivw`PZI@m>< z-=~^rySUG$V^>+q9cn5Rk$A5q77_VMh_HpQs4v=rZhOqt(@u{WTVe8pKy+NYPh%=r z^+y1pgKsH`=ES_XERc|RXe^I%Yc(pD#1R4T>*MhizngDt&$kjS_7zLfW~0+X=xb6KTsp1mBcdU7 z0P}GCg90b6(esKrh}L6qnqDdKqQd?XMryg*HYV7UiHt{Ap@QRy>M?nag0*rFygGAb z$tE_@}{rPi89@EnzZR+9@2oS#u6nEfOHB z#qzuQ3{n*|g8F^#61Ow+geEG-wuH zH}4J>LQory$-WG8##@>m%Uv5LOSF4dZ{5L)x21Cbzs3faHKRS)EY>So4ZA%O8}=%R zF%BK$EiI4T?uL7RpPPL~z_NzXZm0Wv-^;BJD1VXf4bx{arzM!J-fGfb!A-A@Y*-kXv_z&I|^?7m^UE6HU& z!Wq2yujMZbtCO~r@nh%1?yl()?3yM3 zIb`(~PAFG)iAGn(3&+TGtpFw4;U;D*xXF^BU#DP>yHIW;2gWC(BcAEmKc}wI zizKk!%yc*!Xt9Fu?KvaDfx1^QlZSBAmGAWVrXsL>^GLCZEtGbK^3lcAwP-^sS`u6N zh3?2|C5k$=cB#%ZSMQQiM2k{FxejjT{0*WM=qXxgDJ3w|NNrroSS^$$$Mro$?!eh2 zZC$+rvT{L}S6)Zob-hrH$*43jWT14M_&~nxsZu-#2bug{e!}tVMc~ zFa*IT2|U^Qvbp63aArZUg?BUBH~2lkz+dcO=Xt?B)%VjA>^MXKyHz)+0M;)J9&Z6%A3!b0>fXKZpLEx(n^ynoy_cY4b+!#=E;Ep_|O6gb>9FV#ufG1#M55dLnL&bFlgQg@H z7b=t6hg40+rdu;Ho6B!`z=Wf#r%Xg71!r>P!(r3$x(`<{R&|pw^#5K5>ptZJa}}I# zayrKo)^16&^h}i{G%lN-NH*&l@y=g7T{|Hww{$vuI94Fb%=L6+YCDW|GGsr#5htC- zhdpyEq==dq{$Sr2cpd6x<44ID28fKsq|je-N3XMf)%ErOkYR5E_%C_uS`Vu~?YgV* z#<(v@AoMKTlLc!_YH?1(Ngw-|{upqcXjAZ}wJmD|3$7m>(UA-BUDE~)ufDc#M+4X$ zv>(=6D^4vfnlKi_CbmRA_4D@sSysXOD+G^!?wkw{o7D^BfhR}uE8gSLopO>-U&xJr+aYrPz z$C9rqsnJZKv7o;e*87ga92zEblyFGg&QrTd*S}h|E`AWNZwoB!$B0@|b~;Rye>zjg z8_JBH_T-caXVUTvigC|OY!6?qPV=k^v+ED9RZ8vzWv$X&k2JeOtuXSe99Uc)z5Bt@ z@A-Id?uq1f>V~~aWylz z)e!NIC{oJ1D)q)({&q`}cTy3vA>X2bzqON~BaS-`@=KPGyNx5N4E%;Erd~;O3pz4$#)!^%qjlt7RST1k7Z?W;h ze(qs~-B6!K_H-ffVRyz|=pC=b&`qte2)KfaV)1#%<&}>Hfh+X$sLX}jGvpY5Pjd*f ziqWgp6<0q(s#<9e`>fQHPPhfW+~pyD5d4SU@+9jQIsLT{U@`uU-tzvh>8(A$+1!c# zKUsF{oGhG86#h2@}aI47{cF2Ksr;69*A!EV0zM>!9IAo@jglIZ{+NgTz>^v z5zfj_jPxFuiwMToEEMy$7UfcO7itmcb=L1tPVt_D$k*=zK0FH@89#M5oS(= zi;=MKXiSGM?#37lPFFKFOn?hk75x%byN{T_43wr;17CD|2V2pS#vvn`Bq=Xdv^Mp& z#a{-URV7N3s&TLP7!hWcmMw8y;rQwM-HBpzvXc=KbCg0=((IQ^AUmuCrXjo~9JY6u z7lfG^f+Qb||FE&AGUD90y)#`GhWrT&bw*aU5QP9cE2>r{eyTj8i=g5)Yam|($f-M) z2=HfkrRjsnl-qG!6$&bl*MjC=75V&cuB4`l#tzoMwg~e7N$ATz4A?)KByY+%CYZkH zLLhrVP&V}f+R`?xU<~UOs^!&~QqnlTkioR$I%HewY#O4nLRz+8e0H9CX0gO`kZZ9< zcW|Hh9dVyL+*abH(&t?rJ6?sBOL*7x4c*+Igx&API zDr~Qob~5WAXs7NK-Fw!Xqq0`vZgR5N`*`jfm>POp-^>J+*FUbdLD*;rD^IjU*4VZu zdHT+1P;s&HaMX9I$t)n!88>+53a!V7jtH5KQ zvN?@gi09=nHVzlzK3avNiaD<^##x!`QymZ}RG6ae8-_id$zm5 z$byI&{G+qIRhpGr32S!#(FX|!vOXDXYUifU$Q{v{ro7o!mHgdAqrc@u zo9FXsI3LbXe@q_Z>Y4_sOLQPFfyz@$ZdxzUX5EDq%XpiSyOie>t4?yiF`0(@ZKP@1BrG-eu;D1IN6O1poQc6#`>9Zfpjcasd%2YNacAfO8jB~fS9=zkhUyO4gJ<$5}% zQN0-te9W!lxfvHK{9v>5W&PIF+CE7nGnu*5s__-1l?Ni8e}Hca))J5X+Eyii1T9Afao?Sjf5AVdOc)lrS^%!n+yI9Sz&d^T^R9U{!Uc2KBYnUV_fQf6!yvwO-FtwC z0HkY;?y`gvJG7phf%>dEfa|>Wt)PnS(#F+`VFClnfW&*iSq9Qh5Q?h!%H^Im&!-!i9lV*=4vYrapNdGJ9AhiPbWv^rRelQt* zS+|?NP!CAVSAhJW-n6OK4A|=36Qho9UVXp=MCSt74G~)=0THDrV0>PPzLk-eD+9NH zW)C!#(@k>m0m@nOq;LQ|I|yF}@y7Fqs?{xrEJXIRlE_qQumUe>RUWe06HVrCxVJXE zU(Z%p-zp)ch9=dF@2Ky6m%j})(Mo#CBXZG&iS(R!l{dB<Upz+1Tah3s>*KF(8UEEe%%S41%^5m3ud$vb_$+TC+Wav6_M z&bUOHl3`*?9|0O*YdaaNtW|3D+XA)fq^wg7YGP`>-K={VR_oso|A9JE?hy=c2p}NT ze+|j~hp?9aRGj{M#udrGo;RobEx)LM_}$g4-KqqU8gMVG0^#~=2|C;~SU`hRIu)R# z(eN5X*=#7eGF&ZupifGSR5*Xz=$CxAZN#WeVq81E)#>Jz_rB@1vg6nD4N@0*LOC{8 zt})OM2n`;mT2wy}k}(u6C6^@5BWW0Y;f;({lSidtqVR*7yG3`sQbRPf4UaQn^jFHj zoYggiT;)+SCi;YJp{=jE_cMP+0)cyJ&x@k9)-mvS)fg2+k-lb%VR^$#iTTwv1}(&q zrSOUO*z(?v+*&k0ag3 zpMoYAA7#Lj%-Bk(hvO@bc7=}C?@6dst{AHbCR zAdx+=kpJ|F%! zRQWQfL{#@LSBw7Fq;LOlwDbQniT{a4Ye2i}sJndCnzG1a^H#SMnb#k=r-(Zmb4M5% zjwVzk&~PTwkgUn+^!eiRN*_!WOhu!T%T6K$%q1d&a3Dh2q^6dX=V+lowp#fAmQz-1 zRiKy?3P?qlSX&p80Qqjev0%aH<#zn^;d<%vn&o`iY>dnEJ4%`bYSX%c-zUqa9|ro$ zM-}`i44{YHOUwD$L$sR*=B?Vd0{78arH^q754?kVOAqp;+Sdx^t=-oP{Tl0k1M{kf z_#OL=c==BB!5{QW{~=IEFtHs6)}=5p`;C5U1k8_gtE_lo2H}f8B$ds6%P&|p@(uI! zJw8JZ_Q4d6aYw!8CD}JA*cGS`iI$A4fU3fGbP_fYDMm|vO1JGgNhL%FNgfE zOevMds!1>!c9p1nH44-?)2irdgJ*JN*9vaa;zd`jzlO@Rh z&|WvOZs0A_C(O~-xCoaKDbst^YSUHWNv<-~kgkIj1kt$rsLeQc;V2i=d^#^mv)zhB zhf%bBGuZ|i+(%d0ZT6X|UnY8v8zXQicpujRH1-94Wby zYi4AHUOukmHXYd3_^LBcz5?El4J>MGoI111Rr8_9H6k`^6Pl3BfWqI=%SesLD0p57 z1kJ=5Hd?MMD6Q_ggZ?R)@}z|acqo)smQ2`^ZXDu872z-BOy%Z8crnrO^7+1mcj3f! zEIo@-8wjc%-b1mP8K=?jy67uZG2Hs9JhIUn<@M&9C93;N`Bd`50mg=b_o+jpO6c@6 zA{I}%!7k|^-X8(5@&k*M(rWeVb!f5*A84?uOXSHG$J&yBoV3XiR#9<$LM-*6;jq|s z;zj7{Y2S?wmmxmG(o2!x-+HMp!O1>F2Z2WQv*yl30Fx5sBD^-#t7mD*g%Bz04MM_Z z-pp`QR|@Br=Z763H~RaWix(=xbzvn3VP|CR;sq3v1lNs9if^v$pBdv6 z!VO0YR8f&g>Sw!Zz`dvAWuRYXssmi3 z*|KFuJXxffiousncxF-AbXz!jNRP;}2SO(&a5 zM&yjSI!t(vb&X(@!qL+s7C6><#fAV=b^;+x<^6lG9BAwGC-~b;wx!JFBs*0I%q`}A z+Ui-!!%Xv=9k*v&YK9|HR`8%mFF+A5 z9UN}(TF_Pla@8{qwu^}%&o+x{(SMd&=&FpAVo=G!Qx5&dCgLSy<;aw$_+4)z2G&n5 zrBtHHN|lj1*{K3_srI~56f)BL!(^IajL5_oM{szfA|o7D$zq|>5gDK@g41Mvpg6!~ zxp(`sL#t7x{&$=8x?)(bf-oE`#* z$b6(`HzAlj$+BLL$ZA=#uQv|59wna?-Hts4`CG?lYeR?51-<1;Qbli{mr%WOub zM^MCKpK~eOsMazrJ(aANv}Bz_ysZK%a8yepz!GVcw%g4r=y=S_a>%v|J7m$&#XnNo ziXdOoae>^hDrQwa-D}HTopAgDltVklRX{E0hB|NAP~2cS0+LFwmAquUwfMdfW}O15 z;ev7&=P~tdD*#VgBePF|(dm|``nx)6m}W4Q=!#?TqE2>i-@{x%jyDP`r{>=^`S@z1 ztWk;kt`w#HZ_cqep6H1@dMB!k*4IxXiE5^E6Ee?h^^}x!BhCXhiNn>O-AoQFp2<*K z@=sN$iw|M+#Dn4v6B*JVVOts9hHYjPoBT}|25qHY@@*$X7St&U*#0wpDYXk{EFMA9 z*URf%bd8OSxc4a&F0}1t)0>0MsMJU?7l2=dRtOu?<5glY31_H{L`fTI&?XGnQRBvA zoF?qD4tvhCofld_{KiAtI*&;X@?>=G z)TN}5wT^wS6VL9D(k1c7=)QsjMMf&YZ+G(W{4^Ym3;B!=tuHCTj9mzR$7msIB}=ds zz8L6nq5Ol)#@Bk%tRL&xhJ2sL(1&I|gbydY*Ze^>0QJ2J$~vQ0yyA1+v7>9}x}v@L z` z$VKd6(VUPkXahPSY9wBS7e^XJD>S>=N>iE3@Kea#-qs?pB7&Apj|UL zB-0EKnrW(tuf-M>sZi~Qo|0c;lGIoR!@BaPT`f$g^wj|dEzDGrm|*#%@^Ca!0aTP0 zo)yegks7jDb7{IDSEKI)1%YsCABHZJTEyMzwRLEX&Y|@}zObLyWj@oxIUN`A#As8PTKv%E~d*zJB}WFZ82 z)emg&W>CXs=%0*#AjltA2ptROPTf2j*U%Z`zZmyyB(2dH=RO+yrja{&5IRjIy=}FP z26>!CfE-iGAHVFr@T6zKWH9GW^~Sw)OmXWTI|BG%2fr~t_!M{2$PYDg;~mw5fwxccqZP}hbaN**PsVkW#y-gYKI6bw35|)JU$M`-74iYnxr4$Rk-$;3>CEmc zhsL0nJx&*`j$jnD=8tkws`Un!I)NNujav<`dsHxOwvhJW6h?3>k?DK?SE>_1O^I`R z`%FUCx~+IOcz+S$y z_4xahe2`c6C979pPDZlzw99UcmP!*sKI140B}RLGR77Xx8QgMI@C|a&L=UR4KKh?} zz}TFuvdI;Gvan4R(5kRS9VdMxC(+b2>}+9F=#0iSxRJ>h9_ZytU1q-U_c^N!>}H9Y ze<5XAIQvhdY9{ZpP~EX$-%8S$<(covcetL{;rUJX+|#^|m9Db$C7GQ9UIec+V)%=p zlYPaYy-8i@^MhvdC3Ed}92+kNq#}wH79CtAs=ee=u(yO}7Sc}4*G?1yP9AD1rq*f_ zpJUq9?;>FeKJdAq)mm}KaE}plneRqj5$_?}aNZd` ze_&!BRG0Rx;HqjLrgz4ak1}~wKPW27et%7OWEO4h+PPXX6IbvqJ+st2C6q*H)(y+j zpNw#AP4*7s^vc%yLQ3E1vg~5VNi*jfeo(~lPD4$EI76o~w==W_SS7eBps%2j z-Tx@xio4I&%TCgZ?p=}#`(O{Ern(SYs!JME=Zi!)w3%ICS+6h z#jL=TofvCW=^@B9$vs{uj5sZILK!CBL~eCaQ?;|Ju*^7w=SG+Yuybk1lHbuGbcx?^ zHf;&IA++R)w&ON{xh7fjVWdu$jWrx8lTD~{o@c~YS0Ll4qPdoxcPX4Xt3EP9@D*+h zimB|`rC-UFB@1=Spa+%L9`*+6r0DO~*O3d^#1*W5yKj^t@*q@RX@WtPPsbFz3a<(~ zhjkqj25;a!ZK>DY+qk;A<{C-O0wxKByF0J0O_2Eb($4=c(%vyhlx^7>ZQHhO+jjSA z+qP}nwyo9nYTLGL8*iU;?)&%ceeU~SL`6kZ)vwCPIWlw3kvTlZYN?A2$;v&~y8%%v z5v9$S(Uzj)HAAl~IXzXN#g?Ep$G_4wGrS&=zW3j~_%wNvM?EM4X6A2V!zvLNbo*_A z^R)Ial`%*?h{fb_)UsJ{!U5ZE{Iq=28W-LjEkU4t(PkDxTXMRm(#f zCMDX0AZT?R%T|orYGGwI#*?SH~awR`>I$LH0QGp zk|Uzps-_lL6K#cERpTfr?WKDiq1aebFcrJK&rz(QmK%;A$Fn}B{H(wmh(=mGkhnXW zq#OH=lV5NF@H}oT>SZadHWuL6Fi(nICDgc~#uHbNq1GFz`+Ma(sM~ZmT)~GMwL>{@ zFV?8?y~Rjl`qomEU8w^;dJ5`MqFU8S$HDB0#?n$krsTWJ2ef?Y@u%F^qm6?;TA&Yp z6$-qEMsk@qk`oR-TGw_FyS-Ny>WumXM*3i&z$PZD4obpH{uO~(=U`aoSn9$7Sh(`6 z!g6<+aA_aMMb`!OKjI>524GW;AZggOep`_YZ}V`c*j$*UMW&J-|IJY_qYgD#KP^==asloFHh6LH^d@1X?U z7jd|N*wByj)~F*BUI5(%`UDL37^eCR5Pif#eFTtwU{Wy>#YQB)+2d-FIPszM_(QXh zpIre@&yOOfiapXIYf6^*{reben^w|A1PA~?>>rzd{zmfS@7?xa?-WgJjZGZ?3qvs_ zzE2K_5h3&kaWe%Kh35?bhlTKY7ucO(ECe^6@dI=(?X(jLCs%N6BXJA!^Fg@}2}^BlS@5)Z$dj|<(d1CebP?P@B7CGULV%YM0*$&lPQ%{I|XsLnWC`mI!+l<0~5&+ zNtNUfGd0sIl(MC5Y0q{5F+k404PoY*)3x(S{dPd-H$9Gl3g|$gZ!fntbGyn4&hNUf zCyyLLO{R0SmvuOYC^HB4$I7k6+;#fslT2|7?qmjxwsi#bi>zYa9s~8z*0K7^ShQ?f zW|5XlEDx#EP*S|6EY!WryGTh1#n!4#8mE!l6JCaWR+uz{ZM6+l)tFV-k!K#IOZzHQ zs4&5HJB|J|ruGY{!s}>bi?_UfWwUW?7=4OpRc7HXL<+Q!i+ipmma20Qj+S2w)@m1h zf_BBnkY=73A;nU7b$DnV)aLM4*kJ9(tvQveY@e=Eunr2h=z9IIprGbZNNr>F_QRpK zlvPlM3MiZXSi-)^o~WC{xS>}+jSx-L@9*|da!&yrHm%+?y6CzI*vn%)HKtF1Sk3lg zxIiCM@>M<~hXzLi%H06)Ner|QW3b+);hZ`fz7`@XjV8vY+7r&FW1Tx9o+B1${Ul!& zI|n$tWe^_05QM$X*RZRMLhWu4{2H8MhpfZ8V2F~Qa`T{o&xlAL!6+ZmCXtA`ddV?h#!9m1Q4cbWJRW{Qq+G7O z?*E^w|C`V7-!Mh)wA6p2Z1GKcXATdNB;>Xi{IQdEK8n67_0?j7? zzf^-)3}s-vS{LT=)a|kBA8kOGS(sn2zsOmdHS6Dv8#gh0NSZnuYbHx1O5Tw|$W|!f zcT|Hu)}c!@>4=m$~n1X2v9qr_;E_1*>0)pU<21 zNIwTO44GA}A#%!AhLt&SO6E0Nf|rXvyt|J$h#$X|NC^<6ZiP+yp~quW6ewN&M-4!3 zAb&wy{@bq9)awir|FYaaLdf5Y9Q#|l{vA+gLVGJOW&HGvktE63js}s$BM=G%#{)p} z2|(7RgNq0UN)kwk;+WQs^%|2+5+gLD)oAMdb%iXuq1L?ik_uifU%R@^*|^zMZEf+j zZ0WS}J^xy1+1MJdy!_#Qna<%(!X~MCN^_d#jl1#r-f;`{eK|k;n|Tk!|<{)ev`qZy}cEppJFbxGI=4gI5)QHzSPW~k#?_$ z#_HM}oiKJzO`AD(GIp-5*Q3)?J$&%+?qQ+k-8>ALuDW+;txK5GT&tH8o0`{-AEDi( zBHt*sNE5y=F$jW~BXp)uJ|?o@Dp{id=`dcTogzy(G{k(8SfI}HnYT`zp=CNxLrx1v zOB+8?efx~QZKKKi?2^r#5S_bva|YET{P1>kt(d$*xOwfy(bvhOHZc$o3&Ml*bSGb^ z?$MXHnHvD)U1!ILxxBlAILql%*M`6NMOH%`-9vg9HT0Ms=Ej&({9M+eQj8oiBK!`f z$AOU8{F&QQyWEm{b(J%9k4acF;-lUH9WC{uXYS-g4({rRFG9?ySKE2C5kx;tWT zuN=-!UljX19P^>?7J!ChksCuY7>KvSZxMeUcFf4Jjs6^ZB=C-4avKc|9DLqP3;9>x zqfCl$+3g`1Ij#wURlHKX#~}Jf;MmiggkhVhqLgf8r!WTVPXSCKi9oyMX)>VC9)6K9 z7VRw+!}Iet}w?k)5ucJ;7xZc_GBZ@Z*w7ndw$jsicsZ5(;#J2o1|R*3%Uv^|VYQ z;^nu50K5!Y1luB3^suULyHK>Vm*Ffl z8w1By0boW=hH5v`P}klxu&u;t2!DepdaPtSr+Hj_7@>y}rATCngF9@RE3wh!P)c^B z>&F-|q}5cd&Su~rWJum`!P8PYZUz6}qv4p_0kVv|wuNb(EjrJ~+Gh_{@#DDlsSTFs zfa{SAfFI|-FjllF29g(!Mga4rG$uWkbUIZeOUTLR$BJixjJcS`idDCorP??BS(RY8 zGHyGYT;~=6+Va`2A8yu9vDt7{ak3F($I-3eVz+f9*niY^&mC^wX=sZWt!nP{(k}&B!V307|fb_qnEr7aPWF+$lLw zho5&MdotNnjt*c>)y*3_I%-bJ#EwC(Ql(pYrcm>s+g zrHupL^aMSb+^@Wg9}QdD*T`L~oc#dF1RxciFHz7OCQK3Hum&^=H>Pb6X%?g>4L9&1 z8^~n43b#LZ3 zPodDpiGv`5%JP`cOGu;%rZRDuDVfPB2uBI~J;(uriSEgE7NR4X#|q82jES(nV43JO zBKf|$ri~@S;4TNX*dm5siJK6It8iI>7M z#wWlZAWpnior5u&8^x`fFp=(#fky2VnquNvRC6*t{RNVt$e1>6HQ3+X6ciAlm44Oo z9Mb|^krmu<*4`YdX8rm7$zY(4*~%s)98^&%u*>}(Sq9gQkdM|`zUqb-PCKcW?g&bszSn-VPc^RR4%yJxl^zEyIk+fId}RvOXv99+glYZy{Q%RN3QOIJF_>< z82*uuctqBo*xdP7&u^Xu%ra%s1MF8act62|tT(h6J5cB-mj!ODEyBA8G~SxIxcWR_DgK4iH1k*L$7->g@zmD2Ti^I%y$B>f zQZsmnySnj{kNY=PUx5SgH^LbFV{sN=WmSA+!XCP+IO&IA7VtO8n7l%GmTxfrnma;I z-J-ZNTzLMXyGH%d8Aj7@Sq!|=1A!gmQOXk98)lo=CMc28WlSlAKqsS;J* zL+lii1f30sg3D1xD&10MdFYAgvMd|JB6^sH*{->_HP(b+kHRs`PO_~y8OtioC8tHz zPSt%{ZVTRY)PrJ@_<#3PZXg3<8LE<}a2L0o9vkUAWm7jHt3kMvN6DzSxpr%8IibIme28Hspm+ zmBrK+{^?+MM}-8YuIoK+d)&q!c>Uk?SKNX8C|Z`$h5}C%qssh-&*cNk{FcvZ8DC4L zelcrB(~7*6NJ~Z8O1zPvOFR0tMV|`1k#wUDQCK^Xe>RwU>}FoYu1bd8(mxdx3hV~) z7NgB?`c_5`hoRMV1p=oc!Ina2D=9X8Y7xn#_AQXli6 zUx%&$v7t6u;JH`CMOEZYnKyJdqnrwbd$7PXRm-<2RC%@IttrLRl-_tfxwXLdUWV(e z3%g>Q^~qPZHJ5x@Pzzr)=g0Mj-F#Ge&$NiLd7Vh`Lzjw32qGu{i%E%Wg}*&sUc6 zt{d8r))Ply`u57sWw2l?w*zV?PX4kEg~~$3oLbdSG@xXe8knfpTIR{jhBL$av*@~M zgzJ}>>$rQxo9y78MSNp7yKub6TFU5njsUlvOxR_)9LCITEjIc)I$k&|88|(+zzi2; zjtO)Q7t|v**9}Qz0)-@;nVTH(us#r?6#Z0QMw9op@HPWb@VG~40paKY;mH0)JHrD{ zT7%6EzOUL~@Uvca5R)v3*pfa?-!Xcad{h%YF{5Fa(8>@=BMzuVWEuS|9l54jM$=2s z${9(M98#wYsPUwz40Y!wc>$i+1Vt&-< zpILxQ;=#K34~BQD7mgec0CgH+hk=-bru?6JREHF(Q9Pi=FoQtxa)5dkWd8;L^LVBS zA+1M3ag@oyOKP+SU1WMGQf7z;&JjS<(_ssPNTmp%UbEga4F#Xr(u2Zcj^xsj7iXCs zOGb_n<9K2io{Us3s6_g8i6iyC#}zn~1{{hm7^ofB;#(bY<%ubn0yP|?H!rl0spCBj zffr2#3NEoI1wy<6L!C$mv=juM69A?+#CsfoOv3fpA^{F57s_q0 z#4qHOIeudKKNiS6Oj`@6`VU;)35Zh6F$mMgCngMXPYiSW4w{LODRzKVJ7T8ZE5rJx zTtPhXp9x39tqi0N7^9k?<0c56XB@1L*nAeo>us+Xv_>c(H5l2X0QRmci4I&Rpjmyj2huI`sqZ6Bp>tQ57iJp zc^Bz_L{H{FocX@6T&Cv09}Uoq^3Q5$pNY*z;$gmigX6g&=46 zj1_-vfx=-UsQ&;BkN~fydmuB5E4K~MWTqbxTl#D8Jl5eWM64dANp=E+YDdmcnWQB; zcTP$+N_VXPVQ4wnS1r-z!A_9iiP6@E=#H`gbbM6YU1g#xNzpEKt_ccVf&dU!vu|P1 zHnC`%U%U;UpD?j>2()B)NmU`cVsskW)cB}SV6dst9~I1mn_I>c(79$SgENCr7qrb4 z_0Sd- z)vc=HlDbeVr0b0#(}mDl7HSezo^i?7k)QBjtl29GF9NeX5zCS_(_+)HgZ#7IV39Rd z3Yu)P?wsN9YrFXoGSDYg=&We5t3ygx5dD(*J=Ld*7So1e>I9%LRS7UJ%w111Wz$$^ z3q@*TG@b;z3`SD>{-R`zP5-@NzAdNbK5!Jh)CT-+UuY*cL8KiNs!5TuRS9$>k0k|d zL@KH1tWB{3?mOpu;Lts1%)z%?8S)8iuy%(YHR!Ocs7X)?X;@smREbXL(^=?@Zcv^b z^Ns9733f6AcVc%7!eu=0q3-YEkuYa0gA=@OUY2`5lg;tG-?ob(^UQSb1o zpyn4^-0hK$%|u&b3v>)1TQ2>Gp5IP}RMnCt{hpiWQ9ACl=maZIG;9q6V|h3&^Zw(` zJb|OD!ld4)i|wJ7uZ>N?bs<@BIRZQ4SgAW>&aCzoeeGepjPO{yS(+B!Sw+uQ1;DqoQ7pX13GI#u+}%k= z`vrc}Z@uDksh)9Ss&Mh2sh9W}pIx%M)Us{UeSL*TODo!ydpo&XNa+$o;`9&sgdXOV zn%+9Ggk9Zao;wrI^c{|%-NPG>mtX)LwGxs0mg@DYf0;AOrHx&(P3q<{wFg)mF}!hj zyiue)M714{^gfbi{jQ`nQo;7fr%02F1SMOgxOj8ME>LYXO3>QH4{qQb5rpD{rBr}%SI&xhR~w(g=>Sqcm>3J%xu^8+LF$G( zrB;)PP%FCaa?k;tKXUGI(i!@XVtYmJpM@%90004of7%;l|8IMP|Jh&tpCg2S^R!+5 zb|#LJmK~BH$~Tz0y1tiieoM=;v`W5ab+&Y`gM z5?0KxnP2kVWU{j5N4|B#$z%rSgU=2p^TWeR+6{o|9fyAg0m>%iF9!5pbASgz7(>DI zPQ#z~?{;k}=2w>$do5Y88vTt;jh1NBBqa{5YPR#U zl^D0)J2N@oA8dziq$_$XCu|gemqiZcN zgp`G4(m4tjOOuush$}QLQZ`|_ zrv*LtrEEncfJaBT_~>|egm@S@sNM;g5C=h{0}QeYsSk(HEe9STxAAYUehu0uRO{7> zQuqO-alJVOlbZ@7l0IR6<10Lqh`W88G_3zZ+tW+ASmbU1G62Sa(ZKu&-S4@bSQ%teM?k5dgZV9AB0-7i51B+~C?<4_Nhv{@~1T6C_ z$A1?!Md7}Z8L^vw40}|2e9oRi5>N~_Zt&WE;kj|z-rywn`+T^#0kG8<9RayM=ZKQT z#Z6y~UqT#Kj?T=+lC?^Y*uyhr^{&**4w5TN^Fa_P$7^(Fry`s%r>VWy0i0fSgVAq>0u9CreCEM6yK30RdQenpve|(Urom$&8p_tG9$J93StkR;c zSf@RkkAasAA*)r@+VcuYivx-~m!A9L3_lychTI}1uPBXHvXf}bt5l~# zDoxsz`~jykRz;G#*?HT6tz0!qyy{zhR~s7O%5vTcF|=3W-q@k4dD^2UJyc#yBv18w zr0insJIQfqE%Rsu)QNc1)_X}_LvY#h80sh&*aLdemtp;#uCx&~c&AKbeSbbmxtf#LP2$(#4qt_)G;}tFr#ybuVm@E}0`4=O>Z<=^6--||= zkH`h(Se(4Q^Xt7OuVPsF3gM5it3g%T^we%@x8K5AHjTB1J;bKA7P6_lf5wEG%!205 zXCM1UGhwkEQ9x*$x%l!po6tj<5N8bwksYf7CsdC?8_reaNc*GxF z##t6a*NURqDGxA?s|yc9p(swgF;8W9g&$Fg_A9V11-v3!$`-@CkC`Ff{2*91MWQX% zpxc@C@t=MZi5p zC!Tx+=85mFBNUjq&VC~s3P;?+ZLiyQuu75pGamb7_iY)V8?S;RS6}NayCyd0>`$`f zF@H`M`gh&DPlc*Y4Wiru1|c1042Qjj>yC`&C!!Lq(0WI2On#Q*+aB6Eo9`lWf1}G; zwJlVMhtIr#thl$BYJ#aF9>)}o4A9g3I!CSW{?f5v+@PPX3 zQw5eja=0$yaYgiiU1T?l$y+)!ipg6G=Oec3tCq)CUe5au3V*LVK-#S^rY{NJxB7rQ zO0|1sQIQz~P7I7Q%(EyLHwwjHmFN1U@i=SZwSR&{)=;p}FNHh)!k{mm1U^vg!m{W_ zF^VhA2!&0#;e}WuvMq%?R?}y4+SU;80pL9emQAcHjLWkt+l%m_4lg4pRZ(|7q*%CT zXE$+hql5#No=RqKmie`vj6baw)iYA*He&bH30U=$PV!u??~jNJ7+0+FnN6tdsMQl3 z2@Ht?gd;qdRPM6qQbx=6^Y#mHCfw+WTi7uhMg`2zpcmfZh#6)Uvs%SM5AJgfaUIgb za9Cpn!Dkfa>aWgq88}Qi)hzPwFPb9ed4c=u9e|*gS-c`YsvC&_|iH# ziM!8?nGQ4$f?ipuw}}eW9jyoYM?fse&5DTVqdY2AJ8xdJ`kqUMBzrh=6lx7{+so3Zw zTaC1Y3b|6Tj4V<~O&KL1J`5uL)^X_n9aGSj;P^&oOQsHfcWD@t5*bsxMfjoJuyMwm z!x$xMMHgK;^E~(9P2Z^k*pxSwI&Xl@*ri=R=sC-xVG(1 zq?H+vp5wZIjEpC2bxdDzRVY1juv$_C*@+UIs-DF4yvqpM{NSqC671zo_W^WzKr{;*=trnB=$OK2h zft!2C)6X{uMY*2P+r?`)MJLq&Nhyo=!W~uZMY>qJkB^`%7Yrv`(2DaWf*n?>kSK1Q zwDrt>X)OP!r9vow2?Gl_I{vJ$Kq_3Xh2^Q_?dZG3)w4dOH%Gfe~l+8ODG)JFrK>oxAo2D)@zx8oWI&YIwZT9;+(#sx_%ZKZe@?xW-R9$Ud{rhRpBEjP8! zO(bZnXz7SacIp=ejrhjPmQ9d0Gty#RPaJOBQpJ{}ellHew_C0%16o%@bd>G80HVud zW-W#ltCGa-x4X;D!zs~uWXeA)T7JR&&x*Cu?IWg>zfeX4r560}iIlnE zwF_z+jGmZh>x?vRRY1=ufMG&A1H30(3*601HypTs9KmmkRuYz zbysY46)tUzQ+$=4iT6Nenp8^9)KJ$@5hO2YFdvvuq#dQ20#|(c=oGB&3KFzy#VoFsz%SZQAYS-htCj5W?3UqRpU;Qij;*ngZMP!v}m@K$O*3UR7LFaPJr06b>`$+ zd7)oEUCbdh`K}6x7cHR8%QmV*9SOLpY{63VrZ=0}6Fv%>3NNRr*hS;znhO8K=Q!GGim5sVn@$ znoQYql2#3e7#~jA;({)OAXCva^f^NFr69pXs>|vnqlEqW7__wb9WO}w=T1k{W=?*Y>vO?r{nYz40P_;ggT$AFNzu(zP?(DmALAcPVS=< z9C(~dH*U3Ex}XhMC|8Kd?}&?hPs}B!_Q`?jp1WrdpsAkWjinxT^0U@)D2u`dGPe5C zY^AIWso7$wBEWR@xTHa|bd1>gXEDhJ5x7PIQj#l0wLNW}5Z*}?JCl3o(nwVZ(@s{H zRx1pTxL?V=<8y^eK^W@%A)!_ZdcY6eUsys)5EbaPk`FLC!gIxH`1{7~W{#ZA)y4ZN z>I1l-WnJ%BU;iGH%we4OrT$m)QjhpgxeVj~E|*bsv9Fe)@qv)ZW;QEH@U|@nR zrVvx-&ZWDctH+4bmj4E#nsP*;BpD{bO$8KlGqX2EW3)+Pu~}H&c58kq;kAxZRkK=W z`3mvn`^x%!TsZM@OARrBF8DGy>Fzms`0AON{Jz+U+X1$R_?bl*aHh=-A;n7@JZK1o zX)rXkn%yl=pQ{H{Q*JZ7ia^#Am5Xj{=7_7JI%cu%XDxW`7`~E+3X!*XW!XlEV`p7F zphe9?oVSY_G8;N@GJNwWc#Kte4^((BjQ>gG=%zE(Lu;%vPyVP3t=qf6>Itx`d`*qC zt9p$MlyDs!Pd6%c@L0`e(OHnof@&$F!)z2|GFnxVT*;Q9F7faO0?d_J^n z7*Vny&+6LmvYjIJdCzE`ta98Z4cVwoVrfwmW3XOFF6BPt$k^s&&UwIXr711=8JLke zm~TwIVk^%cOGk!EhMuI}xH2|lDwX%MbTwXQh0PU7b*HtDkewif+CZt~ zv|UT}Qxv3V@lzIT+sMd&98NaL0&c%-JBvkmB&H;{F&2YeLAuaqzLeL>4Sa@AT(_TY ziQ-#eB0nFYyEK!V@)ZR@!O>eegjwHv@HUkN}{&$!TA${Uv;(viWPE&e-2SRu&ble16SN$c|@L&!wgbWxHgHb zkjpnT@8T77`V^x#KUQyFjV3Hs?;tgH<25FBXWy;bH6(Q5&P(RBEr|d0wMgPk-jFk{ zlPi?5@&NC<`k<%&6Tkx({!U^-*KcS?xvT8phNG0=H=LMaeFe(Q?kg#? z>~?r^OQa*!HaTfcCpbz|O<7c$sM6t!X4RlM;kF*zMT3=AxF$=Lm1&NKM~xCS^C=R{ z3W`<*#i`f@_9@;azSL9fBh~7qWMcjU;fka7#2>QN4*Ag~t!<(|P}|mUt7f8j;LH0V z_a74&=2aU7Pn@NzQeTE~B}1iGQ6Ej%M);$_lLYup><9KD-MyBghUSmi1 zEb|N|iDNK3G}#m!=pS`-D@Gc-%3It#JRFnjv|t@PB}_VEN>`b=N=3 zY(hqyTHtk_?;L7}zmG}fQW-Up%9E{DbC0xQub#$p^XJIw?t9#M<5H`h4#UyK-}*19 z)#!4NH2L+{`q@Q|O=Ko#FkfINuIUpWzt#0ZuB#_E6H+?8**&@fiKoN&#;uTkc2`h4 zUwyEEjRasTL9iXShv`qLnjThu8|NIxiO`V8fR`i1&FWOlj+p@qd7ncVF3v9WT>^P?VlShGUE$xHP4K>PdpJH;T+t zR9^N98>O2Kc`qT9z&T!iLlgLmnB7QPpj;PZ&sy|Iq1{nS^vLNHI@v2j5Ow7JwR2Wi zibrG%crkgrU~B-G#a;o+k`98tRD)g#{hL1$U6rvSEsF={4y?wSDa6+zk$$@OP(R*~ zSZNV`mHS_rx^VoR0Z@}8 z*7d66ZI@r|*a|lW2cKkDzY4G)!a=mGTWBi=`GbZG$)8(6wQM|C;F`drM7;21KUq)a z9I>&v2kt07+V=1Yg__UFu#i2hbqIPz@m)5}Q7d2SZE_R3{Ta1;l>q7L8ld~=lr#Kr zH%yVU%&5Ke!J}Hj8fK90K<4ob zE#7dwg@~NfDYT7kM$APwM=ta5K;3#6A{OOU3&&<5W)Zn{Ea52nD=in;@KgF2(z zYbO^l=3_E7e5i(6p= zy<*8*QeXxN=2umCNd%&oHX{QXPxASznt@ozAY_36(h6)4M=(8t#86FkkYf<#ZssQI z@P@=qe3aF<>2(rpz9r*W6+0MntT*Y48#L3yk(L}_= z*~G}%&XGjk_J2P7S3)?W+<*W=Hf-N8nS_G)8AbjWYO+PVf4)S7A_yGFM8KyZc))3~iZa9Zp7S73^;X zW=OG1ohi0xI(*1CAu6@o)nu8|8|#-#;iK17V06Jv`x>j9Lh&}bLzV7{vbAyqTpL5^ zkC>B(KjNjs;4POBW?rt{5i#CMmN#q2a#?&TmLJu}@)zkM7~gdqga};bp&h}%&PC_D z^+O;Yhd5KRku+nz|86X4{JJ@t{p;U^{*e{_H~nV+VAQ`$vy^7!kp)q{w_VR%Ct5|i2Z@LCAwr8&fJgf_oVf!6uHdfas_yFU;CBu8<4*gEz`rRDHf&OY>5S3=UF*Hj*fIV}nG&-EcRFW$eT|JrQua_-v~N zMCGI*xb)yKB7?6)dVGdaxclnYK~4;+dCP;6&QU4O4~>if!OfW?oJc zyEWVic|J5ei;ayah=tY7n?x6*n$Ke>xYj9fO@@|^*n=TC`hzY(5o4KE7RF;*)}# ztMG6MS0v=?8tA7Q2~t)O#}Y10-e?1>ey)3oc1*%<*!rZ(jAso01T$?wiD%(xb)jYL z2uv-btLHIIqHHiZTJ!oskhbB-IG|;IWgFB(GRxwz`cuaFG{kT*ffE~9DOj(mJwP{ZjPdA1uL+mi& zEl+j+XTj3nBS4=0+>qYMWJ9oG2+81N_wP`!~oibXp zsprhjnq-X$4IPwD650Ob$XPpBy||Ts{2T4}^pVq?082aD>M}#RMF+fFQ}ePj`$fY% z70T9bz{b3dtV+`-)k$3zq&l@hr^cO>;)|E~j@*vX7~At6E;gK)jduXOP4xjaw%cuU zgq^i_)C~xk>zt3X_?nk%_N60&`&8jgi>B^GXcR0qsBskIXW^ zWEB~$!4u%*YMM*BD=Q5?+2K0ZRy%OKG}O?0xQMZJXA=z&g6hUoH{*Mv@tjmY=>$z} ztsq{yP0W=;Nw?*?MsDVGeXBs;7rhmvejT^j;~!XLyz}ve-IM!;jc)uYpwBxcxPx}_ zS)Jj94*GydtS{g*3VP%fm@>Z%bC3bh^h5`bpt2ADW%Q_szBP{js}~nzqYYTXo?l{9 zcy!hXd*R$b6DR*yj0cE#XF8}4SmK-B+=~-DT}cWjtkDRV4hHauJ&!9|5KLyl^>ct3 zV=r@Y=}7Alr4dniZ>npeTqW>F1*Na&o4jv7 zBiE@~GpRF^hbdgP2@yW?C8w%eZU=PwzE1iu>7=4=u`_gbv2o&a zsZ`U*kWF^6ocS3_314BFMV8qlqhW5TF~htDQ?l8e$9mpgZa+98_TlC#I8X+Y)=Nxx>L`73l>=;L`+k9m7qWOsgNW(3-Z^i@vrRByB995{oy>zTjeT znn6fZ6^W7rqD!pijbS|6l~6FThWV!D^M@N?odWSJ#s%_wCs2 zSikRgU!Yw^wu`pd6fEl|%flMWHq(PpFm{{G28Xb{cI;^rycpo`;TYxp9u@pUZuYd_ z>5`0YCypFQZ;QN&R32kWd4JSIufdp7Bol%W<3#gA1N%6xnU(Xz7)|bNz#12Ljq6>< z4-@H?eyfoHC*a-+$|RK5l|RRuDIP5B8m2#-va%`~S|%6ZRT4#s$ft^$Wx8|hG0Sas zTA)-_$cP7wj#!!-Z4HM8dD-JGD!AF>+knH*8mKRj>P{{sw3hV~VZ9WGhjnU{GJ)$f zVX{dA)g||S5C^zX1-PjOio?k9%Zj!jKb$GcU}E{Ly?`Sl9>L^9%`@_}-%gzj*=574 znDWp$t8L_5|NfcT)Orl)M=d5De>&>XS!+~%S34`kgj}Op*gs2T-y1UKb1)9EARak zarOT(;{J_9%>UV+WG!qhY+P*q6^2b@?Tr6+|4hkBenB6_S9iTeixRz)^JpK}1Y1h9 zSGolOBEVSEfJ_wWQHfpGOp@NgO7KgC35gLa1>Sc*>Sxq_y8(l25MzbxXDi*Co|HTU<4HM{#NQ!aSG z%i^=_hYdmqvfncmqeN8-ERj}SZ5;yza+dUQBWhH^XJJ|Gn9fE_a9fCXL95)oBhN$p z*{e72y=7Os!HV{K(ICoF8q88xyV1>hsMQDT0901&4h5t8ws{kQv5hZY1F^ zH{d)HlZOx`WHl@9F1*Cc&p(m+>HI#McQF(>t*3r`&;$xkRDA8YB~vdJuhK4sbtl-uIzY(xUJaI09*I zeRN)_A8T^*?NL|ud2)y}<_y4)y8RWivG_$Cf9Q{(#)!|_P>smq`)wM?g*ae|a!Y37 zTb)I@M9u-uI24XJPUC=S5y64S=usqXG*@WbV|=Zs=Z5`V09t;$bak4NdyE9QhRYv; zlSstQ<9GUuFCW?vW<)RIIx%!{-u^9!zJOsSWB~*Kfc=l88tkg4zP25a!`1 zlnMyt;I>)DlC6%eL02pxeggaK@d!xd2;hAFaYE6!Q9?_?0<%UVJgU(4^_hPGr6;OAoC^2}uC~1b@!<_c&3xZW~_+SV35|LW|$RVL{TOp<#nP+{74B` zXYmmxoGkt2m!=JN;-3_cBG)f2xbsd9Xpb}*yJ}J}jm{NFwVzN?3LfU9dio72*88}Vm6oAcdj|+m z@H8EFa|E{UM#4*RrXl!Xym#zwn>90FOCflnA~0VndPPvS@t>U2e95+_x($TPhfWcS z>#J9ZK`n2B5z}$}Nvmco+CcU?v`X(&Tm-bYrW1YAZAaI1=DY-qt79|uJdXO^d)_=0 zryGZ0Be!LLqF6?xR0ken{$0MP-|CK5^gDFKFQCu2TO&SH_=+jx)yztgg-~+-!WA;18^-2(}P z1Pzdn?7iP}!`{ti_q{OlbXWgzey65RRiEyvI!DkuBO~rsX;((RF6KOFq zsBC&0bIp7p6|K7&ffbEJZJUIJxmxlu9PG*9S=w}$V6v{T*^PPfvTx<1C?&a~C70W~ z9KUzt%fkVFK|b(x3Gk0xlo#YpM|zqfh+$s&P<;M`l^m$TP5T8_($1i5~b`k{a4)c@5P*w>}3bs;eI>WnogGE zy6Ft|H?IWE$}b#|cZ~ZPPs`}MKWwgdOU4j)#{l9*2Z%WCND>~C3dHrL8~Vx~F)3r7 zWT3EbB@$WY9-t)kGbX`ieF6KVa1vx3Si8Mt+0T^XO^BZ4CH{a)^n~@q&n>+l>nJH6 zkN|&bc>Twk*$|v5V!wMMkp0_xt?2*32!7S>x`#TNbNJtl-jN|hRtDp1c13Z$>^&ip z_JKqCjLiUqGoggTkzyZ%N8Am095x}GDd!Nc5XYHm{WMt=^31`4MM`^V_5~<#|Kjek zQFEnVo?n|AqC|Nm#qM&z`Koo^+TBXPdD}_i_nVz#C{L??TL1#wkUd%!Z|vjFOW3# zI5PA@E6?%t1j8OzZ=|T(ayqxVta)fv*0++g$G(6PhLTVw3$AqQPui-&SUHOO8Etr1 zkrca2(|q7KL=Xdq^aT9lsdN{5yKMB_c-fLTWx@1k`R!?&seQ=C%ob+;p0lxUlx#i= zrdV}+hK-KGf5jE33Zme_0o$0ztN_PkeXcD`DB{YqyiuX0`&;@TK2qbWY>nL~mW^Sj5SI@X*6n|{H{_+DQW->i4 zLm&3$co*JJAC?7FLFwm%+i1AsG~Eq_r_1Xv_IWUz6PAGwHCNml^e%A{n5D3=N;#&w zr;p4!mXBKcxUNA^>eV${7EazTt#|$1H`bt_ZhM>L~c#>FV2kCM75v7yG&Pg{{;2 zFhm)V6o+|B#fb;%{VkfNEwcseB3%+CuQSbBcF)c0W~(m`_I$oOI~ldbUq)Oz*!o7% zicf9q03{q=azxD5jl_qpAhjI{7CnsQ=uQyApQ~MscD4t5Qg&n`Gydk2w)0fcbq_T0 zlhU*1UpqSnR*#Uf`P~}Qdy{VFals3xugib;hcJFCSqt=^tL$vG2-TQ9 zAWQ_*8w&Udp04vH`ow5iV;q;G`#5(MCJr&Bmy{3v{V^L-fuonNOHeGgljVE9yu%lU z&r1<{M7e}8(rzM{;DbnXs2p1FA7@g|j{dXB*QO3-n5s`3z_HRNp=){RVOjojwGXZw z?&zi9jPmIuklXh}ASf@st3ZZ&zeoYIMlNc-X;X}AvSieqnjX0`?!5_!XFcJ7SlyEj zh%rI%Aqn=RHPZ%jFn0Ory*B19ZTmobwg8zhVGVOOruV^}MJ%v~b8!Znf|UZknxeNa zLu!Y>R{4C5l-*GYUaXRBlm;YCjK3(ZE3Cw+UsuiG)u(26VWE}#1Iru=(s&bX|K2%J zJwVkW67b>~Qqad$>=j2i%AFr!a)g&~tTz8H?Y}kU$deRD-4vjF(uFH6Yy(8_Ly3Lz$qGZlZ)h|qV zTU$uA2sG!<=;a*$c;2=T=Md^hY%di2@fmcw}9X|GSxbxtcIQ2a2?aEKD3QJ5iC)2BFcv0zg zVpKWQ);^!Ajx%cvD&2rm{B$TwVNiVGZ&aw>$4o=E+p)N+xe0c$9O^L~bCm6oDTs=X z{7A;;6eYvGJ|;s9gmw`Eagk8+bcnzkH$DwlJbf-*G>l`fq~%wb0UE;Q_E5=}uF0AY z$QPdC(*~70kK|xe%^OA_e(ADLWIiM+h}}VqtjxI#?MScxg>S>v zo|Y@e5V%2%(_@DHA$_;?%WXM-Y7FZ=fk6w%93ayn`Y zlAaSEmO!XziyQ3FnLsU-JZ7`f)9z$P^;;dXfpLCu1I<4EZ+fzFx^j?T&Y3&4Orct7 z`iwZcbzmZN@2CZyDJx%NOZ~sCCysfQ>)&R}o(}L=`skxThqK@}Lzm z#Xh6BA`%P2M{xwSa}p2Py+p4{6oWMaX#f2LK@;Fl4!(utlJ7CnHu*{gRmk2JPQu^| z_qNCl|F!Jr7dj5z*wXUa@}H^A%$G&ee?C{0AyO? zE#y!u0V@xG_)p9by#?LCYZ&#w{ip+UY&q}tF0@)Y50iS!1qm4A+}IMJi(`4z5pT8G za{I$7y?hu?#Gl6t)&{8$^%Q`KW&ysf6IHdBj_Qzeb4Ye+WvNq-u8FUQbok#nD{E;~ zEhG8rE$^13Hd`D{DinTw}=&vk7(9@G0;(R`#Y{#6ycV?Y9XcJ&o8_MbF)Cl$i0KN$7Fv7h6h$Y zHEfmG<}um6yfjnXI44>jHWL*<4k&4PVd;Y)(d=*Kw)}eUB5$N{Ve$l*)Wp5J!kISKArJ0R1j9lhWJaYS`aN{&U~?E%G-rVcUF?IT*{eHx8W(Q%j1mDqa^D%fEq|W8 z{aLk&59&xWiQIwtylEj3HosZaMv!bD8*eIdJOHC1SK0f9@)Tz0PC>-Dx?FsHrIpli zD_PX91Mda-Ipp1)E~w$wrcdM5);%KO&m4C95nh~6B@XyLB?tlHIEQ+Cq7@%Eecmht zz8cc75wWvZ$PL%UYiZZlyDy zAj0YmL0sIga+j4s;s{zQ@rn6|Z|6#E6piOcaVPI#Tj{}eKQqVIb!+q93>5D<{NC3W zgoF<^bMs;Ptzv3CB$!aje$e>(f5R>m`9edZ20|K#E)F|W^DF>EWoD4L1Y=%jAn}?k z;T#>t61x^~W6U*dglPKZ2=jp^leZLz^*}JgO26Gq&^sw5O{P-T>2q_KbX9t_>YKav zm>NcTD-qQ4JAYoB0itXGOes{W0A6{oHAQEYEGbyyJ?4@q4B>wo6C`Erx`EIvG z6uw!6!H>}LlWT?*B~pJgyu0nLYK;T2yy4MoF|Kh>6E9rm{(TNfQcTNtW$uqa}lTcN2y+(D!K5OHo@UpIR#rh zFoGW_s@1&#ckzR~LL!Kgb{^F{ol7$$I(uy(gj0PY5#5^VpidF9?Y87Gfp7W*8dA|Q zfK0VaSF$hBT{Cz^=3WOUisC&D7>-XrLuW`_n*Hkk&f34{C;CeknM6&&=8 zy!1XXKk+Gia3`{U}1fj*aFLB+DJczJ?ao5s1usz$B{yQqo^T*-Z zO5ZE^^#RuncfN*Ug@)9SbhuZhbzSav82}zMLpzdCg8aN8H(U44 zGHAo%>Vc9P;SAp7w5*adqXgvFoC3*nU0SY&LBiZX9QGMTEXW1>o?=M#nh@8@z<85nhzIhU9KC9S4b zldg*OOt>{sAFIH1tFA+A(l=~hc3X7(3)(s zNZ{tQ6rtI1Uy!Q{xYO-Egr>rzqp;#8GptpGk7_kylY+Qh%_$3Ph1f)=o`H#BH+X_A z-_~dN5et_ZcbM9v!3uSUc#FKZ`A>`B5Y_MtPrbQ}dy&UXJV_7!dTlrTL$k(4%G6W3 z%+WRR6B_IJ+v|Tp!3|<>o;~Dsifo5{bNyg`mNi~A@ACqoZh7p1Nwll4W~#nG6J=@- zC)O96+}Z(H_MEu}bhX`+%TMxw&>}Z}ziyFEW<*~oMm*0SyyVdgB}H*iGSqhAB^h;M zLnefq6W6g6`}m)9UxmV+1fsJUB|m&DSx2@;WoZ|*_2<^NN{1hahi>vk%-LoU+Px{F zvHJ!VFSG~0oI`OE131uxtJLBjEDK4?Q-k3@G0f2f8?*RAf)Y?Jm_O?~o8V4i3><}3 zPh7;-1zNS7<=E+;(#G=qtkSb&h<_grJ*Q7Zl~Hru9wP@ z8SD*sj4sK~#}vM9n8HxcdOZ_Sbmh4|iV?(-e{vY|lof%*crZab6y6H=FvYR)kazwq z9Sy%P5B6QKYOm>j_rc~iU9xcVxc%|xUSI-NJ-1Ojadyb*LH(uOZYaU$ZGZ^IVU{OC zap6xTv`=#o@34R3dePj3tbhN_!WQKg__!o|ZjiX9={2<&Iv z-=93FouE722|`f5*exe*PRx=`2F8KP}(8<`1(^M|V_5kCCu=45L(Pw5LZ5Ln{C74XSG(d!`5 z5Y!ypm*iJvNsyI)l1u9Stg3DrI0K;3Ja-UdzMmt+wyT|X0HdCna1TR0ua{%O%z?88 zfa}sfb$!D(C#sh_5@hQ}EZ$D2 z1}-7VevQMSym@$c<=$i71hB2DOMylAGsClgT0}Fx@<_*;FSBsDL1P*D1d?Ze2gk(S z5+bgos!7dDulHN;RRS`wWj6S(w}fxn>N8_(W$+cmF+fV2{=pW|(G?B-U|T+v-I~`w zIb#wouJMN>WO0c~7c+bzTsW0Woyu+FFFbS3&Gh*})(atHDX4hkC7aaAPCh)}mI$Q0 zwHrQm0z0~oY;f`VIa;M+J%4{`0#kDbg-6XoikQywW)KZs)ps4V)vj82i@nV9^eOxt z3V+~U!r&i&ekr%`3mD)8CAuNazuje-70!fDxzY3nFwdF9{Y}p^;6kxrgT9HhT z8aY=I&MWlD#|7JzVt2?fL9oNV)k6kN8qU>iYpPV-(#|WqA36@k2kqqOVtof759g9H zYFuj5rqP@r!{u;DU7{U}hQt{*pmCjzZElKWBNxl(V)!oh0>B) zJywW6*AKK@>@+B4?R2u9`ayJY>wKR<1i@v7+KMqEmB*p3=3EntC!^I8t3D}dRr7_L z*3Qo4Z|D~)m;PD+xJd^dQudftA2L}yg6V(kqV101?Mh3k^|uhQ9kILulUAFKFdi-p zvYWby2=EsK=%EgeCGWv4a5Zf;G6-NJ8qxnXobEe~q%nbIdTGg3DALQ}cfDw&rszS^ zLgnk&wulBd*Etsv?{!cAR(p_3q=YaIA>JiSt~vsY7>HKh!9VX-Q){9Is&BB(%5UY- zO8R9gHx)6Vd0~<_)`yYBr@M8@jk%Nk_9qID*_TA_rCO(?99B%sLpLpZ9&vZunP-sl zr(@c%V>a3=vDfm-V9lVzjcIQk+wB(Wh)L^eH>$WW1i-dmxJIBUWXO!!*BM4mZ7Xb` z(XD>peyL?(zAva0CyN&=t0O7d22i-Jn7lm^> zZjfSvVV4IR(T6bD41nXuj*^6fqLR!N^;R7%M6W;HaLius7N0u}l; zt*iU4dlo|RiW5w3tI>b#_$lIaWcr&Y`Umbp58(xd@8ZtWyk&v0N;W$@iZKfPKS$r% zb+7&oPdPqbAjZzE`ABvaeMF;th?ykGfs?TC#Kc4s;~Wtk+A8xFc+X?8g2yXng>`6E z{LTS^?!@*4rjWlmG;J)5eV}ZTky^$6YYc@em|-m&m~shG=A6`)R8)_c;Tz#%=4Fmx zHFx;Bd}XLKeC~p9{GZQ7%hiEC32KA22UV9q$Anb5@y+v$30k#< zHzQxtLwORZ8_(CFY?i3 z9IQmlwOx17*cy$`smazR^#H1_bwE=6XBl+z$CADS`=A|U&(J`fF+0uCdJbw zb5b{U(S^n&_CE)QddBE11B3kI3`9x3ESMxyR9Lce)#Y&esqzAI``VbXHIaI}5l0GP zQ#3Fky_jNYU0T)8N`}=+hB3u$=*(*!LIR#4^NKelLlf2%TFb9gJWh5krJj+gyr4XgP0e0?fBiSV126-WGvJm9!Omv#>md)RmW*5C1v0aqY03 zxKwfKCAQ$Z>QgXR>Hm?ta6=f+(8%=~3bwlwAO=63<$u{pw#Oko2M!a1&yL9ALyWM6UBu z=oD;8hqDszd4zlx1inbz(3uN1^8M*@6KPU}teR`R!R0fbG4UX|u@*HfJQLd@zO{+w zJ7KQDAQGY88`5p$iVQ!oGqX%o!bqv6N_T1u4El8li!&zJcFKu7{~*O#hU)&0@)tCAc$Kl^dtUgjSJSb# znqr7U=BU-gonPz~UPVZ>8vU%T-zJ>&2AMxST5paz_Zf#X1SPD*40*#)EB%K|kMi!W z^ClUCDi8sUpelLro+l*VKL_Z}UnB#AbVpGFM;!;(rE>dyvZ2}P7kQm^g1Ygmuc%rI z;R(TF?_6~b&%=F;;+WTR)}9(<^ri^$PDu_1t9y>X(gEaPrnbfl73^OO9Nnbgl@$~Z zV|_k41%vf+s*+`9;WBQy*)I!?qciCkZL?Df_b0b@SS2$gNjV70u{FvyR5O}+diNXY zv_aT6bo};R8)na4rTJ^~QQ$@Lv3#jjx{)L>GTsJxAZayu*Vw$lEG-+hX(Ejro#e$G zi4gKR#kFLo1?(M=(Pp3%=_h~qg%bCA-{$lDmJYGis_ zkCL~Tjx3 z7xJ#V4rb)R^g8U1nX9EpqpxGxRF?!6N|v1)M5~DN5@^>`gFILctBEa{A5!A5gGEOd z9+Yd@^)()wb=WkeJY;NN^v}g62&JPzMgA;6u5Jxr_cx_d3l5@g=dBpsm>#Nf>=6cZ z^)-@4CR2-CElD%`V469iOcRg}i%CvXMvc;qVIgvdpKip(xQ#0s zgD93JgH7+hKAhpQX%3b;oSPTX(>Z#B+*~WksV2njtJE<_Nc{~2;F;@k;~A-vSwqQ! z&)q;t49uok%VdDVL927@%#tw7ZFuhm6Sk^=PMZ4=>EwrlM zer4!-vfFzF?ULx|!+jbBoP=%JBkAA_{K6fm|9u|h?O?)YqDCWk7^bhp4~2E|TF^bq zYK>js4)R(tTtF(GKf2*0$YsK}nfn$>52_5$HNYB@RLf(5yMTJkTzE2U8r!wXE+-r# zs{7jvR!_!Nc^DPbdw*l`Oh9DAS#e%w) zL!HVrXi28k(V=cSeHlox-NRaE!I(_BS~T29GG?@ZBIYcKxRMEB%dPSk#h-EIbgiPN zeT_l)T-Rq$glh?fo{xUfQ=3|Zw63gMozmX2QTOji@$bNo1Ibo?r*}A&ocZaa8@(-Y z#zPSI@KTyXCRx;<%ke8LfSRi^k}43Y#@eu9`_eL~S0aw2A>x_KLBit%%Yr`sVD7fbc-2ZX2`M@y zt?@PW;9iZEng|y&UY#6$R(yl;RqYNNSajOw0?e4n6V6&H z_1F+8da7(Cef7;GoZvI7er5MTb~V2 z6|7udtcx44`qo^h7J<-I(fhk5!Oos%p+5$j!Fpc1R)OC)Xj~b$0i9)uZlZp1V*NVu zdm@SRYO$vWk5+o2_K3g(o?iu!drA|Pw& zWBFWXBkiMI^6E;6%#3qMl9sRIfS{ZO4qQtM5pTM4!lH8IpSbTvV$&$mWUwCxb^Ih<)n)^Q-v+GvGHh^gDp^azALt&;jtd8M1h!e z7|cxB)JDFafztmTizM4UH)pC-ZZ&Aox>aa#43_3IyOjX!M7fUlv3OZ+9TgSiQp(yo zj#|_0WOD9FvZ7k~14!<13#jC}_z}-bJW>Pu!MF3g_v;ICD)4m+Gm1rXbB+> z#?$e{7wa9tgTO9Va6E?u_-o+(u9^HahSQqb782@JDTLdGH~#?fknarYGYRk1A_*Ox&)C>sSp;NFodiSi_NZk$t&|hTIS21%&IA< z#Tj#s%-?a)KoIT3`)J(-?DP6>8B$D;yD`^_@w0^dkWekAO-FaYknspE2*IhdMv<-= zXDdsl?b}Js`2>eVT*vhsaNHFeK^`QO9W`2x{70x^1=~l`TVOP|Q`n2BYXya-lFWQ` zCGKvGLCJAko!DN2>%T$W-W7BmuzK-VX%zxfZ0)if2E#b_@q)*{QqW_P9X!-4KWCtt zA~<RkpM;L)Fq@Z3&PRb#-A?(9Mqn-46XxpfSG4MLiNEi227LHreAi z&_f~2`x{?n-n9^!x(LTc>V{1Ah*;oE@o>`+1$-mW$`j0lQwG4lOZb>^SV1H`ig;_9 zUZLJWWn3amSP4tD+ZCfgu(2`X6`l}Vb|a*LZJ)rW!{Ov?X=L+Eb%g<%+L>@!Y0LaG zgom@zU2>*77dt^LRuZQ5AwXk*#4q(2QwOgk9FAKH?DCddvO8>&-AL3m=b7?{^Bo-3 z5__&x!bLH06Uy8Cp3kJ^fJTx1GeJVSX;67?d_~}u&&#T)CV^ne*6fA&*>uJww!!u3 zc2cF^HZA#z1%E{p^HI7nGyXR7Wn^bO&f39>#znL+Od!MWC8k0op<18rP||7UyGU)% zuoSJ(O|6)5Rbn+zo{F?^3W2IJ^)osybS~c6PY1A{zd5P$-*2SbQ!pU2QNSUaQEqRL zrlMP4;J`h6(}V&KLbW$}4tyPTwP}7)zB_6w&l0li{qByc3R$79-pJNmRID3&LagB@ z(BT(psw`!~Ej7s^b;A=cB~(Ec^s@7PtT_MZ#%gBIZR2%Z_tQS+B;OTw@C-?(cM6*H zUdlj1q{g$E+=C$;Wx^mBbkiamMxfvwhmJ$2s|8{_w1_^mFe(s>#l!(R-DNWkWkZWv7V^{( zATs8k@hSr?$f{L@{a(~;^Wny#>nKH;f4TPfp)6m8f~Tc;bf9jQlYB!B$poKoR@~?# z=yCxNPnfJo$wq*#JH4J%jg~72M0Fln?we|sS_nIKeLCAm{Fg?|lXfL~2g9~yb8Zyl zlDgNfoTwz{XR6hs@r|`KQ7EwQ^J<9nt;J3Oib4{j64~t;r1}MsTm5!9BuanP%k*`M zfDzGFRLDwjyYit^aYQ*ZEHgyd%-c&1!wB$^8&Z5)obAYh{I05*!Lws!wS|A@kP z{Xws}7AxSe9W90csxm`tDk?21=dv(AKc-(aCx%vbzYY(chn8Qz4r6XycVfJMb#QY+X=Ht1;^p{Itk-~se3%8e;pB0(uDb)O z9et=@&xSvpwP~HT;Wt>B6F9utGitrF-TDbm2>Ql<@DZ9vX*JE|?q`PhK{mWU3nBgK zC!xUP#M;>6Sp0F4m>iQ)`0zCm@C1i(WG(V?do??A)jNQs0>FGuqkdPMyLb4^w!xI6 ziZjR;68shaJ|^|V;wW{!P2vZBi_kv7(|%+I7n)&1Tf z&h&P^W=EI(c;?1dw_AI^wvkh;_}Hu8K&2;_jPBZ~9q_U6@ogcMPtMP$hK?_;GGIIK z_xlWCVtmtzUvwD!9RFj(Cwz3%`*GhF^}ACNvahYufvdi#$^iezCN49r;(* zI`A#vpp>aM;eC?zyjS2O>*ytaTZG_?L9tgZ`vdNA*29g+6DJ?$i|K8<6Dh zKj^!k(k{co$EQ$4u9G2;Gt_I+S2k)d=tmaU&Mw)c*tng5Um9Q^t0@;ei2wX3j`|bf zf+IcG*!7mDPi6U?Y)XF^|Le!uvA;nUl7=4LSNOwmdS36&J3g88yLwDWDKMW1-pNCB)!m1w{TOlBw&QE z(1&DSV0V@OuzK@8yO?y&K-nzb^%cH!83XmZQC=+k8s@=u>wbPAb-XW)-0K6b@94?@ zK;iBPe>x%ff`537hV6j^KK6RUJ~v%U^}Cf{EaCbon_;Co)7dtbFioc)~q(Q;Z!C^r_prJuPKmTesting Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super( Application.class ); + } +} \ No newline at end of file diff --git a/tileview/src/main/AndroidManifest.xml b/tileview/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c13af9a8 --- /dev/null +++ b/tileview/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/tileview/src/main/java/com/qozix/tileview/TileView.java b/tileview/src/main/java/com/qozix/tileview/TileView.java new file mode 100644 index 00000000..325eaac7 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/TileView.java @@ -0,0 +1,1000 @@ +package com.qozix.tileview; + +import android.content.Context; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.os.Handler; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.qozix.tileview.detail.DetailLevel; +import com.qozix.tileview.detail.DetailLevelManager; +import com.qozix.tileview.geom.CoordinateTranslater; +import com.qozix.tileview.geom.FloatMathHelper; +import com.qozix.tileview.graphics.BitmapProvider; +import com.qozix.tileview.hotspots.HotSpot; +import com.qozix.tileview.hotspots.HotSpotManager; +import com.qozix.tileview.markers.CalloutLayout; +import com.qozix.tileview.markers.MarkerLayout; +import com.qozix.tileview.paths.CompositePathView; +import com.qozix.tileview.tiles.TileCanvasViewGroup; +import com.qozix.tileview.widgets.ScalingLayout; +import com.qozix.tileview.widgets.ZoomPanLayout; + +import java.lang.ref.WeakReference; +import java.util.List; + +/** + * The TileView widget is a subclass of ViewGroup that supports: + * 1. Memory-managed tiled images with multiple levels of detail. + * 2. Panning by drag and fling. + * 3. Zooming by pinch and double-tap. + * 4. Markers and info windows. + * 5. Arbitrary coordinate systems. + * 6. Tappable hot spots. + * 7. Path drawing. + * + * A minimal implementation might look like this: + * + *

{@code
+ * TileView tileView = new TileView( this );
+ * tileView.setSize( 3000, 5000 );
+ * tileView.addDetailLevel( 1.0f, "path/to/tiles/%d-%d.jpg" );
+ * }
+ * + * A more advanced implementation might look like: + * + *
{@code
+ * TileView tileView = new TileView( this );
+ * tileView.setSize( 3000, 5000 );
+ * tileView.defineBounds( 42.379676, -71.094919, 42.346550, -71.040280 );
+ * tileView.addDetailLevel( 1.000f, "path/to/tiles/1000/%d-%d.jpg", 256, 256 );
+ * tileView.addDetailLevel( 0.500f, "path/to/tiles/500/%d-%d.jpg", 256, 256 );
+ * tileView.addDetailLevel( 0.250f, "path/to/tiles/250/%d-%d.jpg", 256, 256 );
+ * tileView.addDetailLevel( 0.125f, "path/to/tiles/125/%d-%d.jpg", 128, 128 );
+ * tileView.addMarker( someView, 42.35848, -71.063736, null, null );
+ * tileView.addMarker( anotherView, 42.3665, -71.05224, -1.0f, -0.5f );
+ * tileView.setMarkerTapListener( someMarkerTapListenerImplementation );
+ * }
+ */ +public class TileView extends ZoomPanLayout implements + ZoomPanLayout.ZoomPanListener, + TileCanvasViewGroup.TileRenderListener, + DetailLevelManager.DetailLevelChangeListener { + + protected static final int DEFAULT_TILE_SIZE = 256; + + private DetailLevelManager mDetailLevelManager = new DetailLevelManager(); + private CoordinateTranslater mCoordinateTranslater = new CoordinateTranslater(); + private HotSpotManager mHotSpotManager = new HotSpotManager(); + + private TileCanvasViewGroup mTileCanvasViewGroup; + private CompositePathView mCompositePathView; + private ScalingLayout mScalingLayout; + private MarkerLayout mMarkerLayout; + private CalloutLayout mCalloutLayout; + + private RenderThrottleHandler mRenderThrottleHandler; + + private boolean mShouldRenderWhilePanning = false; + private boolean mShouldUpdateDetailLevelWhileZooming = false; + + /** + * Constructor to use when creating a TileView from code. + * + * @param context The Context the TileView is running in, through which it can access the current theme, resources, etc. + */ + public TileView( Context context ) { + this( context, null ); + } + + public TileView( Context context, AttributeSet attrs ) { + this( context, attrs, 0 ); + } + + public TileView( Context context, AttributeSet attrs, int defStyleAttr ) { + + super( context, attrs, defStyleAttr ); + + mTileCanvasViewGroup = new TileCanvasViewGroup( context ); + addView( mTileCanvasViewGroup ); + + mCompositePathView = new CompositePathView( context ); + addView( mCompositePathView ); + + mScalingLayout = new ScalingLayout( context ); + addView( mScalingLayout ); + + mMarkerLayout = new MarkerLayout( context ); + addView( mMarkerLayout ); + + mCalloutLayout = new CalloutLayout( context ); + addView( mCalloutLayout ); + + mDetailLevelManager.setDetailLevelChangeListener( this ); + mTileCanvasViewGroup.setTileRenderListener( this ); + addZoomPanListener( this ); + + mRenderThrottleHandler = new RenderThrottleHandler( this ); + + requestRender(); + + } + + /** + * Returns the DetailLevelManager instance used by the TileView to coordinate DetailLevels. + * + * @return The DetailLevelManager instance. + */ + public DetailLevelManager getDetailLevelManager() { + return mDetailLevelManager; + } + + /** + * Returns the CoordinateTranslater instance used by the TileView to manage abritrary coordinate + * systems. + * + * @return The CoordinateTranslater instance. + */ + public CoordinateTranslater getCoordinateTranslater() { + return mCoordinateTranslater; + } + + /** + * Returns the HotSpotManager instance used by the TileView to detect and react to touch events + * that intersect a user-defined region. + * + * @return The HotSpotManager instance. + */ + public HotSpotManager getHotSpotManager() { + return mHotSpotManager; + } + + /** + * Returns the CompositePathView instance used by the TileView to draw and scale paths. + * + * @return The CompositePathView instance. + */ + public CompositePathView getCompositePathView() { + return mCompositePathView; + } + + /** + * Returns the TileCanvasViewGroup instance used by the TileView to manage tile bitmap rendering. + * + * @return The TileCanvasViewGroup instance. + */ + public TileCanvasViewGroup getTileCanvasViewGroup() { + return mTileCanvasViewGroup; + } + + /** + * Returns the MakerLayout instance used by the TileView to position and display Views used + * as markers. + * + * @return The MarkerLayout instance. + */ + public MarkerLayout getMarkerLayout() { + return mMarkerLayout; + } + + /** + * Returns the CalloutLayout instance used by the TileView to position and display Views used + * as callouts. + * + * @return The CalloutLayout instance. + */ + public CalloutLayout getCalloutLayout() { + return mCalloutLayout; + } + + /** + * Returns the ScalingLayout instance used by the TileView to allow insertion of arbitrary + * Views and ViewGroups that will scale visually with the TileView. + * + * @return The ScalingLayout instance. + */ + public ScalingLayout getScalingLayout() { + return mScalingLayout; + } + + /** + * Add a ViewGroup to the TileView at a z-index above tiles and paths but beneath + * markers and callouts. The ViewGroup will be laid out to the full dimensions of the largest + * detail level, and will scale with the TileView. + * Note that only the drawing surface of the view is scaled, other operations that depend + * on dimensions are not (e.g., hit areas, invalidation tests). + * + * @param viewGroup The ViewGroup to be added to the TileView, that will scale visually. + */ + public void addScalingViewGroup( ViewGroup viewGroup ) { + mScalingLayout.addView( viewGroup ); + } + + /** + * Request that the current tile set is re-examined and re-drawn. + * The request is added to a queue and is not guaranteed to be processed at any particular + * time, and will never be handled immediately. + */ + public void requestRender() { + mTileCanvasViewGroup.requestRender(); + } + + /** + * While all render operation requests are queued and batched, this method provides an additional + * throttle layer, so that any subsequent invocations cancel and pending invocations. + * + * This is useful when requesting in a stream fashion, either in a loop or in response to a + * progressive action like an animation or touch move. + */ + public void requestThrottledRender() { + mRenderThrottleHandler.submit(); + } + + /** + * If flinging, defer render, otherwise request now. + * If a render operation starts at the beginning of a fling, a stutter can occur. + */ + protected void requestSafeRender() { + if( isFlinging() ) { + requestThrottledRender(); + } else { + requestRender(); + } + } + + /** + * Notify the TileView that it may stop rendering tiles. The rendering thread will be + * sent an interrupt request, but no guarantee is provided when the request will be responded to. + */ + public void cancelRender() { + mTileCanvasViewGroup.cancelRender(); + } + + /** + * Notify the TileView that it should continue to render any pending tiles, but should not + * accept new render tasks. + */ + public void suppressRender() { + mTileCanvasViewGroup.suppressRender(); + } + + /** + * Notify the TileView that it should resume tiles rendering. + */ + public void resumeRender() { + mTileCanvasViewGroup.resumeRender(); + } + + /** + * Sets a custom class to perform the getBitmap operation when tile bitmaps are requested for + * tile images only. + * By default, a BitmapDecoder implementation is provided that renders bitmaps from the context's + * Assets, but alternative implementations could be used that fetch images via HTTP, or from the + * SD card, or resources, SVG, etc. + * + * @param bitmapProvider A class instance that implements BitmapProvider, and must define a getBitmap method, which accepts a String file name and a Context object, and returns a Bitmap + */ + public void setBitmapProvider( BitmapProvider bitmapProvider ) { + mTileCanvasViewGroup.setBitmapProvider( bitmapProvider ); + } + + /** + * Defines whether tile bitmaps should be rendered using an AlphaAnimation + * + * @param enabled True if the TileView should render tiles with fade transitions + */ + public void setTransitionsEnabled( boolean enabled ) { + mTileCanvasViewGroup.setTransitionsEnabled( enabled ); + } + + /** + * Instructs Tile instances to recycle (or not). This can be useful if using a caching system + * that re-uses bitmaps and expects them to not have been recycled. + * + * The default value is true. + * + * @deprecated This value is no longer considered - bitmaps are always recycled when they're no longer used. + * @param shouldRecycleBitmaps True if bitmaps should call Bitmap.recycle when they are removed from view. + */ + public void setShouldRecycleBitmaps( boolean shouldRecycleBitmaps ) { + mTileCanvasViewGroup.setShouldRecycleBitmaps( shouldRecycleBitmaps ); + } + + /** + * Defines the total size, in pixels, of the tile set at 100% scale. + * The TileView wills pan within it's layout dimensions, with the content (scrollable) + * size defined by this method. + * + * @param width Total width of the tiled set. + * @param height Total height of the tiled set. + */ + @Override + public void setSize( int width, int height ) { + super.setSize( width, height ); + mDetailLevelManager.setSize( width, height ); + mCoordinateTranslater.setSize( width, height ); + } + + /** + * Register a tile set to be used for a particular detail level. + * Each tile set to be used must be registered using this method, + * and at least one tile set must be registered for the TileView to render any tiles. + * + * @param detailScale Scale at which the TileView should use the tiles in this set. + * @param data An arbitrary object of any type that is passed to the BitmapProvider for each tile on this level. + */ + public void addDetailLevel( float detailScale, Object data ) { + addDetailLevel( detailScale, data, DEFAULT_TILE_SIZE, DEFAULT_TILE_SIZE ); + } + + /** + * Register a tile set to be used for a particular detail level. + * Each tile set to be used must be registered using this method, + * and at least one tile set must be registered for the TileView to render any tiles. + * + * @param detailScale Scale at which the TileView should use the tiles in this set. + * @param data An arbitrary object of any type that is passed to the (Adapter|Decoder) for each tile on this level. + * @param tileWidth Size of each tiled column. + * @param tileHeight Size of each tiled row. + */ + public void addDetailLevel( float detailScale, Object data, int tileWidth, int tileHeight ) { + mDetailLevelManager.addDetailLevel( detailScale, data, tileWidth, tileHeight ); + } + + /** + * Register a tile set to be used for a particular detail level. + * Each tile set to be used must be registered using this method, + * and at least one tile set must be registered for the TileView to render any tiles. + * + * @param detailScale Scale at which the TileView should use the tiles in this set. + * @param data An arbitrary object of any type that is passed to the (Adapter|Decoder) for each tile on this level. + * @param tileWidth Size of each tiled column. + * @param tileHeight Size of each tiled row. + * @param levelType Type of level, detail-levels can have the same scale but different looks. + */ + public void addDetailLevel(float detailScale, Object data, int tileWidth, int tileHeight, DetailLevelManager.LevelType levelType ) { + mDetailLevelManager.addDetailLevel( detailScale, data, tileWidth, tileHeight, levelType ); + } + + /** + * Pads the viewport by the number of pixels passed. e.g., setViewportPadding( 100 ) instructs the + * TileView to interpret it's actual viewport offset by 100 pixels in each direction (top, left, + * right, bottom), so more tiles will qualify for "visible" status when intersections are calculated. + * + * @param padding The number of pixels to pad the viewport by + */ + public void setViewportPadding( int padding ) { + mDetailLevelManager.setViewportPadding( padding ); + } + + /** + * Register a set of offset points to use when calculating position within the TileView. + * Any type of coordinate system can be used (any type of lat/lng, percentile-based, etc), + * and all positioned are calculated relatively. If relative bounds are defined, position parameters + * received by TileView methods will be translated to the the appropriate pixel value. + * To remove this process, use undefineBounds. + * + * @param left The left edge of the rectangle used when calculating position. + * @param top The top edge of the rectangle used when calculating position. + * @param right The right edge of the rectangle used when calculating position. + * @param bottom The bottom edge of the rectangle used when calculating position. + */ + public void defineBounds( double left, double top, double right, double bottom ) { + mCoordinateTranslater.setBounds( left, top, right, bottom ); + } + + /** + * Unregisters arbitrary bounds and coordinate system. After invoking this method, + * TileView methods that receive position method parameters will use pixel values, + * relative to the TileView's registered size (at 1.0f scale). + */ + public void undefineBounds() { + mCoordinateTranslater.unsetBounds(); + } + + /** + * Scrolls (instantly) the TileView to the x and y positions provided. The is an overload + * of scrollTo( int x, int y ) that accepts doubles; if the TileView has relative bounds defined, + * those relative doubles will be converted to absolute pixel positions. + * + * @param x The relative x position to move to. + * @param y The relative y position to move to. + */ + public void scrollTo( double x, double y ) { + scrollTo( + mCoordinateTranslater.translateAndScaleX( x, getScale() ), + mCoordinateTranslater.translateAndScaleY( y, getScale() ) + ); + } + + /** + * Scrolls (instantly) the TileView to the x and y positions provided, + * then centers the viewport to the position. + * + * @param x The relative x position to move to. + * @param y The relative y position to move to. + */ + public void scrollToAndCenter( double x, double y ) { + scrollToAndCenter( + mCoordinateTranslater.translateAndScaleX( x, getScale() ), + mCoordinateTranslater.translateAndScaleY( y, getScale() ) + ); + } + + /** + * Scrolls (with animation) the TileView to the relative x and y positions provided. + * + * @param x The relative x position to move to. + * @param y The relative y position to move to. + */ + public void slideTo( double x, double y ) { + slideTo( + mCoordinateTranslater.translateAndScaleX( x, getScale() ), + mCoordinateTranslater.translateAndScaleY( y, getScale() ) + ); + } + + /** + * Scrolls (with animation) the TileView to the x and y positions provided, + * then centers the viewport to the position. + * + * @param x The relative x position to move to. + * @param y The relative y position to move to. + */ + public void slideToAndCenter( double x, double y ) { + slideToAndCenter( + mCoordinateTranslater.translateAndScaleX( x, getScale() ), + mCoordinateTranslater.translateAndScaleY( y, getScale() ) + ); + } + + /** + * Scrolls and scales (with animation) the TileView to the specified x, y and scale provided. + * The TileView will be centered to the coordinates passed. + * + * @param x The relative x position to move to. + * @param y The relative y position to move to. + * @param scale The scale the TileView should be at when the animation is complete. + */ + public void slideToAndCenterWithScale( double x, double y, float scale ) { + slideToAndCenterWithScale( + mCoordinateTranslater.translateAndScaleX( x, scale ), + mCoordinateTranslater.translateAndScaleY( y, scale ), + scale + ); + } + + /** + * Markers added to this TileView will have anchor logic applied on the values provided here. + * E.g., setMarkerAnchorPoints(-0.5f, -1.0f) will have markers centered horizontally, and aligned + * along the bottom edge to the y value supplied. + * + * Anchor values assigned to individual markers will override these default values. + * + * @param anchorX The x-axis position of a marker will be offset by a number equal to the width of the marker multiplied by this value. + * @param anchorY The y-axis position of a marker will be offset by a number equal to the height of the marker multiplied by this value. + */ + public void setMarkerAnchorPoints( Float anchorX, Float anchorY ) { + mMarkerLayout.setAnchors( anchorX, anchorY ); + } + + /** + * Add a marker to the the TileView. The marker can be any View. + * No LayoutParams are required; the View will be laid out using WRAP_CONTENT for both width and height, and positioned based on the parameters. + * + * @param view View instance to be added to the TileView. + * @param x Relative x position the View instance should be positioned at. + * @param y Relative y position the View instance should be positioned at. + * @param anchorX The x-axis position of a marker will be offset by a number equal to the width of the marker multiplied by this value. + * @param anchorY The y-axis position of a marker will be offset by a number equal to the height of the marker multiplied by this value. + * @return The View instance added to the TileView. + */ + public View addMarker( View view, double x, double y, Float anchorX, Float anchorY ) { + return mMarkerLayout.addMarker( view, + mCoordinateTranslater.translateX( x ), + mCoordinateTranslater.translateY( y ), + anchorX, anchorY + ); + } + + /** + * Removes a marker View from the TileView's view tree. + * + * @param view The marker View to be removed. + */ + public void removeMarker( View view ) { + mMarkerLayout.removeMarker( view ); + } + + /** + * Moves an existing marker to another position. + * + * @param view The marker View to be repositioned. + * @param x Relative x position the View instance should be positioned at. + * @param y Relative y position the View instance should be positioned at. + */ + public void moveMarker( View view, double x, double y ) { + mMarkerLayout.moveMarker( view, + mCoordinateTranslater.translateX( x ), + mCoordinateTranslater.translateY( y ) ); + } + + /** + * Scroll the TileView so that the View passed is centered in the viewport. + * + * @param view The View marker that the TileView should center on. + * @param shouldAnimate True if the movement should use a transition effect. + */ + public void moveToMarker( View view, boolean shouldAnimate ) { + if( mMarkerLayout.indexOfChild( view ) == -1 ) { + throw new IllegalStateException( "The view passed is not an existing marker" ); + } + ViewGroup.LayoutParams params = view.getLayoutParams(); + if( params instanceof MarkerLayout.LayoutParams ) { + MarkerLayout.LayoutParams anchorLayoutParams = (MarkerLayout.LayoutParams) params; + int scaledX = FloatMathHelper.scale( anchorLayoutParams.x, getScale() ); + int scaledY = FloatMathHelper.scale( anchorLayoutParams.y, getScale() ); + if( shouldAnimate ) { + slideToAndCenter( scaledX, scaledY ); + } else { + scrollToAndCenter( scaledX, scaledY ); + } + } + } + + /** + * Register a MarkerTapListener for the TileView instance (rather than on a single marker view). + * Unlike standard touch events attached to marker View's (e.g., View.OnClickListener), + * MarkerTapListener.onMarkerTapEvent does not consume the touch event, so will not interfere + * with scrolling. + * + * @param markerTapListener Listener to be added to the TileView's list of MarkerTapListener. + */ + public void setMarkerTapListener( MarkerLayout.MarkerTapListener markerTapListener ) { + mMarkerLayout.setMarkerTapListener( markerTapListener ); + } + + /** + * Add a callout to the the TileView. The callout can be any View. + * No LayoutParams are required; the View will be laid out using WRAP_CONTENT for both + * width and height, and positioned according to the x and y values supplied. + * Callout views will always be positioned at the top of the view tree (at the highest z-index), + * and will always be removed during any touch event that is not consumed by the callout View. + * + * @param view View instance to be added to the TileView. + * @param x Relative x position the View instance should be positioned at. + * @param y Relative y position the View instance should be positioned at. + * @param anchorX The x-axis position of a callout view will be offset by a number equal to the width of the callout view multiplied by this value. + * @param anchorY The y-axis position of a callout view will be offset by a number equal to the height of the callout view multiplied by this value. + * @return The View instance added to the TileView. + */ + public View addCallout( View view, double x, double y, Float anchorX, Float anchorY ) { + return mCalloutLayout.addMarker( view, + mCoordinateTranslater.translateX( x ), + mCoordinateTranslater.translateY( y ), + anchorX, anchorY + ); + } + + /** + * Removes a callout View from the TileView. + * + * @param view The callout View to be removed. + */ + public void removeCallout( View view ) { + mCalloutLayout.removeMarker( view ); + } + + /** + * Register a HotSpot that should fire a listener when a touch event occurs that intersects the + * Region defined by the HotSpot. + * + * The HotSpot virtually moves and scales with the TileView. + * + * @param hotSpot The hotspot that is tested against touch events that occur on the TileView. + * @return The HotSpot instance added. + */ + public HotSpot addHotSpot( HotSpot hotSpot ) { + mHotSpotManager.addHotSpot( hotSpot ); + return hotSpot; + } + + /** + * Register a HotSpot that should fire a listener when a touch event occurs that intersects the + * Region defined by the HotSpot. + * + * The HotSpot virtually moves and scales with the TileView. + * + * @param positions (List) List of paired doubles that represents the region. + * @return HotSpot the hotspot created with this method. + */ + public HotSpot addHotSpot( List positions, HotSpot.HotSpotTapListener listener ) { + Path path = mCoordinateTranslater.pathFromPositions( positions, true ); + RectF bounds = new RectF(); + path.computeBounds( bounds, true ); + Rect rect = new Rect(); + bounds.round( rect ); + Region clip = new Region( rect ); + HotSpot hotSpot = new HotSpot(); + hotSpot.setPath( path, clip ); + hotSpot.setHotSpotTapListener( listener ); + return addHotSpot( hotSpot ); + } + + /** + * Remove a HotSpot registered with addHotSpot. + * + * @param hotSpot The HotSpot instance to remove. + */ + public void removeHotSpot( HotSpot hotSpot ) { + mHotSpotManager.removeHotSpot( hotSpot ); + } + + /** + * Register a HotSpotTapListener with the TileView. This listener will fire if any registered + * HotSpot's region intersects a Tap event. + * + * @param hotSpotTapListener The listener to be added. + */ + public void setHotSpotTapListener( HotSpot.HotSpotTapListener hotSpotTapListener ) { + mHotSpotManager.setHotSpotTapListener( hotSpotTapListener ); + } + + /** + * Register a DrawablePath that will be drawn on a layer above the tiles, but below markers. + * The Path will be scaled with the TileView, but will always be as wide as the stroke set + * for the Paint instance associated with the DrawablePath. + * + * @param drawablePath DrawablePath instance to be drawn by the TileView. + * @return The DrawablePath instance passed to the TileView. + */ + public CompositePathView.DrawablePath drawPath( CompositePathView.DrawablePath drawablePath ) { + return mCompositePathView.addPath( drawablePath ); + } + + /** + * Register a Path and Paint that will be drawn on a layer above the tiles, but below markers. + * The Path will be scaled with the TileView, but will always be as wide as the stroke set + * for the Paint. + * + * @param positions List of doubles that represent the points of the Path. + * @param paint The Paint instance that defines the style of the drawn path. + * @return The DrawablePath instance passed to the TileView. + */ + public CompositePathView.DrawablePath drawPath( List positions, Paint paint ) { + Path path = mCoordinateTranslater.pathFromPositions( positions, false ); + return mCompositePathView.addPath( path, paint ); + } + + /** + * Removes a DrawablePath from the TileView's registry. This path will no longer be drawn by the + * TileView. + * + * @param drawablePath The DrawablePath instance to be removed. + */ + public void removePath( CompositePathView.DrawablePath drawablePath ) { + mCompositePathView.removePath( drawablePath ); + } + + /** + * Returns the Paint instance used by the CompositePathView by default. This can be modified for + * future Path paint operations. + * + * @return The Paint instance used by default. + */ + public Paint getDefaultPathPaint() { + return mCompositePathView.getDefaultPaint(); + } + + /** + * Recycles bitmap image files, prevents path drawing, and clears pending Handler messages, + * appropriate for Activity.onPause. + */ + public void pause() { + mRenderThrottleHandler.clear(); + mDetailLevelManager.invalidateAll(); + setWillNotDraw( true ); + } + + /** + * Clear tile image files and remove all views, appropriate for Activity.onDestroy. + * After invoking this method, the TileView instance should be removed from any view trees, + * and references to it should be set to null. + */ + public void destroy() { + pause(); + mTileCanvasViewGroup.destroy(); + mCompositePathView.clear(); + removeAllViews(); + } + + /** + * Restore visible state (generally after a call to pause). + * Appropriate for Activity.onResume. + */ + public void resume() { + setWillNotDraw( false ); + updateViewport(); + mTileCanvasViewGroup.updateTileSet( mDetailLevelManager.getCurrentDetailLevel() ); + requestRender(); + requestLayout(); + } + + /** + * Allows the TileView to render tiles while panning. + * + * @param shouldRender True if it should render while panning. + */ + public void setShouldRenderWhilePanning( boolean shouldRender ) { + mShouldRenderWhilePanning = shouldRender; + int buffer = shouldRender ? TileCanvasViewGroup.FAST_RENDER_BUFFER : TileCanvasViewGroup.DEFAULT_RENDER_BUFFER; + mTileCanvasViewGroup.setRenderBuffer( buffer ); + } + + /** + * By default, when a zoom begins, the current {@link DetailLevel} is locked so it is used to + * provide tiles until the zoom ends. This ensures that the {@link TileView} is updated + * consistently. + *

+ * However, a zoom out may require a lot of tiles of the locked {@code DetailLevel} to be rendered. + * In worst case, it can cause {@link OutOfMemoryError}. + * Then, disabling the {@code DetailLevel} lock is a bandage to that issue. Using + * {@code setShouldUpdateDetailLevelWhileZooming( true )} is not advised unless you have that issue. + *

+ * + * @param shouldUpdate True if it should lock {@link DetailLevel} when a zoom begins. + */ + public void setShouldUpdateDetailLevelWhileZooming( boolean shouldUpdate ) { + mShouldUpdateDetailLevelWhileZooming = shouldUpdate; + } + + /** + * Allows the use of a custom {@link DetailLevelManager}. + *

+ * For example, to change the logic of {@link DetailLevel} choice for a given scale, you + * declare your own {@code DetailLevelMangerCustom} that extends {@link DetailLevelManager} : + *

{@code
+   * private class DetailLevelManagerCustom extends DetailLevelManager{
+   *  @literal @Override
+   *   public DetailLevel getDetailLevelForScale(){
+   *     // your logic here
+   *   }
+   * }
+   * }
+   * 
+ * Then you should use {@code TileView.setDetailLevelManager} before other method calls, especially + * {@code TileView.setSize} and {@code TileView.addDetailLevel}. + *

+ * + * @param manager The DetailLevelManager instance used. + */ + public void setDetailLevelManager( DetailLevelManager manager ) { + mDetailLevelManager = manager; + mDetailLevelManager.setDetailLevelChangeListener(this); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + mCalloutLayout.removeAllViews(); + return super.onTouchEvent( event ); + } + + @Override + protected void onLayout( boolean changed, int l, int t, int r, int b ) { + super.onLayout( changed, l, t, r, b ); + updateViewport(); + requestRender(); + } + + protected void updateViewport() { + int left = getScrollX(); + int top = getScrollY(); + int right = left + getWidth(); + int bottom = top + getHeight(); + mDetailLevelManager.updateViewport( left, top, right, bottom ); + } + + @Override + protected void onScrollChanged( int l, int t, int oldl, int oldt ) { + super.onScrollChanged( l, t, oldl, oldt ); + updateViewport(); + if( mShouldRenderWhilePanning ) { + requestRender(); + } else { + requestThrottledRender(); + } + } + + @Override + public void onScaleChanged( float scale, float previous ) { + super.onScaleChanged( scale, previous ); + mDetailLevelManager.setScale( scale ); + mHotSpotManager.setScale( scale ); + mTileCanvasViewGroup.setScale( scale ); + mScalingLayout.setScale( scale ); + mCompositePathView.setScale( scale ); + mMarkerLayout.setScale( scale ); + mCalloutLayout.setScale( scale ); + } + + @Override + public void onPanBegin( int x, int y, Origination origin ) { + + } + + @Override + public void onPanUpdate( int x, int y, Origination origin ) { + + } + + @Override + public void onPanEnd( int x, int y, Origination origin ) { + requestRender(); + } + + @Override + public void onZoomBegin( float scale, Origination origin ) { + if ( origin == null ) { + mTileCanvasViewGroup.suppressRender(); + } + mDetailLevelManager.setScale( scale ); + } + + @Override + public void onZoomUpdate( float scale, Origination origin ) { + + } + + @Override + public void onZoomEnd( float scale, Origination origin ) { + if ( origin == null ) { + mTileCanvasViewGroup.resumeRender(); + } + mDetailLevelManager.setScale( scale ); + requestRender(); + } + + @Override + public void onDetailLevelChanged( DetailLevel detailLevel ) { + requestRender(); + mTileCanvasViewGroup.updateTileSet( detailLevel ); + } + + @Override + public boolean onSingleTapConfirmed( MotionEvent event ) { + int x = getScrollX() + (int) event.getX() - getOffsetX(); + int y = getScrollY() + (int) event.getY() - getOffsetY(); + mMarkerLayout.processHit( x, y ); + mHotSpotManager.processHit( x, y ); + return super.onSingleTapConfirmed( event ); + } + + @Override + public void onRenderStart() { + + } + + @Override + public void onRenderCancelled() { + + } + + @Override + public void onRenderComplete() { + + } + + private static class RenderThrottleHandler extends Handler { + + private static final int MESSAGE = 0; + private static final int RENDER_THROTTLE_TIMEOUT = 100; + + private final WeakReference mTileViewWeakReference; + + public RenderThrottleHandler( TileView tileView ) { + super(); + mTileViewWeakReference = new WeakReference( tileView ); + } + + @Override + public void handleMessage( Message msg ) { + TileView tileView = mTileViewWeakReference.get(); + if( tileView != null ) { + tileView.requestSafeRender(); + } + } + + public void clear() { + if( hasMessages( MESSAGE ) ) { + removeMessages( MESSAGE ); + } + } + + public void submit() { + clear(); + sendEmptyMessageDelayed( MESSAGE, RENDER_THROTTLE_TIMEOUT ); + } + } + + /** + * Object used to keep some data when a configuration change happens and the activity is + * re-created. + * It's boiler-plate but this is how to save View state. + */ + private static class SavedState extends BaseSavedState { + /* This will store the current scale and position */ + float mScale; + int mSavedCenterX; + int mSavedCenterY; + + SavedState( Parcelable superState ) { + super( superState ); + } + + private SavedState( Parcel in ) { + super( in ); + mScale = in.readFloat(); + mSavedCenterX = in.readInt(); + mSavedCenterY = in.readInt(); + } + + @Override + public void writeToParcel( Parcel out, int flags ) { + super.writeToParcel( out, flags ); + out.writeFloat( mScale ); + out.writeInt( mSavedCenterX ); + out.writeInt( mSavedCenterY ); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel( Parcel in ) { + return new SavedState( in ); + } + + public SavedState[] newArray( int size ) { + return new SavedState[ size ]; + } + }; + } + + /** + * The default {@code super.onSaveInstanceState} and {@code onRestoreInstanceState} don't + * restore the position on the map as expected (if the instance of {@link TileView} remains the + * same). For this reason and if a new {@link TileView} instance is created, we have to save + * the current scale and position on the map, to restore them later when the {@link TileView} is + * recreated. + */ + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState( superState ); + ss.mScale = getScale(); + ss.mSavedCenterX = getScrollX() + getHalfWidth(); + ss.mSavedCenterY = getScrollY() + getHalfHeight(); + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + final SavedState ss = (SavedState) state; + super.onRestoreInstanceState( ss.getSuperState() ); + setScale( ss.mScale ); + post(new Runnable() { + @Override + public void run() { + scrollToAndCenter(ss.mSavedCenterX, ss.mSavedCenterY); + } + }); + } + +} \ No newline at end of file diff --git a/tileview/src/main/java/com/qozix/tileview/detail/DetailLevel.java b/tileview/src/main/java/com/qozix/tileview/detail/DetailLevel.java new file mode 100644 index 00000000..b48ea231 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/detail/DetailLevel.java @@ -0,0 +1,195 @@ +package com.qozix.tileview.detail; + +import android.graphics.Rect; +import android.support.annotation.NonNull; + +import com.qozix.tileview.tiles.Tile; + +import java.util.HashSet; +import java.util.Set; + +public class DetailLevel implements Comparable { + + private float mScale; + private int mTileWidth; + private int mTileHeight; + private Object mData; + private DetailLevelManager.LevelType mLevelType; + + private DetailLevelManager mDetailLevelManager; + + private StateSnapshot mLastStateSnapshot; + + private Set mTilesVisibleInViewport = new HashSet<>(); + + public DetailLevel( DetailLevelManager detailLevelManager, float scale, Object data, int tileWidth, int tileHeight, DetailLevelManager.LevelType levelType ) { + mDetailLevelManager = detailLevelManager; + mScale = scale; + mData = data; + mTileWidth = tileWidth; + mTileHeight = tileHeight; + mLevelType = levelType; + } + + public DetailLevel( DetailLevelManager detailLevelManager, float scale, Object data, int tileWidth, int tileHeight ) { + mDetailLevelManager = detailLevelManager; + mScale = scale; + mData = data; + mTileWidth = tileWidth; + mTileHeight = tileHeight; + } + + public DetailLevelManager getDetailLevelManager() { + return mDetailLevelManager; + } + + /** + * Returns true if there has been a change, false otherwise. + * + * @return True if there has been a change, false otherwise. + */ + public boolean computeCurrentState() { + float relativeScale = getRelativeScale(); + int drawableWidth = mDetailLevelManager.getScaledWidth(); + int drawableHeight = mDetailLevelManager.getScaledHeight(); + float offsetWidth = mTileWidth * relativeScale; + float offsetHeight = mTileHeight * relativeScale; + Rect viewport = new Rect( mDetailLevelManager.getComputedViewport() ); + viewport.top = Math.max( viewport.top, 0 ); + viewport.left = Math.max( viewport.left, 0 ); + viewport.right = Math.min( viewport.right, drawableWidth ); + viewport.bottom = Math.min( viewport.bottom, drawableHeight ); + int rowStart = (int) Math.floor( viewport.top / offsetHeight ); + int rowEnd = (int) Math.ceil( viewport.bottom / offsetHeight ); + int columnStart = (int) Math.floor( viewport.left / offsetWidth ); + int columnEnd = (int) Math.ceil( viewport.right / offsetWidth ); + StateSnapshot stateSnapshot = new StateSnapshot( this, rowStart, rowEnd, columnStart, columnEnd ); + boolean sameState = stateSnapshot.equals( mLastStateSnapshot ); + mLastStateSnapshot = stateSnapshot; + return !sameState; + } + + /** + * Returns a list of Tile instances describing the currently visible viewport. + * + * @return List of Tile instances describing the currently visible viewport. + */ + public Set getVisibleTilesFromLastViewportComputation() { + if( mLastStateSnapshot == null ) { + throw new StateNotComputedException(); + } + return mTilesVisibleInViewport; + } + + public boolean hasComputedState() { + return mLastStateSnapshot != null; + } + + public void computeVisibleTilesFromViewport() { + mTilesVisibleInViewport.clear(); + for( int rowCurrent = mLastStateSnapshot.rowStart; rowCurrent < mLastStateSnapshot.rowEnd; rowCurrent++ ) { + for( int columnCurrent = mLastStateSnapshot.columnStart; columnCurrent < mLastStateSnapshot.columnEnd; columnCurrent++ ) { + Tile tile = new Tile( columnCurrent, rowCurrent, mTileWidth, mTileHeight, mData, this ); + mTilesVisibleInViewport.add( tile ); + } + } + } + + /** + * Ensures that computeCurrentState will return true, indicating a change has occurred. + */ + public void invalidate() { + mLastStateSnapshot = null; + } + + public float getScale() { + return mScale; + } + + public float getRelativeScale() { + return mDetailLevelManager.getScale() / mScale; + } + + public int getTileWidth() { + return mTileWidth; + } + + public int getTileHeight() { + return mTileHeight; + } + + public Object getData() { + return mData; + } + + public DetailLevelManager.LevelType getLevelType() { + return mLevelType; + } + + @Override + public int compareTo( @NonNull DetailLevel detailLevel ) { + return (int) Math.signum( getScale() - detailLevel.getScale() ); + } + + @Override + public boolean equals( Object object ) { + if( this == object ) { + return true; + } + if( object instanceof DetailLevel ) { + DetailLevel detailLevel = (DetailLevel) object; + return mScale == detailLevel.getScale() + && mData != null && mData.equals( detailLevel.getData() ) + && mLevelType != null && mLevelType.equals( detailLevel.getLevelType() ); + } + return false; + } + + @Override + public int hashCode() { + //TODO should this hashCode mix with the mLevelType hashcode? + long bits = (Double.doubleToLongBits( getScale() ) * 43); + return (((int) bits) ^ ((int) (bits >> 32))); + } + + public static class StateNotComputedException extends IllegalStateException { + public StateNotComputedException() { + super( "Grid has not been computed; " + + "you must call computeCurrentState at some point prior to calling " + + "getVisibleTilesFromLastViewportComputation." ); + } + } + + private static class StateSnapshot { + public int rowStart; + public int rowEnd; + public int columnStart; + public int columnEnd; + public DetailLevel detailLevel; + + public StateSnapshot( DetailLevel detailLevel, int rowStart, int rowEnd, int columnStart, int columnEnd ) { + this.detailLevel = detailLevel; + this.rowStart = rowStart; + this.rowEnd = rowEnd; + this.columnStart = columnStart; + this.columnEnd = columnEnd; + } + + public boolean equals( Object o ) { + if( o == this ) { + return true; + } + if( o instanceof StateSnapshot ) { + StateSnapshot stateSnapshot = (StateSnapshot) o; + return detailLevel.equals( stateSnapshot.detailLevel ) + && rowStart == stateSnapshot.rowStart + && columnStart == stateSnapshot.columnStart + && rowEnd == stateSnapshot.rowEnd + && columnEnd == stateSnapshot.columnEnd; + } + return false; + } + } + + +} \ No newline at end of file diff --git a/tileview/src/main/java/com/qozix/tileview/detail/DetailLevelManager.java b/tileview/src/main/java/com/qozix/tileview/detail/DetailLevelManager.java new file mode 100644 index 00000000..99bf0707 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/detail/DetailLevelManager.java @@ -0,0 +1,236 @@ +package com.qozix.tileview.detail; + +import android.graphics.Rect; + +import com.qozix.tileview.geom.FloatMathHelper; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +public class DetailLevelManager { + + public interface LevelType { + + } + + private Map> mDetailLevelLinkedList = new HashMap<>(); + + private DetailLevelChangeListener mDetailLevelChangeListener; + + protected float mScale = 1; + + private int mBaseWidth; + private int mBaseHeight; + private int mScaledWidth; + private int mScaledHeight; + + private boolean mDetailLevelLocked; + + private int mPadding; + + private Rect mViewport = new Rect(); + private Rect mComputedViewport = new Rect(); + private Rect mComputedScaledViewport = new Rect(); + + private boolean dirty; + private LevelType mCurrentLevelType; + + private DetailLevel mCurrentDetailLevel; + + public DetailLevelManager() { + update(); + } + + public float getScale() { + return mScale; + } + + public void setScale( float scale ) { + mScale = scale; + update(); + } + + public int getBaseWidth() { + return mBaseWidth; + } + + public int getBaseHeight() { + return mBaseHeight; + } + + public int getScaledWidth() { + return mScaledWidth; + } + + public int getScaledHeight() { + return mScaledHeight; + } + + public void setSize( int width, int height ) { + mBaseWidth = width; + mBaseHeight = height; + update(); + } + + public void setLevelType(LevelType levelType) { + if( this.mCurrentLevelType == levelType ) return; + this.mCurrentLevelType = levelType; + this.dirty = true; + this.update(); + } + + public void setDetailLevelChangeListener( DetailLevelChangeListener detailLevelChangeListener ) { + mDetailLevelChangeListener = detailLevelChangeListener; + } + + /** + * "pads" the viewport by the number of pixels passed. e.g., setViewportPadding( 100 ) instructs the + * DetailManager to interpret it's actual viewport offset by 100 pixels in each direction (top, left, + * right, bottom), so more tiles will qualify for "visible" status when intersections are calculated. + * + * @param pixels The number of pixels to pad the viewport by. + */ + public void setViewportPadding( int pixels ) { + mPadding = pixels; + updateComputedViewport(); + } + + public void updateViewport( int left, int top, int right, int bottom ) { + mViewport.set( left, top, right, bottom ); + updateComputedViewport(); + } + + private void updateComputedViewport() { + mComputedViewport.set( mViewport ); + mComputedViewport.top -= mPadding; + mComputedViewport.left -= mPadding; + mComputedViewport.bottom += mPadding; + mComputedViewport.right += mPadding; + } + + public Rect getViewport() { + return mViewport; + } + + public Rect getComputedViewport() { + return mComputedViewport; + } + + public Rect getComputedScaledViewport(float scale){ + mComputedScaledViewport.set( + (int) (mComputedViewport.left * scale), + (int) (mComputedViewport.top * scale), + (int) (mComputedViewport.right * scale), + (int) (mComputedViewport.bottom * scale) + ); + return mComputedScaledViewport; + } + + /** + * While the detail level is locked (after this method is invoked, and before unlockDetailLevel is invoked), + * the DetailLevel will not change, and the current DetailLevel will be scaled beyond the normal + * bounds. Normally, during any scale change the DetailManager searches for the DetailLevel with + * a registered scale closest to the defined mScale. While locked, this does not occur. + */ + public void lockDetailLevel() { + mDetailLevelLocked = true; + } + + /** + * Unlocks a DetailLevel locked with lockDetailLevel. + */ + public void unlockDetailLevel() { + mDetailLevelLocked = false; + } + + public boolean getIsLocked() { + return mDetailLevelLocked; + } + + public void resetDetailLevels() { + mDetailLevelLinkedList.clear(); + update(); + } + + public DetailLevel getCurrentDetailLevel() { + return mCurrentDetailLevel; + } + + protected void update() { + if(this.dirty || !mDetailLevelLocked ) { + DetailLevel matchingLevel = getDetailLevelForScale(); + if( matchingLevel != null ) { + this.dirty = !matchingLevel.equals( mCurrentDetailLevel ); + mCurrentDetailLevel = matchingLevel; + } + } + mScaledWidth = FloatMathHelper.scale( mBaseWidth, mScale ); + mScaledHeight = FloatMathHelper.scale( mBaseHeight, mScale ); + if( this.dirty ) { + if( mDetailLevelChangeListener != null ) { + mDetailLevelChangeListener.onDetailLevelChanged( mCurrentDetailLevel ); + } + this.dirty = false; + } + } + + public void addDetailLevel( float scale, Object data, int tileWidth, int tileHeight) { + this.addDetailLevel(scale, data, tileWidth, tileHeight, null); + } + + public void addDetailLevel( float scale, Object data, int tileWidth, int tileHeight, LevelType levelType) { + DetailLevel detailLevel = new DetailLevel( this, scale, data, tileWidth, tileHeight, levelType); + LinkedList levels = mDetailLevelLinkedList.get(levelType); + if( levels != null){ + if(levels.contains( detailLevel ) ) { + return; + } else { + levels.add( detailLevel ); + Collections.sort( levels ); + } + } else { + //a list with one item does not need to be sorted, just add the new list to the map + levels = new LinkedList<>(); + levels.add(detailLevel); + mDetailLevelLinkedList.put(levelType, levels); + } + update(); + } + + public DetailLevel getDetailLevelForScale() { + LinkedList levels = mDetailLevelLinkedList.get(mCurrentLevelType); + if( levels == null || levels.size() == 0 ) { + return null; + } + if( levels.size() == 1 ) { + return levels.get( 0 ); + } + DetailLevel match = null; + int index = levels.size() - 1; + for( int i = index; i >= 0; i-- ) { + match = levels.get( i ); + if( match.getScale() < mScale ) { + if( i < index ) { + match = levels.get( i + 1 ); + } + break; + } + } + return match; + } + + public void invalidateAll(){ + for( LinkedList levels : mDetailLevelLinkedList.values() ) { + for (DetailLevel detailLevel : levels) { + detailLevel.invalidate(); + } + } + } + + public interface DetailLevelChangeListener { + void onDetailLevelChanged( DetailLevel detailLevel ); + } + +} diff --git a/tileview/src/main/java/com/qozix/tileview/geom/CoordinateTranslater.java b/tileview/src/main/java/com/qozix/tileview/geom/CoordinateTranslater.java new file mode 100644 index 00000000..c8563b6f --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/geom/CoordinateTranslater.java @@ -0,0 +1,233 @@ +package com.qozix.tileview.geom; + +import android.graphics.Path; + +import java.util.List; + +/** + * Helper class to translate relative coordinates into absolute pixels. + * Note that these methods always take arguments x and y in that order; + * this may be counter-intuitive since coordinates are often expressed as lat (y), lng (x). + * When using translation methods of this class, pass latitude and longitude in the reverse + * order: translationMethod( longitude, latitude ) + */ +public class CoordinateTranslater { + + private double mLeft; + private double mTop; + private double mRight; + private double mBottom; + + private double mDiffX; + private double mDiffY; + + private int mWidth; + private int mHeight; + + private boolean mHasDefinedBounds; + + /** + * Set size in pixels of the image at 100% scale. + * + * @param width Width of the tiled image in pixels. + * @param height Height of the tiled image in pixels. + */ + public void setSize( int width, int height ) { + mWidth = width; + mHeight = height; + } + + /** + * Define arbitrary bound coordinates to the edges of the tiled image (e.g., latitude and longitude). + * + * @param left The left boundary (e.g., west longitude). + * @param top The top boundary (e.g., north latitude). + * @param right The right boundary (e.g., east longitude). + * @param bottom The bottom boundary (e.g., south latitude). + */ + public void setBounds( double left, double top, double right, double bottom ) { + mHasDefinedBounds = true; + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + mDiffX = mRight - mLeft; + mDiffY = mBottom - mTop; + } + + public void unsetBounds() { + mHasDefinedBounds = false; + mLeft = 0; + mTop = 0; + mRight = mWidth; + mBottom = mHeight; + mDiffX = mWidth; + mDiffY = mHeight; + } + + /** + * Translate a relative X position to an absolute pixel value. + * + * @param x The relative X position (e.g., longitude) to translate to absolute pixels. + * @return The translated position as a pixel value. + */ + public int translateX( double x ) { + if( !mHasDefinedBounds ) { + return (int) x; + } + double factor = (x - mLeft) / mDiffX; + return FloatMathHelper.scale( mWidth, (float) factor ); + } + + /** + * Translate a relative X position to an absolute pixel value, considering a scale value as well. + * + * @param x The relative X position (e.g., longitude) to translate to absolute pixels. + * @return The translated position as a pixel value. + */ + public int translateAndScaleX( double x, float scale ) { + return FloatMathHelper.scale( translateX( x ), scale ); + } + + /** + * Translate a relative Y position to an absolute pixel value. + * + * @param y The relative Y position (e.g., latitude) to translate to absolute pixels. + * @return The translated position as a pixel value. + */ + public int translateY( double y ) { + if( !mHasDefinedBounds ) { + return (int) y; + } + double factor = (y - mTop) / mDiffY; + return FloatMathHelper.scale( mHeight, (float) factor ); + } + + /** + * Translate a relative Y position to an absolute pixel value, considering a scale value as well. + * + * @param y The relative Y position (e.g., latitude) to translate to absolute pixels. + * @return The translated position as a pixel value. + */ + public int translateAndScaleY( double y, float scale ) { + return FloatMathHelper.scale( translateY( y ), scale ); + } + + /** + * Translate an absolute pixel value to a relative coordinate. + * + * @param x The x value to be translated. + * @return The relative value of the x coordinate supplied. + */ + public double translateAbsoluteToRelativeX( float x ) { + return mLeft + ( x * mDiffX / mWidth ); + } + + /** + * Pipes to {@link #translateAbsoluteToRelativeX( float )} + */ + public double translateAbsoluteToRelativeX( int x ) { + return translateAbsoluteToRelativeX( (float) x ); + } + + /** + * Convenience method to translate an absolute pixel value to a relative coordinate, while considering a scale value. + * + * @param x The x value to be translated. + * @param scale The scale to apply. + * @return The relative value of the x coordinate supplied. + */ + public double translateAndScaleAbsoluteToRelativeX( float x, float scale ) { + return translateAbsoluteToRelativeX( x / scale ); + } + + /** + * @see #translateAndScaleAbsoluteToRelativeX(float, float) + */ + public double translateAndScaleAbsoluteToRelativeX( int x, float scale ) { + return translateAbsoluteToRelativeX( x / scale ); + } + + /** + * Translate an absolute pixel value to a relative coordinate. + * + * @param y The y value to be translated. + * @return The relative value of the y coordinate supplied. + */ + public double translateAbsoluteToRelativeY( float y ) { + return mTop + ( y * mDiffY / mHeight ); + } + + /** + * Pipes to {@link #translateAbsoluteToRelativeY( float )} + */ + public double translateAbsoluteToRelativeY( int y ) { + return translateAbsoluteToRelativeY( (float) y ); + } + + /** + * Convenience method to translate an absolute pixel value to a relative coordinate, while considering a scale value. + * + * @param y The y value to be translated. + * @param scale The scale to apply. + * @return The relative value of the y coordinate supplied. + */ + public double translateAndScaleAbsoluteToRelativeY( float y, float scale ) { + return translateAbsoluteToRelativeY( y / scale ); + } + + /** + * @see #translateAndScaleAbsoluteToRelativeY(float, float) + */ + public double translateAndScaleAbsoluteToRelativeY( int y, float scale ) { + return translateAbsoluteToRelativeY( y / scale ); + } + + /** + * Determines if a given position (x, y) falls within the bounds defined, or the absolute pixel size of the image, if arbitrary bounds are nor supplied. + * + * @param x The x value of the coordinate to test. + * @param y The y value of the coordinate to test. + * @return True if the point falls within the defined area; false if not. + */ + public boolean contains( double x, double y ) { + double top = mTop; + double bottom = mBottom; + double left = mLeft; + double right = mRight; + if( mTop > mBottom ) { + top = mBottom; + bottom = mTop; + } + if( mLeft > mRight ) { + left = mRight; + right = mLeft; + } + return y >= top + && y <= bottom + && x >= left + && x <= right; + } + + /** + * Convenience method to convert a List of coordinates (pairs of doubles) to a Path instance. + * + * @param positions List of coordinates (pairs of doubles). + * @param shouldClose True if the path should be closed at the end of this operation. + * @return The Path instance created from the positions supplied. + */ + public Path pathFromPositions( List positions, boolean shouldClose ) { + Path path = new Path(); + double[] start = positions.get( 0 ); + path.moveTo( translateX( start[0] ), translateY( start[1] ) ); + for( int i = 1; i < positions.size(); i++ ) { + double[] position = positions.get( i ); + path.lineTo( translateX( position[0] ), translateY( position[1] ) ); + } + if( shouldClose ) { + path.close(); + } + return path; + } + +} \ No newline at end of file diff --git a/tileview/src/main/java/com/qozix/tileview/geom/FloatMathHelper.java b/tileview/src/main/java/com/qozix/tileview/geom/FloatMathHelper.java new file mode 100644 index 00000000..4de42648 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/geom/FloatMathHelper.java @@ -0,0 +1,13 @@ +package com.qozix.tileview.geom; + +/** + * @author Mike Dunn, 10/23/15. + */ +public class FloatMathHelper { + public static int scale( int base, float multiplier ) { + return (int) ((base * multiplier) + 0.5); + } + public static int unscale( int base, float multiplier ) { + return (int) ((base / multiplier) + 0.5); + } +} diff --git a/tileview/src/main/java/com/qozix/tileview/graphics/BitmapProvider.java b/tileview/src/main/java/com/qozix/tileview/graphics/BitmapProvider.java new file mode 100644 index 00000000..9c6ca635 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/graphics/BitmapProvider.java @@ -0,0 +1,19 @@ +package com.qozix.tileview.graphics; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.qozix.tileview.tiles.Tile; + +/** + * This interface provides the bitmap data necessary to draw each tile. It will be run + * in a worker (non-UI) thread, so network operations are safe here. How the bitmap is generated + * is entirely up to you - assets, resources, file system, network access, SVG, drawn dynamically + * with Canvas instances, it doesn't matter - the method is passed a Tile instance (with information + * like column and row numbers, including the arbitrary data object passed for the detail level), + * and a Context instance to help with things like file i/o - as long as the + * getBitmap method returns a bitmap, everything will run along nicely. + */ +public interface BitmapProvider { + Bitmap getBitmap( Tile tile, Context context ); +} diff --git a/tileview/src/main/java/com/qozix/tileview/graphics/BitmapProviderAssets.java b/tileview/src/main/java/com/qozix/tileview/graphics/BitmapProviderAssets.java new file mode 100644 index 00000000..479cf9a3 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/graphics/BitmapProviderAssets.java @@ -0,0 +1,52 @@ +package com.qozix.tileview.graphics; + +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import com.qozix.tileview.tiles.Tile; + +import java.io.InputStream; + +/** + * This is a very simple implementation of BitmapProvider, using a formatted string to find + * an asset by filename, and built-in methods to decode the bitmap data. + * + * Feel free to use your own implementation here, where you might implement a favorite library like + * Picasso, or add your own disk-caching scheme, etc. + */ + +public class BitmapProviderAssets implements BitmapProvider { + + private static final BitmapFactory.Options OPTIONS = new BitmapFactory.Options(); + + static { + OPTIONS.inPreferredConfig = Bitmap.Config.RGB_565; + } + + @Override + public Bitmap getBitmap( Tile tile, Context context ) { + Object data = tile.getData(); + if( data instanceof String ) { + String unformattedFileName = (String) tile.getData(); + String formattedFileName = String.format( unformattedFileName, tile.getColumn(), tile.getRow() ); + AssetManager assetManager = context.getAssets(); + try { + InputStream inputStream = assetManager.open( formattedFileName ); + if( inputStream != null ) { + try { + return BitmapFactory.decodeStream( inputStream, null, OPTIONS ); + } catch( OutOfMemoryError | Exception e ) { + // this is probably an out of memory error - you can try sleeping (this method won't be called in the UI thread) or try again (or give up) + } + } + } catch( Exception e ) { + // this is probably an IOException, meaning the file can't be found + } + } + return null; + } + + +} diff --git a/tileview/src/main/java/com/qozix/tileview/hotspots/HotSpot.java b/tileview/src/main/java/com/qozix/tileview/hotspots/HotSpot.java new file mode 100644 index 00000000..de5dec59 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/hotspots/HotSpot.java @@ -0,0 +1,38 @@ +package com.qozix.tileview.hotspots; + +import android.graphics.Region; + +public class HotSpot extends Region { + + private Object mTag; + private HotSpotTapListener mHotSpotTapListener; + + public Object getTag() { + return mTag; + } + + public void setTag( Object object ) { + mTag = object; + } + + public void setHotSpotTapListener( HotSpotTapListener hotSpotTapListener ) { + mHotSpotTapListener = hotSpotTapListener; + } + + public HotSpotTapListener getHotSpotTapListener() { + return mHotSpotTapListener; + } + + public interface HotSpotTapListener { + void onHotSpotTap( HotSpot hotSpot, int x, int y ); + } + + @Override + public boolean equals( Object obj ) { + if( obj instanceof HotSpot ) { + HotSpot hotSpot = (HotSpot) obj; + return super.equals( hotSpot ) && hotSpot.mHotSpotTapListener == mHotSpotTapListener; + } + return false; + } +} diff --git a/tileview/src/main/java/com/qozix/tileview/hotspots/HotSpotManager.java b/tileview/src/main/java/com/qozix/tileview/hotspots/HotSpotManager.java new file mode 100644 index 00000000..07994358 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/hotspots/HotSpotManager.java @@ -0,0 +1,65 @@ +package com.qozix.tileview.hotspots; + +import com.qozix.tileview.geom.FloatMathHelper; + +import java.util.Iterator; +import java.util.LinkedList; + +public class HotSpotManager { + + private float mScale = 1; + + private HotSpot.HotSpotTapListener mHotSpotTapListener; + private LinkedList mHotSpots = new LinkedList(); + + public float getScale() { + return mScale; + } + + public void setScale( float scale ) { + mScale = scale; + } + + public void addHotSpot( HotSpot hotSpot ) { + mHotSpots.add( hotSpot ); + } + + public void removeHotSpot( HotSpot hotSpot ) { + mHotSpots.remove( hotSpot ); + } + + public void setHotSpotTapListener( HotSpot.HotSpotTapListener hotSpotTapListener ) { + mHotSpotTapListener = hotSpotTapListener; + } + + public void clear() { + mHotSpots.clear(); + } + + private HotSpot getMatch( int x, int y ) { + int scaledX = FloatMathHelper.unscale( x, mScale ); + int scaledY = FloatMathHelper.unscale( y, mScale ); + Iterator iterator = mHotSpots.descendingIterator(); + while( iterator.hasNext() ) { + HotSpot hotSpot = iterator.next(); + if( hotSpot.contains( scaledX, scaledY ) ) { + return hotSpot; + } + } + return null; + } + + public void processHit( int x, int y ) { + HotSpot hotSpot = getMatch( x, y ); + if( hotSpot != null ) { + HotSpot.HotSpotTapListener spotListener = hotSpot.getHotSpotTapListener(); + if( spotListener != null ) { + spotListener.onHotSpotTap( hotSpot, x, y ); + } + if( mHotSpotTapListener != null ) { + mHotSpotTapListener.onHotSpotTap( hotSpot, x, y ); + } + } + } + +} diff --git a/tileview/src/main/java/com/qozix/tileview/markers/CalloutLayout.java b/tileview/src/main/java/com/qozix/tileview/markers/CalloutLayout.java new file mode 100644 index 00000000..4b52a5cb --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/markers/CalloutLayout.java @@ -0,0 +1,15 @@ +package com.qozix.tileview.markers; + +import android.content.Context; + +/** + * @deprecated In the next major version, instances of this class will be replaced + * by instances of MarkerLayout + */ +public class CalloutLayout extends MarkerLayout { + + public CalloutLayout( Context context ) { + super( context ); + } + +} diff --git a/tileview/src/main/java/com/qozix/tileview/markers/MarkerLayout.java b/tileview/src/main/java/com/qozix/tileview/markers/MarkerLayout.java new file mode 100644 index 00000000..86cdfff2 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/markers/MarkerLayout.java @@ -0,0 +1,269 @@ +package com.qozix.tileview.markers; + +import android.content.Context; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +import com.qozix.tileview.geom.FloatMathHelper; + +public class MarkerLayout extends ViewGroup { + + private float mScale = 1; + + private float mAnchorX; + private float mAnchorY; + + private MarkerTapListener mMarkerTapListener; + + public MarkerLayout( Context context ) { + super( context ); + setClipChildren( false ); + } + + /** + * Sets the anchor values used by this ViewGroup if it's children do not + * have anchor values supplied directly (via individual LayoutParams). + * + * @param aX x-axis anchor value (offset computed by multiplying this value by the child's width). + * @param aY y-axis anchor value (offset computed by multiplying this value by the child's height). + */ + public void setAnchors( float aX, float aY ) { + mAnchorX = aX; + mAnchorY = aY; + requestLayout(); + } + + /** + * Sets the scale (0-1) of the MarkerLayout. + * + * @param scale The new value of the MarkerLayout scale. + */ + public void setScale( float scale ) { + mScale = scale; + requestLayout(); + } + + /** + * Retrieves the current scale of the MarkerLayout. + * + * @return The current scale of the MarkerLayout. + */ + public float getScale() { + return mScale; + } + + public View addMarker( View view, int x, int y, Float aX, Float aY ) { + LayoutParams layoutParams = new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, x, y, aX, aY ); + return addMarker( view, layoutParams ); + } + + public View addMarker( View view, LayoutParams params ) { + addView( view, params ); + return view; + } + + public void moveMarker( View view, int x, int y ) { + LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + layoutParams.x = x; + layoutParams.y = y; + moveMarker( view, layoutParams ); + } + + public void moveMarker( View view, LayoutParams params ) { + if( indexOfChild( view ) > -1 ) { + view.setLayoutParams( params ); + requestLayout(); + } + } + + public void removeMarker( View view ) { + removeView( view ); + } + + public void setMarkerTapListener( MarkerTapListener markerTapListener ) { + mMarkerTapListener = markerTapListener; + } + + private View getViewFromTap( int x, int y ) { + for( int i = getChildCount() - 1; i >= 0; i-- ) { + View child = getChildAt( i ); + LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); + Rect hitRect = layoutParams.getHitRect(); + if( hitRect.contains( x, y ) ) { + return child; + } + } + return null; + } + + public void processHit( int x, int y ) { + if( mMarkerTapListener != null ) { + View view = getViewFromTap( x, y ); + if( view != null ) { + mMarkerTapListener.onMarkerTap( view, x, y ); + } + } + } + + @Override + protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) { + measureChildren( widthMeasureSpec, heightMeasureSpec ); + for( int i = 0; i < getChildCount(); i++ ) { + View child = getChildAt( i ); + if( child.getVisibility() != GONE ) { + MarkerLayout.LayoutParams layoutParams = (MarkerLayout.LayoutParams) child.getLayoutParams(); + // get anchor offsets + float widthMultiplier = (layoutParams.anchorX == null) ? mAnchorX : layoutParams.anchorX; + float heightMultiplier = (layoutParams.anchorY == null) ? mAnchorY : layoutParams.anchorY; + // actual sizes of children + int actualWidth = child.getMeasuredWidth(); + int actualHeight = child.getMeasuredHeight(); + // offset dimensions by anchor values + float widthOffset = actualWidth * widthMultiplier; + float heightOffset = actualHeight * heightMultiplier; + // get offset position + int scaledX = FloatMathHelper.scale( layoutParams.x, mScale ); + int scaledY = FloatMathHelper.scale( layoutParams.y, mScale ); + // save computed values + layoutParams.mLeft = (int) (scaledX + widthOffset); + layoutParams.mTop = (int) (scaledY + heightOffset); + layoutParams.mRight = layoutParams.mLeft + actualWidth; + layoutParams.mBottom = layoutParams.mTop + actualHeight; + } + } + int availableWidth = MeasureSpec.getSize( widthMeasureSpec ); + int availableHeight = MeasureSpec.getSize( heightMeasureSpec ); + setMeasuredDimension( availableWidth, availableHeight ); + } + + @Override + protected void onLayout( boolean changed, int l, int t, int r, int b ) { + for( int i = 0; i < getChildCount(); i++ ) { + View child = getChildAt( i ); + if( child.getVisibility() != GONE ) { + LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); + child.layout( layoutParams.mLeft, layoutParams.mTop, layoutParams.mRight, layoutParams.mBottom ); + } + } + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0 ); + } + + @Override + protected boolean checkLayoutParams( ViewGroup.LayoutParams layoutParams ) { + return layoutParams instanceof LayoutParams; + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams( ViewGroup.LayoutParams layoutParams ) { + return new LayoutParams( layoutParams ); + } + + /** + * Per-child layout information associated with AnchorLayout. + */ + public static class LayoutParams extends ViewGroup.LayoutParams { + + /** + * The absolute left position of the child in pixels. + */ + public int x = 0; + + /** + * The absolute right position of the child in pixels. + */ + public int y = 0; + + /** + * Float value to determine the child's horizontal offset. + * This float is multiplied by the child's width. + * If null, the containing AnchorLayout's anchor values will be used. + */ + public Float anchorX = null; + + /** + * Float value to determine the child's vertical offset. + * This float is multiplied by the child's height. + * If null, the containing AnchorLayout's anchor values will be used. + */ + public Float anchorY = null; + + private int mTop; + private int mLeft; + private int mBottom; + private int mRight; + + private Rect mHitRect; + + private Rect getHitRect() { + if( mHitRect == null ) { + mHitRect = new Rect(); + } + mHitRect.left = mLeft; + mHitRect.top = mTop; + mHitRect.right = mRight; + mHitRect.bottom = mBottom; + return mHitRect; + } + + /** + * Copy constructor. + * + * @param source LayoutParams instance to copy properties from. + */ + public LayoutParams( ViewGroup.LayoutParams source ) { + super( source ); + } + + /** + * Creates a new set of layout parameters with the specified values. + * + * @param width Information about how wide the view wants to be. This should generally be WRAP_CONTENT or a fixed value. + * @param height Information about how tall the view wants to be. This should generally be WRAP_CONTENT or a fixed value. + */ + public LayoutParams( int width, int height ) { + super( width, height ); + } + + /** + * Creates a new set of layout parameters with the specified values. + * + * @param width Information about how wide the view wants to be. This should generally be WRAP_CONTENT or a fixed value. + * @param height Information about how tall the view wants to be. This should generally be WRAP_CONTENT or a fixed value. + * @param left Sets the absolute x value of the view's position in pixels. + * @param top Sets the absolute y value of the view's position in pixels. + */ + public LayoutParams( int width, int height, int left, int top ) { + super( width, height ); + x = left; + y = top; + } + + /** + * Creates a new set of layout parameters with the specified values. + * + * @param width Information about how wide the view wants to be. This should generally be WRAP_CONTENT or a fixed value. + * @param height Information about how tall the view wants to be. This should generally be WRAP_CONTENT or a fixed value. + * @param left Sets the absolute x value of the view's position in pixels. + * @param top Sets the absolute y value of the view's position in pixels. + * @param anchorLeft Sets the relative horizontal offset of the view (multiplied by the view's width). + * @param anchorTop Sets the relative vertical offset of the view (multiplied by the view's height). + */ + public LayoutParams( int width, int height, int left, int top, Float anchorLeft, Float anchorTop ) { + super( width, height ); + x = left; + y = top; + anchorX = anchorLeft; + anchorY = anchorTop; + } + + } + + public interface MarkerTapListener { + void onMarkerTap( View view, int x, int y ); + } +} diff --git a/tileview/src/main/java/com/qozix/tileview/paths/CompositePathView.java b/tileview/src/main/java/com/qozix/tileview/paths/CompositePathView.java new file mode 100644 index 00000000..3fe0625d --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/paths/CompositePathView.java @@ -0,0 +1,110 @@ +package com.qozix.tileview.paths; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.view.View; + +import java.util.HashSet; + +public class CompositePathView extends View { + + private static final int DEFAULT_STROKE_COLOR = 0xFF000000; + private static final int DEFAULT_STROKE_WIDTH = 10; + + private float mScale = 1; + + private boolean mShouldDraw = true; + + private Path mRecyclerPath = new Path(); + private Matrix mMatrix = new Matrix(); + + private HashSet mDrawablePaths = new HashSet(); + + private Paint mDefaultPaint = new Paint(); + + { + mDefaultPaint.setStyle( Paint.Style.STROKE ); + mDefaultPaint.setColor( DEFAULT_STROKE_COLOR ); + mDefaultPaint.setStrokeWidth( DEFAULT_STROKE_WIDTH ); + mDefaultPaint.setAntiAlias( true ); + } + + public CompositePathView( Context context ) { + super( context ); + setWillNotDraw( false ); + } + + public float getScale() { + return mScale; + } + + public void setScale( float scale ) { + mScale = scale; + mMatrix.setScale( mScale, mScale ); + invalidate(); + } + + public Paint getDefaultPaint() { + return mDefaultPaint; + } + + public DrawablePath addPath( Path path, Paint paint ) { + if( paint == null ) { + paint = mDefaultPaint; + } + DrawablePath DrawablePath = new DrawablePath(); + DrawablePath.path = path; + DrawablePath.paint = paint; + return addPath( DrawablePath ); + } + + public DrawablePath addPath( DrawablePath DrawablePath ) { + mDrawablePaths.add( DrawablePath ); + invalidate(); + return DrawablePath; + } + + public void removePath( DrawablePath path ) { + mDrawablePaths.remove( path ); + invalidate(); + } + + public void clear() { + mDrawablePaths.clear(); + invalidate(); + } + + public void setShouldDraw( boolean shouldDraw ) { + mShouldDraw = shouldDraw; + invalidate(); + } + + @Override + public void onDraw( Canvas canvas ) { + if( mShouldDraw ) { + for( DrawablePath drawablePath : mDrawablePaths ) { + mRecyclerPath.set( drawablePath.path ); + mRecyclerPath.transform( mMatrix ); + canvas.drawPath( mRecyclerPath, drawablePath.paint ); + } + } + super.onDraw( canvas ); + } + + public static class DrawablePath { + + /** + * The path that this drawable will follow. + */ + public Path path; + + /** + * The paint to be used for this path. + */ + public Paint paint; + + } +} diff --git a/tileview/src/main/java/com/qozix/tileview/tiles/Tile.java b/tileview/src/main/java/com/qozix/tileview/tiles/Tile.java new file mode 100644 index 00000000..7c2a651d --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/tiles/Tile.java @@ -0,0 +1,297 @@ +package com.qozix.tileview.tiles; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.view.animation.AnimationUtils; + +import com.qozix.tileview.detail.DetailLevel; +import com.qozix.tileview.detail.DetailLevelManager; +import com.qozix.tileview.geom.FloatMathHelper; +import com.qozix.tileview.graphics.BitmapProvider; + +import java.lang.ref.WeakReference; +import java.util.Objects; + +public class Tile { + + public enum State { + UNASSIGNED, + PENDING_DECODE, + DECODED + } + + private static final int DEFAULT_TRANSITION_DURATION = 200; + + private State mState = State.UNASSIGNED; + + private int mWidth; + private int mHeight; + private int mLeft; + private int mTop; + private int mRight; + private int mBottom; + + private float mProgress; + + private int mRow; + private int mColumn; + + private float mDetailLevelScale; + + private Object mData; + private Bitmap mBitmap; + + private Rect mIntrinsicRect = new Rect(); + private Rect mBaseRect = new Rect(); + private Rect mRelativeRect = new Rect(); + private Rect mScaledRect = new Rect(); + + public Long mRenderTimeStamp; + + private boolean mTransitionsEnabled; + + private int mTransitionDuration = DEFAULT_TRANSITION_DURATION; + + private Paint mPaint; + + private DetailLevel mDetailLevel; + + private WeakReference mTileRenderRunnableWeakReference; + + public Tile( int column, int row, int width, int height, Object data, DetailLevel detailLevel ) { + mRow = row; + mColumn = column; + mWidth = width; + mHeight = height; + mLeft = column * width; + mTop = row * height; + mRight = mLeft + mWidth; + mBottom = mTop + mHeight; + mData = data; + mDetailLevel = detailLevel; + mDetailLevelScale = mDetailLevel.getScale(); + mIntrinsicRect.set( 0, 0, mWidth, mHeight ); + mBaseRect.set( mLeft, mTop, mRight, mBottom ); + mRelativeRect.set( + FloatMathHelper.unscale( mLeft, mDetailLevelScale ), + FloatMathHelper.unscale( mTop, mDetailLevelScale ), + FloatMathHelper.unscale( mRight, mDetailLevelScale ), + FloatMathHelper.unscale( mBottom, mDetailLevelScale ) + ); + mScaledRect.set( mRelativeRect ); + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + public int getLeft() { + return mLeft; + } + + public int getTop() { + return mTop; + } + + public int getRow() { + return mRow; + } + + public int getColumn() { + return mColumn; + } + + public Object getData() { + return mData; + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public boolean hasBitmap() { + return mBitmap != null; + } + + public Rect getBaseRect() { + return mBaseRect; + } + + public Rect getRelativeRect() { + return mRelativeRect; + } + + /** + * @deprecated + * @return + */ + public float getRendered() { + return mProgress; + } + + /** + * @deprecated + */ + public void stampTime() { + // no op + } + + public Rect getScaledRect( float scale ) { + mScaledRect.set( + (int) (mRelativeRect.left * scale), + (int) (mRelativeRect.top * scale), + (int) (mRelativeRect.right * scale), + (int) (mRelativeRect.bottom * scale) + ); + return mScaledRect; + } + + public void setTransitionDuration( int transitionDuration ) { + mTransitionDuration = transitionDuration; + } + + public State getState() { + return mState; + } + + public void setState( State state ) { + mState = state; + } + + public void execute( TileRenderPoolExecutor tileRenderPoolExecutor ) { + if(mState != State.UNASSIGNED){ + return; + } + mState = State.PENDING_DECODE; + TileRenderRunnable runnable = new TileRenderRunnable(); + mTileRenderRunnableWeakReference = new WeakReference<>( runnable ); + runnable.setTile( this ); + runnable.setTileRenderPoolExecutor( tileRenderPoolExecutor ); + tileRenderPoolExecutor.execute( runnable ); + } + + public void computeProgress(){ + if( !mTransitionsEnabled ) { + return; + } + if( mRenderTimeStamp == null ) { + mRenderTimeStamp = AnimationUtils.currentAnimationTimeMillis(); + mProgress = 0; + return; + } + double elapsed = AnimationUtils.currentAnimationTimeMillis() - mRenderTimeStamp; + mProgress = (float) Math.min( 1, elapsed / mTransitionDuration ); + if( mProgress == 1f ) { + mRenderTimeStamp = null; + mTransitionsEnabled = false; + } + } + + public void setTransitionsEnabled( boolean enabled ) { + mTransitionsEnabled = enabled; + if( enabled ) { + mProgress = 0f; + } + } + + public DetailLevel getDetailLevel() { + return mDetailLevel; + } + + public boolean getIsDirty() { + return mTransitionsEnabled && mProgress < 1f; + } + + public Paint getPaint() { + if( !mTransitionsEnabled ) { + return mPaint = null; + } + if( mPaint == null ) { + mPaint = new Paint(); + } + mPaint.setAlpha( (int) (255 * mProgress) ); + return mPaint; + } + + void generateBitmap( Context context, BitmapProvider bitmapProvider ) { + if( mBitmap != null ) { + return; + } + mBitmap = bitmapProvider.getBitmap( this, context ); + mState = State.DECODED; + } + + /** + * Deprecated + * @param b + */ + void destroy( boolean b ) { + reset(); + } + + void reset() { + if( mState == State.PENDING_DECODE ) { + if ( mTileRenderRunnableWeakReference != null ) { + TileRenderRunnable runnable = mTileRenderRunnableWeakReference.get(); + if( runnable != null ) { + runnable.cancel( true ); + } + } + } + mState = State.UNASSIGNED; + mRenderTimeStamp = null; + if( mBitmap != null && !mBitmap.isRecycled() ) { + mBitmap.recycle(); + } + mBitmap = null; + } + + /** + * @param canvas The canvas the tile's bitmap should be drawn into + */ + public void draw( Canvas canvas ) { + if( mBitmap != null ) { + canvas.drawBitmap( mBitmap, mIntrinsicRect, mRelativeRect, getPaint() ); + } + } + + @Override + public int hashCode() { + int hash = 17; + hash = hash * 31 + getColumn(); + hash = hash * 31 + getRow(); + hash = hash * 31 + (int) ( 1000 * getDetailLevel().getScale() ); + DetailLevelManager.LevelType levelType = getDetailLevel().getLevelType(); + if( levelType != null ) hash = hash * 31 + levelType.hashCode(); + return hash; + } + + @Override + public boolean equals( Object o ) { + if( this == o ) { + return true; + } + if( o instanceof Tile ) { + Tile m = (Tile) o; + return m.getRow() == getRow() + && m.getColumn() == getColumn() + && m.getDetailLevel().getScale() == getDetailLevel().getScale() + && ( m.getDetailLevel().getLevelType() == null + ? getDetailLevel().getLevelType() == null + : m.getDetailLevel().getLevelType().equals( getDetailLevel().getLevelType() ) ); + } + return false; + } + + public String toShortString(){ + return mColumn + ":" + mRow; + } + +} diff --git a/tileview/src/main/java/com/qozix/tileview/tiles/TileCanvasViewGroup.java b/tileview/src/main/java/com/qozix/tileview/tiles/TileCanvasViewGroup.java new file mode 100644 index 00000000..d75c80c4 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/tiles/TileCanvasViewGroup.java @@ -0,0 +1,489 @@ +package com.qozix.tileview.tiles; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.ViewGroup; + +import com.qozix.tileview.detail.DetailLevel; +import com.qozix.tileview.graphics.BitmapProvider; +import com.qozix.tileview.graphics.BitmapProviderAssets; + +import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * This class extends ViewGroup for legacy reasons, and may be changed to extend View at + * some future point; consider all ViewGroup methods deprecated. + */ +public class TileCanvasViewGroup extends ViewGroup { + + private static final int RENDER_FLAG = 1; + + public static final int DEFAULT_RENDER_BUFFER = 250; + public static final int FAST_RENDER_BUFFER = 15; + + private static final int DEFAULT_TRANSITION_DURATION = 200; + + private float mScale = 1; + + private BitmapProvider mBitmapProvider; + + private DetailLevel mDetailLevelToRender; + private DetailLevel mLastRenderedDetailLevel; + + + private boolean mRenderIsCancelled = false; + private boolean mRenderIsSuppressed = false; + private boolean mIsRendering = false; + + private boolean mShouldRecycleBitmaps = true; + + private boolean mTransitionsEnabled = true; + private int mTransitionDuration = DEFAULT_TRANSITION_DURATION; + + private TileRenderThrottleHandler mTileRenderThrottleHandler; + private TileRenderListener mTileRenderListener; + private TileRenderThrowableListener mTileRenderThrowableListener; + + private int mRenderBuffer = DEFAULT_RENDER_BUFFER; + + private TileRenderPoolExecutor mTileRenderPoolExecutor; + + private Set mTilesInCurrentViewport = new HashSet<>(); + private Set mPreviouslyDrawnTiles = new HashSet<>(); + private Set mDecodedTilesInCurrentViewport = new HashSet<>(); + + private Region mDirtyRegion = new Region(); + + private boolean mHasInvalidatedOnCleanOnce; + + public TileCanvasViewGroup( Context context ) { + super( context ); + setWillNotDraw( false ); + mTileRenderThrottleHandler = new TileRenderThrottleHandler( this ); + mTileRenderPoolExecutor = new TileRenderPoolExecutor(); + } + + @Override + protected void onLayout( boolean changed, int l, int t, int r, int b ) { + + } + + public void setScale( float factor ) { + mScale = factor; + invalidate(); + } + + public float getScale() { + return mScale; + } + + public float getInvertedScale() { + return 1f / mScale; + } + + public boolean getTransitionsEnabled() { + return mTransitionsEnabled; + } + + public void setTransitionsEnabled( boolean enabled ) { + mTransitionsEnabled = enabled; + } + + public int getTransitionDuration() { + return mTransitionDuration; + } + + public void setTransitionDuration( int duration ) { + mTransitionDuration = duration; + } + + public BitmapProvider getBitmapProvider() { + if( mBitmapProvider == null ) { + mBitmapProvider = new BitmapProviderAssets(); + } + return mBitmapProvider; + } + + public void setBitmapProvider( BitmapProvider bitmapProvider ) { + mBitmapProvider = bitmapProvider; + } + + public void setTileRenderListener( TileRenderListener tileRenderListener ) { + mTileRenderListener = tileRenderListener; + } + + public int getRenderBuffer() { + return mRenderBuffer; + } + + public void setRenderBuffer( int renderBuffer ) { + mRenderBuffer = renderBuffer; + } + + /** + * @return True if tile bitmaps should be recycled. + * @deprecated This value is no longer considered - bitmaps are always recycled when they're no longer used. + */ + public boolean getShouldRecycleBitmaps() { + return mShouldRecycleBitmaps; + } + + /** + * @param shouldRecycleBitmaps True if tile bitmaps should be recycled. + * @deprecated This value is no longer considered - bitmaps are always recycled when they're no longer used. + */ + public void setShouldRecycleBitmaps( boolean shouldRecycleBitmaps ) { + mShouldRecycleBitmaps = shouldRecycleBitmaps; + } + + public void setTileRenderThrowableListener( TileRenderThrowableListener tileRenderThrowableListener ) { + mTileRenderThrowableListener = tileRenderThrowableListener; + } + + /** + * The layout dimensions supplied to this ViewGroup will be exactly as large as the scaled + * width and height of the containing ZoomPanLayout (or TileView). However, when the canvas + * is scaled, it's clip area is also scaled - offset this by providing dimensions scaled as + * large as the smallest size the TileCanvasView might be. + */ + + public void requestRender() { + mRenderIsCancelled = false; + if( mDetailLevelToRender == null ) { + return; + } + if( !mTileRenderThrottleHandler.hasMessages( RENDER_FLAG ) ) { + mTileRenderThrottleHandler.sendEmptyMessageDelayed( RENDER_FLAG, mRenderBuffer ); + } + } + + /** + * Prevent new render tasks from starting, attempts to interrupt ongoing tasks, and will + * prevent queued tiles from begin decoded or rendered. + */ + public void cancelRender() { + mRenderIsCancelled = true; + if( mTileRenderPoolExecutor != null ) { + mTileRenderPoolExecutor.cancel(); + } + } + + /** + * Prevent new render tasks from starting, but does not cancel any ongoing operations. + */ + public void suppressRender() { + mRenderIsSuppressed = true; + } + + /** + * Enables new render tasks to start. + */ + public void resumeRender() { + mRenderIsSuppressed = false; + } + + /** + * Returns true if the TileView has threads currently decoding tile Bitmaps. + * + * @return True if the TileView has threads currently decoding tile Bitmaps. + */ + public boolean getIsRendering() { + return mIsRendering; + } + + /** + * Clears existing tiles and cancels any existing render tasks. + */ + public void clear() { + cancelRender(); + mTilesInCurrentViewport.clear(); + mPreviouslyDrawnTiles.clear(); + invalidate(); + } + + /** + * This function is now a no-op + * + * @param recentlyComputedVisibleTileSet + * @deprecated + */ + public void reconcile( Set recentlyComputedVisibleTileSet ) { + // noop + } + + void renderTiles() { + if( !mRenderIsCancelled && !mRenderIsSuppressed && mDetailLevelToRender != null ) { + beginRenderTask(); + } + } + + private Rect getComputedViewport() { + if( mDetailLevelToRender == null ) { + return null; + } + return mDetailLevelToRender.getDetailLevelManager().getComputedScaledViewport( getInvertedScale() ); + } + + private boolean establishDirtyRegion() { + boolean shouldInvalidate = false; + mDirtyRegion.set( getComputedViewport() ); + for( Tile tile : mTilesInCurrentViewport ) { + if( tile.getState() == Tile.State.DECODED ) { + tile.computeProgress(); + mDecodedTilesInCurrentViewport.add( tile ); + if( tile.getIsDirty() ) { + shouldInvalidate = true; + } else { + mDirtyRegion.op( tile.getRelativeRect(), Region.Op.DIFFERENCE ); + } + } + } + return shouldInvalidate; + } + + private boolean drawPreviousTiles( Canvas canvas ) { + boolean shouldInvalidate = false; + Iterator tilesFromLastDetailLevelIterator = mPreviouslyDrawnTiles.iterator(); + while( tilesFromLastDetailLevelIterator.hasNext() ) { + Tile tile = tilesFromLastDetailLevelIterator.next(); + Rect rect = tile.getRelativeRect(); + if( mDirtyRegion.quickReject( rect ) ) { + tilesFromLastDetailLevelIterator.remove(); + } else { + tile.computeProgress(); + tile.draw( canvas ); + shouldInvalidate |= tile.getIsDirty(); + } + } + return shouldInvalidate; + } + + private boolean drawAndClearCurrentDecodedTiles( Canvas canvas ) { + boolean shouldInvalidate = false; + for( Tile tile : mDecodedTilesInCurrentViewport ) { + // these tiles should already have progress computed by the time they get here + tile.draw( canvas ); + shouldInvalidate |= tile.getIsDirty(); + } + mDecodedTilesInCurrentViewport.clear(); + return shouldInvalidate; + } + + private void handleInvalidation( boolean shouldInvalidate ) { + if( shouldInvalidate ) { + // there's more work to do, partially opaque tiles were drawn + mHasInvalidatedOnCleanOnce = false; + invalidate(); + } else { + // if all tiles were fully opaque, we need another pass to clear our tiles from last level + if( !mHasInvalidatedOnCleanOnce ) { + mHasInvalidatedOnCleanOnce = true; + invalidate(); + } + } + } + + private void drawTilesWithoutConsideringPreviouslyDrawnLevel( Canvas canvas ) { + boolean shouldInvalidate = false; + for( Tile tile : mTilesInCurrentViewport ) { + if( tile.getState() == Tile.State.DECODED ) { + tile.computeProgress(); + tile.draw( canvas ); + shouldInvalidate |= tile.getIsDirty(); + } + } + handleInvalidation( shouldInvalidate ); + } + + private void drawTilesConsideringPreviouslyDrawnLevel( Canvas canvas ) { + // compute states, populate opaque region + boolean shouldInvalidate = establishDirtyRegion(); + // draw any previous tiles that are in viewport and not under full opaque current tiles + shouldInvalidate |= drawPreviousTiles( canvas ); + // draw the current tile set + shouldInvalidate |= drawAndClearCurrentDecodedTiles( canvas ); + // depending on transition states and previous tile draw ops, add'l invalidation might be needed + handleInvalidation( shouldInvalidate ); + } + + /** + * Draw tile bitmaps into the surface canvas displayed by this View. + * + * @param canvas The Canvas instance to draw tile bitmaps into. + */ + private void drawTiles( Canvas canvas ) { + if( mPreviouslyDrawnTiles.size() > 0 ) { + drawTilesConsideringPreviouslyDrawnLevel( canvas ); + } else { + drawTilesWithoutConsideringPreviouslyDrawnLevel( canvas ); + } + } + + public void updateTileSet( DetailLevel detailLevel ) { + if( detailLevel == null ) { + return; + } + if( detailLevel.equals( mDetailLevelToRender ) ) { + return; + } + cancelRender(); + markTilesAsPrevious(); + mDetailLevelToRender = detailLevel; + requestRender(); + } + + private void markTilesAsPrevious() { + for( Tile tile : mTilesInCurrentViewport ) { + if( tile.getState() == Tile.State.DECODED ) { + mPreviouslyDrawnTiles.add( tile ); + } + } + mTilesInCurrentViewport.clear(); + } + + private void beginRenderTask() { + // if visible columns and rows are same as previously computed, fast-fail + boolean changed = mDetailLevelToRender.computeCurrentState(); + if( !changed && mDetailLevelToRender.equals( mLastRenderedDetailLevel ) ) { + return; + } + // determine tiles are mathematically within the current viewport; force re-computation + mDetailLevelToRender.computeVisibleTilesFromViewport(); + // get rid of anything outside, use previously computed intersections + cleanup(); + // are there any new tiles the Executor isn't already aware of? + boolean wereTilesAdded = mTilesInCurrentViewport.addAll( mDetailLevelToRender.getVisibleTilesFromLastViewportComputation() ); + // if so, start up a new batch + if( wereTilesAdded ) { + mTileRenderPoolExecutor.queue( this, mTilesInCurrentViewport ); + } + } + + /** + * This should seldom be necessary, as it's built into beginRenderTask + */ + public void cleanup() { + if( mDetailLevelToRender == null || !mDetailLevelToRender.hasComputedState() ) { + return; + } + // these tiles are mathematically within the current viewport, and should be already computed + Set recentlyComputedVisibleTileSet = mDetailLevelToRender.getVisibleTilesFromLastViewportComputation(); + // use an iterator to avoid concurrent modification + Iterator tilesInCurrentViewportIterator = mTilesInCurrentViewport.iterator(); + while( tilesInCurrentViewportIterator.hasNext() ) { + Tile tile = tilesInCurrentViewportIterator.next(); + // this tile was visible previously, but is no longer, destroy and de-list it + if( !recentlyComputedVisibleTileSet.contains( tile ) ) { + tile.reset(); + tilesInCurrentViewportIterator.remove(); + } + } + } + + // this tile has been decoded by the time it gets passed here + void addTileToCanvas( final Tile tile ) { + if( mTilesInCurrentViewport.contains( tile ) ) { + invalidate(); + } + } + + void onRenderTaskPreExecute() { + mIsRendering = true; + if( mTileRenderListener != null ) { + mTileRenderListener.onRenderStart(); + } + } + + void onRenderTaskCancelled() { + if( mTileRenderListener != null ) { + mTileRenderListener.onRenderCancelled(); + } + mIsRendering = false; + } + + void onRenderTaskPostExecute() { + mIsRendering = false; + mTileRenderThrottleHandler.post( mRenderPostExecuteRunnable ); + } + + void handleTileRenderException( Throwable throwable ) { + if( mTileRenderThrowableListener != null ) { + mTileRenderThrowableListener.onRenderThrow( throwable ); + } + } + + boolean getRenderIsCancelled() { + return mRenderIsCancelled; + } + + public void destroy() { + mTileRenderPoolExecutor.shutdownNow(); + clear(); + if( !mTileRenderThrottleHandler.hasMessages( RENDER_FLAG ) ) { + mTileRenderThrottleHandler.removeMessages( RENDER_FLAG ); + } + } + + @Override + public void onDraw( Canvas canvas ) { + super.onDraw( canvas ); + canvas.save(); + canvas.scale( mScale, mScale ); + drawTiles( canvas ); + canvas.restore(); + } + + private static class TileRenderThrottleHandler extends Handler { + + private final WeakReference mTileCanvasViewGroupWeakReference; + + public TileRenderThrottleHandler( TileCanvasViewGroup tileCanvasViewGroup ) { + super( Looper.getMainLooper() ); + mTileCanvasViewGroupWeakReference = new WeakReference<>( tileCanvasViewGroup ); + } + + @Override + public final void handleMessage( Message message ) { + final TileCanvasViewGroup tileCanvasViewGroup = mTileCanvasViewGroupWeakReference.get(); + if( tileCanvasViewGroup != null ) { + tileCanvasViewGroup.renderTiles(); + } + } + } + + /** + * Interface definition for callbacks to be invoked after render operations. + */ + public interface TileRenderListener { + void onRenderStart(); + void onRenderCancelled(); + void onRenderComplete(); + } + + // ideally this would be part of TileRenderListener, but that's a breaking change + public interface TileRenderThrowableListener { + void onRenderThrow( Throwable throwable ); + } + + // This runnable is required to run on UI thread + private Runnable mRenderPostExecuteRunnable = new Runnable() { + @Override + public void run() { + cleanup(); + if( mTileRenderListener != null ) { + mTileRenderListener.onRenderComplete(); + } + mLastRenderedDetailLevel = mDetailLevelToRender; + requestRender(); + } + }; + +} diff --git a/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderHandler.java b/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderHandler.java new file mode 100644 index 00000000..c71b5623 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderHandler.java @@ -0,0 +1,77 @@ +package com.qozix.tileview.tiles; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import java.lang.ref.WeakReference; + +/** + * @author Mike Dunn, 3/10/16. + */ +class TileRenderHandler extends Handler { + + public static final int RENDER_ERROR = -1; + public static final int RENDER_INCOMPLETE = 0; + public static final int RENDER_COMPLETE = 1; + + public enum Status { + + ERROR( RENDER_ERROR ), + INCOMPLETE( RENDER_INCOMPLETE ), + COMPLETE( RENDER_COMPLETE ); + + private int mMessageCode; + + Status( int messageCode ) { + mMessageCode = messageCode; + } + + int getMessageCode() { + return mMessageCode; + } + + } + + private WeakReference mTileCanvasViewGroupWeakReference; + + public TileRenderHandler() { + this( Looper.getMainLooper() ); + } + + public TileRenderHandler( Looper looper ) { + super( looper ); + } + + public void setTileCanvasViewGroup( TileCanvasViewGroup tileCanvasViewGroup ) { + mTileCanvasViewGroupWeakReference = new WeakReference<>( tileCanvasViewGroup ); + } + + public TileCanvasViewGroup getTileCanvasViewGroup() { + if( mTileCanvasViewGroupWeakReference == null ) { + return null; + } + return mTileCanvasViewGroupWeakReference.get(); + } + + @Override + public void handleMessage( Message message ) { + TileRenderRunnable tileRenderRunnable = (TileRenderRunnable) message.obj; + TileCanvasViewGroup tileCanvasViewGroup = getTileCanvasViewGroup(); + if( tileCanvasViewGroup == null ) { + return; + } + Tile tile = tileRenderRunnable.getTile(); + if( tile == null ) { + return; + } + switch( message.what ) { + case RENDER_ERROR: + tileCanvasViewGroup.handleTileRenderException( tileRenderRunnable.getThrowable() ); + break; + case RENDER_COMPLETE: + tileCanvasViewGroup.addTileToCanvas( tile ); + break; + } + } +} diff --git a/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderPoolExecutor.java b/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderPoolExecutor.java new file mode 100644 index 00000000..51a85ed4 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderPoolExecutor.java @@ -0,0 +1,110 @@ +package com.qozix.tileview.tiles; + +import android.os.Handler; + +import java.lang.ref.WeakReference; +import java.util.Set; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class TileRenderPoolExecutor extends ThreadPoolExecutor { + + private static final int KEEP_ALIVE_TIME = 1; + private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; + + private static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors(); + private static final int INITIAL_POOL_SIZE = AVAILABLE_PROCESSORS >> 1; + private static final int MAXIMUM_POOL_SIZE = AVAILABLE_PROCESSORS; + + private WeakReference mTileCanvasViewGroupWeakReference; + + private TileRenderHandler mHandler = new TileRenderHandler(); + + public TileRenderPoolExecutor() { + super( + INITIAL_POOL_SIZE, + MAXIMUM_POOL_SIZE, + KEEP_ALIVE_TIME, + KEEP_ALIVE_TIME_UNIT, + new LinkedBlockingDeque() + ); + } + + public void queue( TileCanvasViewGroup tileCanvasViewGroup, Set renderSet ) { + mTileCanvasViewGroupWeakReference = new WeakReference<>( tileCanvasViewGroup ); + mHandler.setTileCanvasViewGroup( tileCanvasViewGroup ); + tileCanvasViewGroup.onRenderTaskPreExecute(); + for( Runnable runnable : getQueue() ) { + if( runnable instanceof TileRenderRunnable ) { + TileRenderRunnable tileRenderRunnable = (TileRenderRunnable) runnable; + if( tileRenderRunnable.isDone() || tileRenderRunnable.isCancelled() ) { + continue; + } + Tile tile = tileRenderRunnable.getTile(); + if( tile != null && !renderSet.contains( tile ) ) { + tile.reset(); + } + } + } + for( Tile tile : renderSet ) { + if( isShutdownOrTerminating() ) { + return; + } + tile.execute( this ); + } + } + + public Handler getHandler(){ + return mHandler; + } + + public TileCanvasViewGroup getTileCanvasViewGroup(){ + if( mTileCanvasViewGroupWeakReference == null ) { + return null; + } + return mTileCanvasViewGroupWeakReference.get(); + } + + private void broadcastCancel() { + if( mTileCanvasViewGroupWeakReference != null ) { + TileCanvasViewGroup tileCanvasViewGroup = mTileCanvasViewGroupWeakReference.get(); + if( tileCanvasViewGroup != null ) { + tileCanvasViewGroup.onRenderTaskCancelled(); + } + } + } + + public void cancel() { + for( Runnable runnable : getQueue() ) { + if( runnable instanceof TileRenderRunnable ) { + TileRenderRunnable tileRenderRunnable = (TileRenderRunnable) runnable; + tileRenderRunnable.cancel( true ); + Tile tile = tileRenderRunnable.getTile(); + if( tile != null ) { + tile.reset(); + } + } + } + getQueue().clear(); + broadcastCancel(); + } + + public boolean isShutdownOrTerminating() { + return isShutdown() || isTerminating() || isTerminated(); + } + + @Override + protected void afterExecute( Runnable runnable, Throwable throwable ) { + synchronized( this ) { + super.afterExecute( runnable, throwable ); + if( getQueue().size() == 0 && getActiveCount() == 1 ) { + TileCanvasViewGroup tileCanvasViewGroup = mTileCanvasViewGroupWeakReference.get(); + if( tileCanvasViewGroup != null ) { + tileCanvasViewGroup.onRenderTaskPostExecute(); + } + } + } + } + +} diff --git a/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderRunnable.java b/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderRunnable.java new file mode 100644 index 00000000..92a629e3 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/tiles/TileRenderRunnable.java @@ -0,0 +1,129 @@ +package com.qozix.tileview.tiles; + +import android.os.Handler; +import android.os.Message; +import android.os.Process; + +import java.lang.ref.WeakReference; + +/** + * @author Mike Dunn, 3/10/16. + */ +class TileRenderRunnable implements Runnable { + + private WeakReference mTileWeakReference; + private WeakReference mTileRenderPoolExecutorWeakReference; + + private boolean mCancelled = false; + private boolean mComplete = false; + + private volatile Thread mThread; + + private Throwable mThrowable; + + public boolean cancel( boolean mayInterrupt ) { + if( mayInterrupt && mThread != null ) { + mThread.interrupt(); + } + boolean cancelled = mCancelled; + mCancelled = true; + if( mTileRenderPoolExecutorWeakReference != null ) { + TileRenderPoolExecutor tileRenderPoolExecutor = mTileRenderPoolExecutorWeakReference.get(); + if(tileRenderPoolExecutor != null){ + tileRenderPoolExecutor.remove( this ); + } + } + return !cancelled; + } + + public boolean isCancelled() { + return mCancelled; + } + + public boolean isDone() { + return mComplete; + } + + public void setTileRenderPoolExecutor(TileRenderPoolExecutor tileRenderPoolExecutor ) { + mTileRenderPoolExecutorWeakReference = new WeakReference<>(tileRenderPoolExecutor); + } + + public void setTile( Tile tile ) { + mTileWeakReference = new WeakReference<>( tile ); + } + + public Tile getTile() { + if( mTileWeakReference != null ) { + return mTileWeakReference.get(); + } + return null; + } + + public Throwable getThrowable() { + return mThrowable; + } + + public TileRenderHandler.Status renderTile() { + if( mCancelled ) { + return TileRenderHandler.Status.INCOMPLETE; + } + android.os.Process.setThreadPriority( Process.THREAD_PRIORITY_BACKGROUND ); + if( mThread.isInterrupted() ) { + return TileRenderHandler.Status.INCOMPLETE; + } + Tile tile = getTile(); + if( tile == null ) { + return TileRenderHandler.Status.INCOMPLETE; + } + TileRenderPoolExecutor tileRenderPoolExecutor = mTileRenderPoolExecutorWeakReference.get(); + if( tileRenderPoolExecutor == null ) { + return TileRenderHandler.Status.INCOMPLETE; + } + TileCanvasViewGroup tileCanvasViewGroup = tileRenderPoolExecutor.getTileCanvasViewGroup(); + if(tileCanvasViewGroup == null ) { + return TileRenderHandler.Status.INCOMPLETE; + } + try { + tile.generateBitmap( tileCanvasViewGroup.getContext(), tileCanvasViewGroup.getBitmapProvider() ); + } catch( Throwable throwable ) { + mThrowable = throwable; + return TileRenderHandler.Status.ERROR; + } + if( mCancelled || tile.getBitmap() == null || mThread.isInterrupted() ) { + tile.reset(); + return TileRenderHandler.Status.INCOMPLETE; + } + return TileRenderHandler.Status.COMPLETE; + } + + @Override + public void run() { + mThread = Thread.currentThread(); + TileRenderHandler.Status status = renderTile(); + if( status == TileRenderHandler.Status.INCOMPLETE ) { + return; + } + if( status == TileRenderHandler.Status.COMPLETE ) { + mComplete = true; + } + TileRenderPoolExecutor tileRenderPoolExecutor = mTileRenderPoolExecutorWeakReference.get(); + if( tileRenderPoolExecutor != null ) { + TileCanvasViewGroup tileCanvasViewGroup = tileRenderPoolExecutor.getTileCanvasViewGroup(); + if( tileCanvasViewGroup != null ) { + Tile tile = getTile(); + if( tile != null ) { + Handler handler = tileRenderPoolExecutor.getHandler(); + if( handler != null ) { + // need to stamp time now, since it'll be drawn before the handler posts + tile.setTransitionsEnabled( tileCanvasViewGroup.getTransitionsEnabled() ); + tile.setTransitionDuration( tileCanvasViewGroup.getTransitionDuration() ); + Message message = handler.obtainMessage( status.getMessageCode(), this ); + message.sendToTarget(); + } + } + } + + } + } + +} diff --git a/tileview/src/main/java/com/qozix/tileview/view/TouchUpGestureDetector.java b/tileview/src/main/java/com/qozix/tileview/view/TouchUpGestureDetector.java new file mode 100644 index 00000000..3b2939c7 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/view/TouchUpGestureDetector.java @@ -0,0 +1,29 @@ +package com.qozix.tileview.view; + +import android.view.MotionEvent; + +/** + * @author Mike Dunn, 10/6/15. + */ +public class TouchUpGestureDetector { + + private OnTouchUpListener mOnTouchUpListener; + + public TouchUpGestureDetector( OnTouchUpListener listener ) { + mOnTouchUpListener = listener; + } + + public boolean onTouchEvent( MotionEvent event ) { + if( event.getActionMasked() == MotionEvent.ACTION_UP ) { + if( mOnTouchUpListener != null ) { + return mOnTouchUpListener.onTouchUp( event ); + } + } + return true; + } + + public interface OnTouchUpListener { + boolean onTouchUp( MotionEvent event ); + } +} + diff --git a/tileview/src/main/java/com/qozix/tileview/widgets/ScalingLayout.java b/tileview/src/main/java/com/qozix/tileview/widgets/ScalingLayout.java new file mode 100644 index 00000000..a22edf1c --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/widgets/ScalingLayout.java @@ -0,0 +1,83 @@ +package com.qozix.tileview.widgets; + +import android.content.Context; +import android.graphics.Canvas; +import android.view.View; +import android.view.ViewGroup; + +import com.qozix.tileview.geom.FloatMathHelper; + +public class ScalingLayout extends ViewGroup { + + private float mScale = 1; + + /** + * + * @param context + */ + public ScalingLayout( Context context ) { + super( context ); + setWillNotDraw( false ); + + + + } + + /** + * + * @param scale + */ + public void setScale( float scale ) { + mScale = scale; + invalidate(); + } + + /** + * + * @return + */ + public float getScale() { + return mScale; + } + + /* + * When scaling a canvas, as happens in onDraw, the clip area will be reduced at a small scale, + * thus decreasing the drawable surface, but when scaled up, the canvas is still constrained + * by the original width and height of the backing bitmap, which are not scaled. Offset those + * by dividing the measure and layout dimensions by the current scale. + */ + + @Override + protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) { + int availableWidth = FloatMathHelper.unscale( MeasureSpec.getSize( widthMeasureSpec ), mScale ); + int availableHeight = FloatMathHelper.unscale( MeasureSpec.getSize( heightMeasureSpec ), mScale ); + // the container's children should be the size provided by setSize + // don't use measureChildren because that grabs the child's LayoutParams + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( availableWidth, MeasureSpec.EXACTLY ); + int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( availableHeight, MeasureSpec.EXACTLY ); + for( int i = 0; i < getChildCount(); i++){ + View child = getChildAt( i ); + child.measure( childWidthMeasureSpec, childHeightMeasureSpec ); + } + setMeasuredDimension( availableWidth, availableHeight ); + } + + @Override + protected void onLayout( boolean changed, int l, int t, int r, int b ) { + int availableWidth = FloatMathHelper.unscale( r - l, mScale ); + int availableHeight = FloatMathHelper.unscale( b - t, mScale ); + for( int i = 0; i < getChildCount(); i++ ) { + View child = getChildAt( i ); + if( child.getVisibility() != GONE ) { + child.layout( 0, 0, availableWidth, availableHeight ); + } + } + } + + @Override + public void onDraw( Canvas canvas ) { + canvas.scale( mScale, mScale ); + super.onDraw( canvas ); + } + +} \ No newline at end of file diff --git a/tileview/src/main/java/com/qozix/tileview/widgets/ZoomPanLayout.java b/tileview/src/main/java/com/qozix/tileview/widgets/ZoomPanLayout.java new file mode 100644 index 00000000..68d16143 --- /dev/null +++ b/tileview/src/main/java/com/qozix/tileview/widgets/ZoomPanLayout.java @@ -0,0 +1,977 @@ +package com.qozix.tileview.widgets; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +import com.qozix.tileview.geom.FloatMathHelper; +import com.qozix.tileview.view.TouchUpGestureDetector; + +import java.lang.ref.WeakReference; +import java.util.HashSet; + +/** + * ZoomPanLayout extends ViewGroup to provide support for scrolling and zooming. + * Fling, drag, pinch and double-tap events are supported natively. + * + * Children of ZoomPanLayout are laid out to the sizes provided by setSize, + * and will always be positioned at 0,0. + */ + +public class ZoomPanLayout extends ViewGroup implements + GestureDetector.OnGestureListener, + GestureDetector.OnDoubleTapListener, + ScaleGestureDetector.OnScaleGestureListener, + TouchUpGestureDetector.OnTouchUpListener { + + private static final int DEFAULT_ZOOM_PAN_ANIMATION_DURATION = 400; + + private int mBaseWidth; + private int mBaseHeight; + private int mScaledWidth; + private int mScaledHeight; + + private float mScale = 1; + + private float mMinScale = 0; + private float mMaxScale = 1; + + private int mOffsetX; + private int mOffsetY; + + private float mEffectiveMinScale = 0; + private boolean mShouldLoopScale = true; + + private boolean mIsFlinging; + private boolean mIsDragging; + private boolean mIsScaling; + private boolean mIsSliding; + + private int mAnimationDuration = DEFAULT_ZOOM_PAN_ANIMATION_DURATION; + + private HashSet mZoomPanListeners = new HashSet(); + + private Scroller mScroller; + private ZoomPanAnimator mZoomPanAnimator; + + private ScaleGestureDetector mScaleGestureDetector; + private GestureDetector mGestureDetector; + private TouchUpGestureDetector mTouchUpGestureDetector; + private MinimumScaleMode mMinimumScaleMode = MinimumScaleMode.FILL; + + /** + * Constructor to use when creating a ZoomPanLayout from code. + * + * @param context The Context the ZoomPanLayout is running in, through which it can access the current theme, resources, etc. + */ + public ZoomPanLayout( Context context ) { + this( context, null ); + } + + public ZoomPanLayout( Context context, AttributeSet attrs ) { + this( context, attrs, 0 ); + } + + public ZoomPanLayout( Context context, AttributeSet attrs, int defStyleAttr ) { + super( context, attrs, defStyleAttr ); + setWillNotDraw( false ); + setClipChildren( false ); + mScroller = new Scroller( context ); + mGestureDetector = new GestureDetector( context, this ); + mScaleGestureDetector = new ScaleGestureDetector( context, this ); + mTouchUpGestureDetector = new TouchUpGestureDetector( this ); + } + + @Override + protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec ) { + // the container's children should be the size provided by setSize + // don't use measureChildren because that grabs the child's LayoutParams + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( mScaledWidth, MeasureSpec.EXACTLY ); + int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( mScaledHeight, MeasureSpec.EXACTLY ); + for( int i = 0; i < getChildCount(); i++){ + View child = getChildAt( i ); + child.measure( childWidthMeasureSpec, childHeightMeasureSpec ); + } + // but the layout itself should report normal (on screen) dimensions + int width = MeasureSpec.getSize( widthMeasureSpec ); + int height = MeasureSpec.getSize( heightMeasureSpec ); + width = resolveSize( width, widthMeasureSpec ); + height = resolveSize( height, heightMeasureSpec ); + setMeasuredDimension( width, height ); + } + + /* + ZoomPanChildren will always be laid out with the scaled dimenions - what is visible during + scroll operations. Thus, a RelativeLayout added as a child that had views within it using + rules like ALIGN_PARENT_RIGHT would function as expected; similarly, an ImageView would be + stretched between the visible edges. + If children further operate on scale values, that should be accounted for + in the child's logic (see ScalingLayout). + */ + @Override + protected void onLayout( boolean changed, int l, int t, int r, int b ) { + final int width = getWidth(); + final int height = getHeight(); + + mOffsetX = mScaledWidth >= width ? 0 : width / 2 - mScaledWidth / 2; + mOffsetY = mScaledHeight >= height ? 0 : height / 2 - mScaledHeight / 2; + + for( int i = 0; i < getChildCount(); i++ ) { + View child = getChildAt( i ); + if( child.getVisibility() != GONE ) { + child.layout( mOffsetX, mOffsetY, mScaledWidth + mOffsetX, mScaledHeight + mOffsetY ); + } + } + calculateMinimumScaleToFit(); + constrainScrollToLimits(); + } + + /** + * Determines whether the ZoomPanLayout should limit it's minimum scale to no less than what + * would be required to fill it's container. + * + * @param shouldScaleToFit True to limit minimum scale, false to allow arbitrary minimum scale. + */ + public void setShouldScaleToFit( boolean shouldScaleToFit ) { + setMinimumScaleMode(shouldScaleToFit ? MinimumScaleMode.FILL : MinimumScaleMode.NONE); + } + + /** + * Sets the minimum scale mode + * + * @param minimumScaleMode The minimum scale mode + */ + public void setMinimumScaleMode( MinimumScaleMode minimumScaleMode ) { + mMinimumScaleMode = minimumScaleMode; + calculateMinimumScaleToFit(); + } + + /** + * Determines whether the ZoomPanLayout should go back to minimum scale after a double-tap at + * maximum scale. + * + * @param shouldLoopScale True to allow going back to minimum scale, false otherwise. + */ + public void setShouldLoopScale( boolean shouldLoopScale ) { + mShouldLoopScale = shouldLoopScale; + } + + /** + * Set minimum and maximum mScale values for this ZoomPanLayout. + * Note that if minimumScaleMode is set to {@link MinimumScaleMode#FIT} or {@link MinimumScaleMode#FILL}, the minimum value set here will be ignored + * Default values are 0 and 1. + * + * @param min Minimum scale the ZoomPanLayout should accept. + * @param max Maximum scale the ZoomPanLayout should accept. + */ + public void setScaleLimits( float min, float max ) { + mMinScale = min; + mMaxScale = max; + setScale( mScale ); + } + + /** + * Sets the size (width and height) of the ZoomPanLayout + * as it should be rendered at a scale of 1f (100%). + * + * @param width Width of the underlying image, not the view or viewport. + * @param height Height of the underlying image, not the view or viewport. + */ + public void setSize( int width, int height ) { + mBaseWidth = width; + mBaseHeight = height; + updateScaledDimensions(); + calculateMinimumScaleToFit(); + constrainScrollToLimits(); + requestLayout(); + } + + /** + * Returns the base (not scaled) width of the underlying composite image. + * + * @return The base (not scaled) width of the underlying composite image. + */ + public int getBaseWidth() { + return mBaseWidth; + } + + /** + * Returns the base (not scaled) height of the underlying composite image. + * + * @return The base (not scaled) height of the underlying composite image. + */ + public int getBaseHeight() { + return mBaseHeight; + } + + /** + * Returns the scaled width of the underlying composite image. + * + * @return The scaled width of the underlying composite image. + */ + public int getScaledWidth() { + return mScaledWidth; + } + + /** + * Returns the scaled height of the underlying composite image. + * + * @return The scaled height of the underlying composite image. + */ + public int getScaledHeight() { + return mScaledHeight; + } + + /** + * Sets the scale (0-1) of the ZoomPanLayout. + * + * @param scale The new value of the ZoomPanLayout scale. + */ + public void setScale( float scale ) { + scale = getConstrainedDestinationScale( scale ); + if( mScale != scale ) { + float previous = mScale; + mScale = scale; + updateScaledDimensions(); + constrainScrollToLimits(); + onScaleChanged( scale, previous ); + invalidate(); + } + } + + /** + * Retrieves the current scale of the ZoomPanLayout. + * + * @return The current scale of the ZoomPanLayout. + */ + public float getScale() { + return mScale; + } + + /** + * Returns the horizontal distance children are offset if the content is scaled smaller than width. + * + * @return + */ + public int getOffsetX() { + return mOffsetX; + } + + /** + * Return the vertical distance children are offset if the content is scaled smaller than height. + * + * @return + */ + public int getOffsetY() { + return mOffsetY; + } + + /** + * Returns whether the ZoomPanLayout is currently being flung. + * + * @return true if the ZoomPanLayout is currently flinging, false otherwise. + */ + public boolean isFlinging() { + return mIsFlinging; + } + + /** + * Returns whether the ZoomPanLayout is currently being dragged. + * + * @return true if the ZoomPanLayout is currently dragging, false otherwise. + */ + public boolean isDragging() { + return mIsDragging; + } + + /** + * Returns whether the ZoomPanLayout is currently operating a scroll tween. + * + * @return True if the ZoomPanLayout is currently scrolling, false otherwise. + */ + public boolean isSliding() { + return mIsSliding; + } + + /** + * Returns whether the ZoomPanLayout is currently operating a scale tween. + * + * @return True if the ZoomPanLayout is currently scaling, false otherwise. + */ + public boolean isScaling() { + return mIsScaling; + } + + /** + * Returns the Scroller instance used to manage dragging and flinging. + * + * @return The Scroller instance use to manage dragging and flinging. + */ + public Scroller getScroller() { + return mScroller; + } + + /** + * Returns the duration zoom and pan animations will use. + * + * @return The duration zoom and pan animations will use. + */ + public int getAnimationDuration() { + return mAnimationDuration; + } + + /** + * Set the duration zoom and pan animation will use. + * + * @param animationDuration The duration animations will use. + */ + public void setAnimationDuration( int animationDuration ) { + mAnimationDuration = animationDuration; + if( mZoomPanAnimator != null ) { + mZoomPanAnimator.setDuration( mAnimationDuration ); + } + } + + /** + * Adds a ZoomPanListener to the ZoomPanLayout, which will receive notification of actions + * relating to zoom and pan events. + * + * @param zoomPanListener ZoomPanListener implementation to add. + * @return True when the listener set did not already contain the Listener, false otherwise. + */ + public boolean addZoomPanListener( ZoomPanListener zoomPanListener ) { + return mZoomPanListeners.add( zoomPanListener ); + } + + /** + * Removes a ZoomPanListener from the ZoomPanLayout + * + * @param listener ZoomPanListener to remove. + * @return True if the Listener was removed, false otherwise. + */ + public boolean removeZoomPanListener( ZoomPanListener listener ) { + return mZoomPanListeners.remove( listener ); + } + + /** + * Scrolls and centers the ZoomPanLayout to the x and y values provided. + * + * @param x Horizontal destination point. + * @param y Vertical destination point. + */ + public void scrollToAndCenter( int x, int y ) { + scrollTo( x - getHalfWidth(), y - getHalfHeight() ); + } + + /** + * Set the scale of the ZoomPanLayout while maintaining the current center point. + * + * @param scale The new value of the ZoomPanLayout scale. + */ + public void setScaleFromCenter( float scale ) { + setScaleFromPosition( getHalfWidth(), getHalfHeight(), scale ); + } + + /** + * Scrolls the ZoomPanLayout to the x and y values provided using scrolling animation. + * + * @param x Horizontal destination point. + * @param y Vertical destination point. + */ + public void slideTo( int x, int y ) { + getAnimator().animatePan( x, y ); + } + + /** + * Scrolls and centers the ZoomPanLayout to the x and y values provided using scrolling animation. + * + * @param x Horizontal destination point. + * @param y Vertical destination point. + */ + public void slideToAndCenter( int x, int y ) { + slideTo( x - getHalfWidth(), y - getHalfHeight() ); + } + + /** + * Animates the ZoomPanLayout to the scale provided, and centers the viewport to the position + * supplied. + * + * @param x Horizontal destination point. + * @param y Vertical destination point. + * @param scale The final scale value the ZoomPanLayout should animate to. + */ + public void slideToAndCenterWithScale( int x, int y, float scale ) { + getAnimator().animateZoomPan( x - getHalfWidth(), y - getHalfHeight(), scale ); + } + + /** + * Scales the ZoomPanLayout with animated progress, without maintaining scroll position. + * + * @param destination The final scale value the ZoomPanLayout should animate to. + */ + public void smoothScaleTo( float destination ) { + getAnimator().animateZoom( destination ); + } + + /** + * Animates the ZoomPanLayout to the scale provided, while maintaining position determined by + * the focal point provided. + * + * @param focusX The horizontal focal point to maintain, relative to the screen (as supplied by MotionEvent.getX). + * @param focusY The vertical focal point to maintain, relative to the screen (as supplied by MotionEvent.getY). + * @param scale The final scale value the ZoomPanLayout should animate to. + */ + public void smoothScaleFromFocalPoint( int focusX, int focusY, float scale ) { + scale = getConstrainedDestinationScale( scale ); + if( scale == mScale ) { + return; + } + int x = getOffsetScrollXFromScale( focusX, scale, mScale ); + int y = getOffsetScrollYFromScale( focusY, scale, mScale ); + getAnimator().animateZoomPan( x, y, scale ); + } + + /** + * Animate the scale of the ZoomPanLayout while maintaining the current center point. + * + * @param scale The final scale value the ZoomPanLayout should animate to. + */ + public void smoothScaleFromCenter( float scale ) { + smoothScaleFromFocalPoint( getHalfWidth(), getHalfHeight(), scale ); + } + + /** + * Provide this method to be overriden by subclasses, e.g., onScrollChanged. + */ + public void onScaleChanged( float currentScale, float previousScale ) { + // noop + } + + private float getConstrainedDestinationScale( float scale ) { + scale = Math.max( scale, mEffectiveMinScale ); + scale = Math.min( scale, mMaxScale ); + return scale; + } + + private void constrainScrollToLimits() { + int x = getScrollX(); + int y = getScrollY(); + int constrainedX = getConstrainedScrollX( x ); + int constrainedY = getConstrainedScrollY( y ); + if( x != constrainedX || y != constrainedY ) { + scrollTo( constrainedX, constrainedY ); + } + } + + private void updateScaledDimensions() { + mScaledWidth = FloatMathHelper.scale( mBaseWidth, mScale ); + mScaledHeight = FloatMathHelper.scale( mBaseHeight, mScale ); + } + + protected ZoomPanAnimator getAnimator() { + if( mZoomPanAnimator == null ) { + mZoomPanAnimator = new ZoomPanAnimator( this ); + mZoomPanAnimator.setDuration( mAnimationDuration ); + } + return mZoomPanAnimator; + } + + private int getOffsetScrollXFromScale( int offsetX, float destinationScale, float currentScale ) { + int scrollX = getScrollX() + offsetX; + float deltaScale = destinationScale / currentScale; + return (int) (scrollX * deltaScale) - offsetX; + } + + private int getOffsetScrollYFromScale( int offsetY, float destinationScale, float currentScale ) { + int scrollY = getScrollY() + offsetY; + float deltaScale = destinationScale / currentScale; + return (int) (scrollY * deltaScale) - offsetY; + } + + public void setScaleFromPosition( int offsetX, int offsetY, float scale ) { + scale = getConstrainedDestinationScale( scale ); + if( scale == mScale ) { + return; + } + int x = getOffsetScrollXFromScale( offsetX, scale, mScale ); + int y = getOffsetScrollYFromScale( offsetY, scale, mScale ); + + setScale( scale ); + + x = getConstrainedScrollX( x ); + y = getConstrainedScrollY( y ); + + scrollTo( x, y ); + } + + @Override + public boolean canScrollHorizontally( int direction ) { + int position = getScrollX(); + return direction > 0 ? position < getScrollLimitX() : direction < 0 && position > 0; + } + + @Override + public boolean onTouchEvent( MotionEvent event ) { + boolean gestureIntercept = mGestureDetector.onTouchEvent( event ); + boolean scaleIntercept = mScaleGestureDetector.onTouchEvent( event ); + boolean touchIntercept = mTouchUpGestureDetector.onTouchEvent( event ); + return gestureIntercept || scaleIntercept || touchIntercept || super.onTouchEvent( event ); + } + + @Override + public void scrollTo( int x, int y ) { + x = getConstrainedScrollX( x ); + y = getConstrainedScrollY( y ); + super.scrollTo( x, y ); + } + + private void calculateMinimumScaleToFit() { + float minimumScaleX = getWidth() / (float) mBaseWidth; + float minimumScaleY = getHeight() / (float) mBaseHeight; + float recalculatedMinScale = calculatedMinScale(minimumScaleX, minimumScaleY); + if( recalculatedMinScale != mEffectiveMinScale ) { + mEffectiveMinScale = recalculatedMinScale; + if( mScale < mEffectiveMinScale ){ + setScale( mEffectiveMinScale ); + } + } + } + + private float calculatedMinScale( float minimumScaleX, float minimumScaleY ) { + switch( mMinimumScaleMode ) { + case FILL: return Math.max( minimumScaleX, minimumScaleY ); + case FIT: return Math.min( minimumScaleX, minimumScaleY ); + } + + return mMinScale; + } + + protected int getHalfWidth() { + return FloatMathHelper.scale( getWidth(), 0.5f ); + } + + protected int getHalfHeight() { + return FloatMathHelper.scale( getHeight(), 0.5f ); + } + + private int getConstrainedScrollX( int x ) { + return Math.max( 0, Math.min( x, getScrollLimitX() ) ); + } + + private int getConstrainedScrollY( int y ) { + return Math.max( 0, Math.min( y, getScrollLimitY() ) ); + } + + private int getScrollLimitX() { + return mScaledWidth - getWidth(); + } + + private int getScrollLimitY() { + return mScaledHeight - getHeight(); + } + + @Override + public void computeScroll() { + if( mScroller.computeScrollOffset() ) { + int startX = getScrollX(); + int startY = getScrollY(); + int endX = getConstrainedScrollX( mScroller.getCurrX() ); + int endY = getConstrainedScrollY( mScroller.getCurrY() ); + if( startX != endX || startY != endY ) { + scrollTo( endX, endY ); + if( mIsFlinging ) { + broadcastFlingUpdate(); + } + } + if( mScroller.isFinished() ) { + if( mIsFlinging ) { + mIsFlinging = false; + broadcastFlingEnd(); + } + } else { + ViewCompat.postInvalidateOnAnimation( this ); + } + } + } + + private void broadcastDragBegin() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanBegin( getScrollX(), getScrollY(), ZoomPanListener.Origination.DRAG ); + } + } + + private void broadcastDragUpdate() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanUpdate( getScrollX(), getScrollY(), ZoomPanListener.Origination.DRAG ); + } + } + + private void broadcastDragEnd() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanEnd( getScrollX(), getScrollY(), ZoomPanListener.Origination.DRAG ); + } + } + + private void broadcastFlingBegin() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanBegin( mScroller.getStartX(), mScroller.getStartY(), ZoomPanListener.Origination.FLING ); + } + } + + private void broadcastFlingUpdate() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanUpdate( mScroller.getCurrX(), mScroller.getCurrY(), ZoomPanListener.Origination.FLING ); + } + } + + private void broadcastFlingEnd() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanEnd( mScroller.getFinalX(), mScroller.getFinalY(), ZoomPanListener.Origination.FLING ); + } + } + + private void broadcastProgrammaticPanBegin() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanBegin( getScrollX(), getScrollY(), null ); + } + } + + private void broadcastProgrammaticPanUpdate() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanUpdate( getScrollX(), getScrollY(), null ); + } + } + + private void broadcastProgrammaticPanEnd() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onPanEnd( getScrollX(), getScrollY(), null ); + } + } + + private void broadcastPinchBegin() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onZoomBegin( mScale, ZoomPanListener.Origination.PINCH ); + } + } + + private void broadcastPinchUpdate() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onZoomUpdate( mScale, ZoomPanListener.Origination.PINCH ); + } + } + + private void broadcastPinchEnd() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onZoomEnd( mScale, ZoomPanListener.Origination.PINCH ); + } + } + + private void broadcastProgrammaticZoomBegin() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onZoomBegin( mScale, null ); + } + } + + private void broadcastProgrammaticZoomUpdate() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onZoomUpdate( mScale, null ); + } + } + + private void broadcastProgrammaticZoomEnd() { + for( ZoomPanListener listener : mZoomPanListeners ) { + listener.onZoomEnd( mScale, null ); + } + } + + @Override + public boolean onDown( MotionEvent event ) { + if( mIsFlinging && !mScroller.isFinished() ) { + mScroller.forceFinished( true ); + mIsFlinging = false; + broadcastFlingEnd(); + } + return true; + } + + @Override + public boolean onFling( MotionEvent event1, MotionEvent event2, float velocityX, float velocityY ) { + mScroller.fling( getScrollX(), getScrollY(), (int) -velocityX, (int) -velocityY, 0, getScrollLimitX(), 0, getScrollLimitY() ); + mIsFlinging = true; + ViewCompat.postInvalidateOnAnimation( this ); + broadcastFlingBegin(); + return true; + } + + @Override + public void onLongPress( MotionEvent event ) { + + } + + @Override + public boolean onScroll( MotionEvent e1, MotionEvent e2, float distanceX, float distanceY ) { + int scrollEndX = getScrollX() + (int) distanceX; + int scrollEndY = getScrollY() + (int) distanceY; + scrollTo( scrollEndX, scrollEndY ); + if( !mIsDragging ) { + mIsDragging = true; + broadcastDragBegin(); + } else { + broadcastDragUpdate(); + } + return true; + } + + @Override + public void onShowPress( MotionEvent event ) { + + } + + @Override + public boolean onSingleTapUp( MotionEvent event ) { + return true; + } + + @Override + public boolean onSingleTapConfirmed( MotionEvent event ) { + return true; + } + + @Override + public boolean onDoubleTap( MotionEvent event ) { + float destination = (float)( Math.pow( 2, Math.floor( Math.log( mScale * 2 ) / Math.log( 2 ) ) ) ); + float effectiveDestination = mShouldLoopScale && mScale >= mMaxScale ? mMinScale : destination; + destination = getConstrainedDestinationScale( effectiveDestination ); + smoothScaleFromFocalPoint( (int) event.getX(), (int) event.getY(), destination ); + return true; + } + + @Override + public boolean onDoubleTapEvent( MotionEvent event ) { + return true; + } + + @Override + public boolean onTouchUp( MotionEvent event ) { + if( mIsDragging ) { + mIsDragging = false; + if( !mIsFlinging ) { + broadcastDragEnd(); + } + } + return true; + } + + @Override + public boolean onScaleBegin( ScaleGestureDetector scaleGestureDetector ) { + mIsScaling = true; + broadcastPinchBegin(); + return true; + } + + @Override + public void onScaleEnd( ScaleGestureDetector scaleGestureDetector ) { + mIsScaling = false; + broadcastPinchEnd(); + } + + @Override + public boolean onScale( ScaleGestureDetector scaleGestureDetector ) { + float currentScale = mScale * mScaleGestureDetector.getScaleFactor(); + setScaleFromPosition( + (int) scaleGestureDetector.getFocusX(), + (int) scaleGestureDetector.getFocusY(), + currentScale ); + broadcastPinchUpdate(); + return true; + } + + private static class ZoomPanAnimator extends ValueAnimator implements + ValueAnimator.AnimatorUpdateListener, + ValueAnimator.AnimatorListener { + + private WeakReference mZoomPanLayoutWeakReference; + private ZoomPanState mStartState = new ZoomPanState(); + private ZoomPanState mEndState = new ZoomPanState(); + private boolean mHasPendingZoomUpdates; + private boolean mHasPendingPanUpdates; + + public ZoomPanAnimator( ZoomPanLayout zoomPanLayout ) { + super(); + addUpdateListener( this ); + addListener( this ); + setFloatValues( 0f, 1f ); + setInterpolator( new FastEaseInInterpolator() ); + mZoomPanLayoutWeakReference = new WeakReference( zoomPanLayout ); + } + + private boolean setupPanAnimation( int x, int y ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + mStartState.x = zoomPanLayout.getScrollX(); + mStartState.y = zoomPanLayout.getScrollY(); + mEndState.x = x; + mEndState.y = y; + return mStartState.x != mEndState.x || mStartState.y != mEndState.y; + } + return false; + } + + private boolean setupZoomAnimation( float scale ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + mStartState.scale = zoomPanLayout.getScale(); + mEndState.scale = scale; + return mStartState.scale != mEndState.scale; + } + return false; + } + + public void animateZoomPan( int x, int y, float scale ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + mHasPendingZoomUpdates = setupZoomAnimation( scale ); + mHasPendingPanUpdates = setupPanAnimation( x, y ); + if( mHasPendingPanUpdates || mHasPendingZoomUpdates ) { + start(); + } + } + } + + public void animateZoom( float scale ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + mHasPendingZoomUpdates = setupZoomAnimation( scale ); + if( mHasPendingZoomUpdates ) { + start(); + } + } + } + + public void animatePan( int x, int y ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + mHasPendingPanUpdates = setupPanAnimation( x, y ); + if( mHasPendingPanUpdates ) { + start(); + } + } + } + + @Override + public void onAnimationUpdate( ValueAnimator animation ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + float progress = (float) animation.getAnimatedValue(); + if( mHasPendingZoomUpdates ) { + float scale = mStartState.scale + (mEndState.scale - mStartState.scale) * progress; + zoomPanLayout.setScale( scale ); + zoomPanLayout.broadcastProgrammaticZoomUpdate(); + } + if( mHasPendingPanUpdates ) { + int x = (int) (mStartState.x + (mEndState.x - mStartState.x) * progress); + int y = (int) (mStartState.y + (mEndState.y - mStartState.y) * progress); + zoomPanLayout.scrollTo( x, y ); + zoomPanLayout.broadcastProgrammaticPanUpdate(); + } + } + } + + @Override + public void onAnimationStart( Animator animator ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + if( mHasPendingZoomUpdates ) { + zoomPanLayout.mIsScaling = true; + zoomPanLayout.broadcastProgrammaticZoomBegin(); + } + if( mHasPendingPanUpdates ) { + zoomPanLayout.mIsSliding = true; + zoomPanLayout.broadcastProgrammaticPanBegin(); + } + } + } + + @Override + public void onAnimationEnd( Animator animator ) { + ZoomPanLayout zoomPanLayout = mZoomPanLayoutWeakReference.get(); + if( zoomPanLayout != null ) { + if( mHasPendingZoomUpdates ) { + mHasPendingZoomUpdates = false; + zoomPanLayout.mIsScaling = false; + zoomPanLayout.broadcastProgrammaticZoomEnd(); + } + if( mHasPendingPanUpdates ) { + mHasPendingPanUpdates = false; + zoomPanLayout.mIsSliding = false; + zoomPanLayout.broadcastProgrammaticPanEnd(); + } + } + } + + @Override + public void onAnimationCancel( Animator animator ) { + onAnimationEnd( animator ); + } + + @Override + public void onAnimationRepeat( Animator animator ) { + + } + + private static class ZoomPanState { + public int x; + public int y; + public float scale; + } + + private static class FastEaseInInterpolator implements Interpolator { + @Override + public float getInterpolation( float input ) { + return (float) (1 - Math.pow( 1 - input, 8 )); + } + } + } + + public interface ZoomPanListener { + enum Origination { + DRAG, + FLING, + PINCH + } + void onPanBegin( int x, int y, Origination origin ); + void onPanUpdate( int x, int y, Origination origin ); + void onPanEnd( int x, int y, Origination origin ); + void onZoomBegin( float scale, Origination origin ); + void onZoomUpdate( float scale, Origination origin ); + void onZoomEnd( float scale, Origination origin ); + } + + public enum MinimumScaleMode { + /** + * Limit the minimum scale to no less than what + * would be required to fill the container + */ + FILL, + + /** + * Limit the minimum scale to no less than what + * would be required to fit inside the container + */ + FIT, + + /** + * Allow arbitrary minimum scale. + */ + NONE + } +} diff --git a/tileview/src/test/java/com/qozix/tileview/ExampleUnitTest.java b/tileview/src/test/java/com/qozix/tileview/ExampleUnitTest.java new file mode 100644 index 00000000..a52b89ae --- /dev/null +++ b/tileview/src/test/java/com/qozix/tileview/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package com.qozix.tileview; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals( 4, 2 + 2 ); + } +} \ No newline at end of file From 4634056a131a8028406fb83698f5b19b95efc40e Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Thu, 17 Jan 2019 12:03:36 +0800 Subject: [PATCH 08/83] * Updated readme in preparation of... --- README.md | 61 ++++++++----------------------------------------- arts/scr01.jpg | Bin 0 -> 152341 bytes 2 files changed, 9 insertions(+), 52 deletions(-) create mode 100644 arts/scr01.jpg diff --git a/README.md b/README.md index 86fd85d8..37d845d7 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,17 @@ -## DO NOT FORK ME! Fork [flagmaggot/blocktopograph](https://github.com/flagmaggot/blocktopograph) instead! -## 【This fork is not maintained NOW】 - # Blocktopograph -By @protolambda. - -**NOTE**: This is a *very* old legacy project! Code quality is awful, but I'm not going to rewrite the whole app for "fun". - Feel free to fork it and improve it yourself, - it is licensed under AGPL v3; reverse engineering the MCPE format is time consuming, share your updates. - -## [Download on Google-Play](https://play.google.com/store/apps/details?id=com.mithrilmania.blocktopograph) - -### Showcase website - -Screenshots, download links, roadmap etc. can all be found on [blocktopograph.mithrilmania.com](http://blocktopograph.mithrilmania.com). - - -## Get-started - -Steps to get started quickly: - -### Get-started: pre-installation - -This project requires you to download some android SDKs, tools, libraries and drivers. - -- SDKs + Tools: please check the sdk version before cloning a repo, then install sdk+tools for this version with SDK-manager. -- Libraries: You need to install the google-services and google-repository libraries with SDK-manager. -- Drivers: download the appropriate drivers for your phone to use `adb`. - The SDK-manager provides windows drivers for the Nexus phones. -- Some libraries are downloaded by gradle itself. You do not have to worry about these. -- Sub-modules are managed with git. (See installation) - - -### Get-started: installation +By @protolambda. +This fork is the only one supporting MCPE 1.2~1.9 for now. -NOTE: You may want to fork one of the dependencies (or this project) if you want to contribute. +## Download +Google play download not available now. +Download apk from [the release page](https://github.com/oO0oO0oO0o0o00/blocktopograph/releases). +screenshot -1. `mkdir block-project` or something like that. This will be the main-container -1. `cd` into the new folder -1. `git clone` (your fork of) this repository -1. `git clone` (your fork of) [android-leveldb](https://github.com/mithrilmania/android-leveldb) -1. `cd android-leveldb` and `git submodule update` to get the - [leveldb-mcpe](https://github.com/mithrilmania/leveldb-mcpe) git-submodule, it is required for building this project. -1. `cd ..` (back to the main container) -1. `git clone` (your fork of) [TileView](https://github.com/mithrilmania/TileView) -1. Add `local.properties` files to these projects, - with `sdk.dir` for your sdk home, - and with `ndk.dir` specified for `android-leveldb`. -1. Open the cloned blocktopograph repo with your IDE (android-studio and intellij-idea are tested). - The blocktopograph repo should be the `root-module`; - `app`, `library`(android-leveldb) and `tileview`(TileView) will be recognized as sub-modules. -1. Build the project with gradle -1. Make the project, to get android-leveldb native libs. -1. Switch build-variants of the projects you want to debug and rebuild with gradle (or leave them as is) -1. Good to go! Try running a debug build (`app` submodule)! Start with small-changes to see if you encounter any problems. +## Build +Clone project in Android Studio: `File->New->Project from Version Control->Git` +Install missing SDK components. Android Studio would give you the auto-fix options. ### Release-Workflow diff --git a/arts/scr01.jpg b/arts/scr01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf2fbdc594a1e74ea48d81b08ea7bc938ea323f0 GIT binary patch literal 152341 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

Z-`*vwI05YCcy&D^hM#bse> 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 Date: Thu, 17 Jan 2019 12:06:00 +0800 Subject: [PATCH 09/83] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 37d845d7..15b5d3fe 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ This fork is the only one supporting MCPE 1.2~1.9 for now. ## Download Google play download not available now. -Download apk from [the release page](https://github.com/oO0oO0oO0o0o00/blocktopograph/releases). -screenshot +Download apk from [the release page](https://github.com/oO0oO0oO0o0o00/blocktopograph/releases). +screenshot ## Build -Clone project in Android Studio: `File->New->Project from Version Control->Git` +Clone project in Android Studio: `File -> New -> Project from Version Control -> Git` Install missing SDK components. Android Studio would give you the auto-fix options. ### Release-Workflow From ee99a31723567c389e88c680d3f9140f00a45a2b Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Thu, 17 Jan 2019 12:09:54 +0800 Subject: [PATCH 10/83] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15b5d3fe..3d22ba18 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Blocktopograph -By @protolambda. +By [@protolambda](https://github.com/protolambda), [@MithrilMania](https://github.com/MithrilMania) and +[flagmaggot](https://github.com/flagmaggot). This fork is the only one supporting MCPE 1.2~1.9 for now. ## Download From b056600730293f6b76114579a445eeb345bdf57f Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Thu, 17 Jan 2019 16:01:04 +0800 Subject: [PATCH 11/83] * Showing chess grid for non-exist chunks --- .../blocktopograph/chunk/terrain/MeowTeChData.java | 6 +++++- leveldb/src/main/java/com/litl/leveldb/Chunk.java | 4 ++++ leveldb/src/main/jni/Chunk.cc | 14 ++------------ leveldb/src/main/jni/Chunk.h | 13 ++++--------- leveldb/src/main/jni/com_litl_leveldb_Chunk.cc | 10 +++++++++- leveldb/src/main/jni/leveldbjni.cc | 3 +++ 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java index 6ae8d3d5..debd199b 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java @@ -23,6 +23,10 @@ public boolean loadTerrain() { nativeChunk = new com.litl.leveldb.Chunk( this.chunk.worldData.db, this.chunk.x << 4, this.chunk.z << 4, this.chunk.dimension.id); + if (nativeChunk.isDead()) { + nativeChunk = null; + return false; + } return true; } @@ -44,7 +48,7 @@ public int getHighestBlockYUnderAt(int x, int y, int z) { if (val != 0) return yy; } - return 0; + return -1; } @Override diff --git a/leveldb/src/main/java/com/litl/leveldb/Chunk.java b/leveldb/src/main/java/com/litl/leveldb/Chunk.java index ca665a95..6e11ec51 100644 --- a/leveldb/src/main/java/com/litl/leveldb/Chunk.java +++ b/leveldb/src/main/java/com/litl/leveldb/Chunk.java @@ -10,6 +10,10 @@ public Chunk(DB db, int x, int z, int dim) { mPtr = nativeOpen(db.getPtr(), x, z, dim); } + public boolean isDead() { + return mPtr == 0; + } + public int getBlock(int x, int y, int z) { return nativeGetBlock(mPtr, x, y, z); } diff --git a/leveldb/src/main/jni/Chunk.cc b/leveldb/src/main/jni/Chunk.cc index 304c9d83..19e8fab1 100644 --- a/leveldb/src/main/jni/Chunk.cc +++ b/leveldb/src/main/jni/Chunk.cc @@ -10,6 +10,7 @@ #include #include #include +#include "subchunk.h" #ifdef LOG_CHUNK_LOADSAVE #define LOGE_LS(x, ...) LOGE(CAT("Chunk: ", x), ##__VA_ARGS__); @@ -25,16 +26,7 @@ //Init constants. -const int32_t Chunk::msk[] = {0b1, 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, - 0b11111111, - 0b111111111, 0b1111111111, 0b11111111111, - 0b111111111111, - 0b1111111111111, 0b11111111111111, 0b11111111111111}; - -const char Chunk::pattern_name[] = {0x0a, 0x00, 0x00, 0x08, 0x04, 0x00, 'n', 'a', 'm', - 'e'}; - -const char Chunk::pattern_val[] = {0x02, 0x03, 0x00, 'v', 'a', 'l'}; +leveldb::ReadOptions Chunk::readOptions; Chunk::Chunk(leveldb::DB *db, mapkey_t key) : key(key) { @@ -49,8 +41,6 @@ void Chunk::loadSubChunk(leveldb::DB *db, unsigned char which) { LDBKEY_SUBCHUNK(this->key, which) leveldb::Slice slice(key, 0 == this->key.dimension ? 10 : 14); std::string val; - leveldb::ReadOptions readOptions; - readOptions.decompress_allocator = new leveldb::DecompressAllocator; bool hit = db->Get(readOptions, slice, &val).ok(); if (hit) {//Found, detect version. switch (val[0]) { diff --git a/leveldb/src/main/jni/Chunk.h b/leveldb/src/main/jni/Chunk.h index 78b711e2..568ff7d5 100644 --- a/leveldb/src/main/jni/Chunk.h +++ b/leveldb/src/main/jni/Chunk.h @@ -7,23 +7,16 @@ #include #include -#include #include #include "mapkey.h" #include "debug_conf.h" #include "qstr.h" +class SubChunk; + class Chunk { private: - //Constants. - - static const int32_t msk[]; - - static const char pattern_name[]; - - static const char pattern_val[]; - //Member vars. mapkey_t key; @@ -34,6 +27,8 @@ class Chunk { public: + static leveldb::ReadOptions readOptions; + Chunk(leveldb::DB *db, mapkey_t key); ~Chunk(); diff --git a/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc b/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc index 3197044b..6b4814ce 100644 --- a/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc +++ b/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc @@ -11,7 +11,15 @@ static jlong nativeOpen(JNIEnv *env, jclass clazz, jlong dbptr, jint x, jint z, jint dim) { leveldb::DB *db = reinterpret_cast(dbptr); - Chunk *chunk = new Chunk(db, LDBKEY_STRUCT(x, z, dim)); + mapkey_t mapkey = LDBKEY_STRUCT(x, z, dim); + LDBKEY_VERSION(mapkey) + leveldb::Slice skey(key_db, mapkey.dimension == 0 ? 9 : 13); + std::string str; + leveldb::Status status = db->Get(Chunk::readOptions, skey, &str); + if (status.ok()) { + if (str[0] < 7)return NULL; + } else return NULL; + Chunk *chunk = new Chunk(db, mapkey); return reinterpret_cast(chunk); } diff --git a/leveldb/src/main/jni/leveldbjni.cc b/leveldb/src/main/jni/leveldbjni.cc index 0fe69826..28d53a5f 100644 --- a/leveldb/src/main/jni/leveldbjni.cc +++ b/leveldb/src/main/jni/leveldbjni.cc @@ -1,4 +1,5 @@ #include "leveldbjni.h" +#include "Chunk.h" extern int register_com_litl_leveldb_DB(JNIEnv *env); @@ -42,5 +43,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { register_com_litl_leveldb_Iterator(env); register_com_litl_leveldb_Chunk(env); + Chunk::readOptions.decompress_allocator = new leveldb::DecompressAllocator; + return JNI_VERSION_1_6; } From 0d36f5afc51ff98db311038746ca8b2df0cae4d9 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Sat, 19 Jan 2019 16:20:25 +0800 Subject: [PATCH 12/83] + Java rewrite of V1.2+ chunks implementation + Memory leaks fix (partial) via the use of `WeakReference`s and making `AsyncTask`s static, native leak NOT fix. --- app/build.gradle | 2 +- .../mithrilmania/blocktopograph/World.java | 115 ++- .../blocktopograph/WorldActivity.java | 387 +++++----- .../blocktopograph/WorldData.java | 131 ++-- .../blocktopograph/chunk/Chunk.java | 31 +- .../blocktopograph/chunk/ChunkData.java | 8 +- .../blocktopograph/chunk/ChunkManager.java | 63 +- .../blocktopograph/chunk/NBTChunkData.java | 8 +- .../blocktopograph/chunk/Version.java | 39 +- .../chunk/terrain/MeowTeChData.java | 126 ---- .../chunk/terrain/TerrainChunkData.java | 19 +- .../chunk/terrain/V0_9_TerrainChunkData.java | 35 +- .../chunk/terrain/V1_0_TerrainChunkData.java | 62 +- .../chunk/terrain/V1_1_TerrainChunkData.java | 63 +- .../terrain/V1_2_Plus_TerrainChunkData.java | 267 +++++++ .../blocktopograph/map/BlockNameResolver.java | 276 +++++++ .../blocktopograph/map/MCTileProvider.java | 199 ++---- .../blocktopograph/map/MapFragment.java | 672 +++++++++--------- .../blocktopograph/map/MarkerAsyncTask.java | 51 +- .../blocktopograph/map/MarkerManager.java | 56 +- .../map/renderer/BiomeRenderer.java | 44 +- .../map/renderer/BlockLightRenderer.java | 58 +- .../map/renderer/CaveRenderer.java | 52 +- .../map/renderer/ChessPatternRenderer.java | 48 +- .../map/renderer/DebugRenderer.java | 33 +- .../map/renderer/GrassRenderer.java | 39 +- .../map/renderer/HeightmapRenderer.java | 59 +- .../map/renderer/MapRenderer.java | 24 +- .../map/renderer/NetherRenderer.java | 60 +- .../map/renderer/SatelliteRenderer.java | 40 +- .../map/renderer/SlimeChunkRenderer.java | 41 +- .../map/renderer/XRayRenderer.java | 53 +- .../nbt/convert/NBTInputStream.java | 46 +- leveldb/build.gradle | 5 +- .../src/main/java/com/litl/leveldb/Chunk.java | 39 - leveldb/src/main/jni/Android.mk | 12 +- leveldb/src/main/jni/Application.mk | 4 + leveldb/src/main/jni/Chunk.cc | 78 -- leveldb/src/main/jni/Chunk.h | 39 - leveldb/src/main/jni/blocknames.cc | 277 -------- leveldb/src/main/jni/blocknames.h | 17 - .../src/main/jni/com_litl_leveldb_Chunk.cc | 53 -- leveldb/src/main/jni/com_litl_leveldb_DB.cc | 220 +++--- leveldb/src/main/jni/debug_conf.h | 34 - leveldb/src/main/jni/leveldbjni.cc | 6 - leveldb/src/main/jni/mapkey.h | 51 -- leveldb/src/main/jni/qstr.h | 23 - leveldb/src/main/jni/subchunk.cc | 163 ----- leveldb/src/main/jni/subchunk.h | 48 -- 49 files changed, 1927 insertions(+), 2349 deletions(-) delete mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/map/BlockNameResolver.java delete mode 100644 leveldb/src/main/java/com/litl/leveldb/Chunk.java delete mode 100644 leveldb/src/main/jni/Chunk.cc delete mode 100644 leveldb/src/main/jni/Chunk.h delete mode 100644 leveldb/src/main/jni/blocknames.cc delete mode 100644 leveldb/src/main/jni/blocknames.h delete mode 100644 leveldb/src/main/jni/com_litl_leveldb_Chunk.cc delete mode 100644 leveldb/src/main/jni/debug_conf.h delete mode 100644 leveldb/src/main/jni/mapkey.h delete mode 100644 leveldb/src/main/jni/qstr.h delete mode 100644 leveldb/src/main/jni/subchunk.cc delete mode 100644 leveldb/src/main/jni/subchunk.h diff --git a/app/build.gradle b/app/build.gradle index 6cc14830..b252b39a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 28 defaultConfig { - applicationId 'com.rbq2012.blocktopograph' + applicationId 'rbq2012.blocktopograph' minSdkVersion 16 targetSdkVersion 28 versionCode 10 diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/World.java b/app/src/main/java/com/mithrilmania/blocktopograph/World.java index 4680dd9c..6f997c31 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/World.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/World.java @@ -15,62 +15,15 @@ public class World implements Serializable { - private static final long serialVersionUID = 792709417041090031L; - - public String getWorldDisplayName() { - if (worldName == null) return null; - //return worldname, without special color codes - // (character prefixed by the section-sign character) - // Short quick regex, shouldn't affect performance too much - return worldName.replaceAll("\u00A7.", ""); - } - - public enum SpecialDBEntryType { - - //Who came up with the formatting for these NBT keys is CRAZY - // (PascalCase, camelCase, snake_case, lowercase, m-prefix(Android), tilde-prefix; it's all there!) - BIOME_DATA("BiomeData"), - OVERWORLD("Overworld"), - M_VILLAGES("mVillages"), - PORTALS("portals"), - LOCAL_PLAYER("~local_player"), - AUTONOMOUS_ENTITIES("AutonomousEntities"), - DIMENSION_0("dimension0"), - DIMENSION_1("dimension1"), - DIMENSION_2("dimension2"); - - public final String keyName; - public final byte[] keyBytes; - - SpecialDBEntryType(String keyName) { - this.keyName = keyName; - this.keyBytes = keyName.getBytes(NBTConstants.CHARSET); - } - } - // The World (just a WorldData handle) is serializable, this is the tag used in the android workflow public static final String ARG_WORLD_SERIALIZED = "world_ser"; - - + private static final long serialVersionUID = 792709417041090031L; public final String worldName; - public final File worldFolder; - - private transient WorldData worldData; - - public final File levelFile; - public CompoundTag level; - - public static class WorldLoadException extends Exception { - private static final long serialVersionUID = 1812348294537392782L; - - public WorldLoadException(String msg) { - super(msg); - } - } - + private transient WorldData worldData; + private MarkerManager markersManager; public World(File worldFolder) throws WorldLoadException { @@ -99,6 +52,14 @@ public World(File worldFolder) throws WorldLoadException { } + public String getWorldDisplayName() { + if (worldName == null) return null; + //return worldname, without special color codes + // (character prefixed by the section-sign character) + // Short quick regex, shouldn't affect performance too much + return worldName.replaceAll("\u00A7.", ""); + } + public long getWorldSeed() { if (this.level == null) return 0; @@ -111,8 +72,6 @@ public void writeLevel(CompoundTag level) throws IOException { this.level = level; } - private MarkerManager markersManager; - public MarkerManager getMarkerManager() { if (markersManager == null) markersManager = new MarkerManager(new File(this.worldFolder, "markers.txt")); @@ -127,16 +86,21 @@ public String getID() { return this.worldFolder.getName(); } - public WorldData getWorldData() { if (this.worldData == null) { - ///Meow - boolean isMeow = ((IntTag) level.getChildTagByKey("StorageVersion")).getValue() >= 7; - this.worldData = new WorldData(this, isMeow); + this.worldData = new WorldData(this); } return this.worldData; } + public void closeDown() throws WorldData.WorldDBException { + if (this.worldData != null) this.worldData.closeDB(); + } + + public void pause() throws WorldData.WorldDBException { + closeDown(); + } + /* public byte[] loadChunkData(int chunkX, int chunkZ, ChunkTag dataType, Dimension dimension) throws WorldData.WorldDBLoadException, WorldData.WorldDBException { return getWorldData().getChunkData(chunkX, chunkZ, dataType, dimension); @@ -167,14 +131,6 @@ public ChunkData createEmptyChunkData(int chunkX, int chunkZ, ChunkTag dataType, } */ - public void closeDown() throws WorldData.WorldDBException { - if (this.worldData != null) this.worldData.closeDB(); - } - - public void pause() throws WorldData.WorldDBException { - closeDown(); - } - public void resume() throws WorldData.WorldDBException { this.getWorldData().openDB(); @@ -206,4 +162,35 @@ public void logDBKeys() { e.printStackTrace(); } } + + public enum SpecialDBEntryType { + + //Who came up with the formatting for these NBT keys is CRAZY + // (PascalCase, camelCase, snake_case, lowercase, m-prefix(Android), tilde-prefix; it's all there!) + BIOME_DATA("BiomeData"), + OVERWORLD("Overworld"), + M_VILLAGES("mVillages"), + PORTALS("portals"), + LOCAL_PLAYER("~local_player"), + AUTONOMOUS_ENTITIES("AutonomousEntities"), + DIMENSION_0("dimension0"), + DIMENSION_1("dimension1"), + DIMENSION_2("dimension2"); + + public final String keyName; + public final byte[] keyBytes; + + SpecialDBEntryType(String keyName) { + this.keyName = keyName; + this.keyBytes = keyName.getBytes(NBTConstants.CHARSET); + } + } + + public static class WorldLoadException extends Exception { + private static final long serialVersionUID = 1812348294537392782L; + + public WorldLoadException(String msg) { + super(msg); + } + } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java index e7b38460..4a5a2611 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java @@ -71,46 +71,46 @@ synchronized public FirebaseAnalytics getFirebaseAnalytics() { public enum CustomFirebaseEvent { //max 32 chars: "0123456789abcdef0123456789abcdef" - MAPFRAGMENT_OPEN( "map_fragment_open"), - MAPFRAGMENT_RESUME( "map_fragment_resume"), - MAPFRAGMENT_RESET( "map_fragment_reset"), - NBT_EDITOR_OPEN( "nbt_editor_open"), - NBT_EDITOR_SAVE( "nbt_editor_save"), - WORLD_OPEN( "world_open"), - WORLD_RESUME( "world_resume"), - GPS_PLAYER( "gps_player"), - GPS_MULTIPLAYER( "gps_multiplayer"), - GPS_SPAWN( "gps_spawn"), - GPS_MARKER( "gps_marker"), - GPS_COORD( "gps_coord"); + MAPFRAGMENT_OPEN("map_fragment_open"), + MAPFRAGMENT_RESUME("map_fragment_resume"), + MAPFRAGMENT_RESET("map_fragment_reset"), + NBT_EDITOR_OPEN("nbt_editor_open"), + NBT_EDITOR_SAVE("nbt_editor_save"), + WORLD_OPEN("world_open"), + WORLD_RESUME("world_resume"), + GPS_PLAYER("gps_player"), + GPS_MULTIPLAYER("gps_multiplayer"), + GPS_SPAWN("gps_spawn"), + GPS_MARKER("gps_marker"), + GPS_COORD("gps_coord"); public final String eventID; - CustomFirebaseEvent(String eventID){ + CustomFirebaseEvent(String eventID) { this.eventID = eventID; } } @Override - public void logFirebaseEvent(CustomFirebaseEvent firebaseEvent){ + public void logFirebaseEvent(CustomFirebaseEvent firebaseEvent) { getFirebaseAnalytics().logEvent(firebaseEvent.eventID, new Bundle()); } @Override - public void logFirebaseEvent(CustomFirebaseEvent firebaseEvent, Bundle eventContent){ + public void logFirebaseEvent(CustomFirebaseEvent firebaseEvent, Bundle eventContent) { getFirebaseAnalytics().logEvent(firebaseEvent.eventID, eventContent); } @Override public void showActionBar() { ActionBar bar = getSupportActionBar(); - if(bar != null) bar.show(); + if (bar != null) bar.show(); } @Override public void hideActionBar() { ActionBar bar = getSupportActionBar(); - if(bar != null) bar.hide(); + if (bar != null) bar.hide(); } @Override @@ -119,14 +119,14 @@ public void onWindowFocusChanged(boolean hasFocus) { // immersive fullscreen for Android Kitkat and higher if (hasFocus && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { ActionBar bar = getSupportActionBar(); - if(bar != null) bar.hide(); + if (bar != null) bar.hide(); this.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } } @@ -143,7 +143,7 @@ protected void onCreate(Bundle savedInstanceState) { this.world = (World) (savedInstanceState == null ? getIntent().getSerializableExtra(World.ARG_WORLD_SERIALIZED) : savedInstanceState.getSerializable(World.ARG_WORLD_SERIALIZED)); - if(world == null){ + if (world == null) { //WTF, try going back to the previous screen by finishing this hopeless activity... finish(); } @@ -170,8 +170,6 @@ protected void onCreate(Bundle savedInstanceState) { toggle.syncState(); - - View headerView = navigationView.getHeaderView(0); assert headerView != null; @@ -209,12 +207,10 @@ Send anonymous world data to the Firebase (Google analytics for Android) server. bundle.putString("name", this.world.getWorldDisplayName()); - // anonymous global counter of opened worlds logFirebaseEvent(CustomFirebaseEvent.WORLD_OPEN, bundle); - // Open the world-map as default content openWorldMap(); @@ -231,14 +227,14 @@ Send anonymous world data to the Firebase (Google analytics for Android) server. } @Override - public void onStart(){ + public void onStart() { Log.d("World activity starting..."); super.onStart(); Log.d("World activity started"); } @Override - public void onResume(){ + public void onResume() { Log.d("World activity resuming..."); super.onResume(); @@ -255,7 +251,7 @@ public void onResume(){ } @Override - public void onPause(){ + public void onPause() { Log.d("World activity pausing..."); super.onPause(); @@ -269,14 +265,14 @@ public void onPause(){ } @Override - public void onStop(){ + public void onStop() { Log.d("World activity stopping..."); super.onStop(); Log.d("World activity stopped"); } @Override - public void onDestroy(){ + public void onDestroy() { Log.d("World activity destroying..."); super.onDestroy(); Log.d("World activity destroyed..."); @@ -299,31 +295,31 @@ public void onBackPressed() { if (count == 0) { new AlertDialog.Builder(this) - .setMessage(R.string.ask_close_world) - .setCancelable(false) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - WorldActivity.this.finish(); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); + .setMessage(R.string.ask_close_world) + .setCancelable(false) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + WorldActivity.this.finish(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); - } else if(confirmContentClose != null){ + } else if (confirmContentClose != null) { //An important fragment is opened, // something that couldn't be reopened in its current state easily, // ask the user if he/she intended to close it. new AlertDialog.Builder(this) - .setMessage(confirmContentClose) - .setCancelable(false) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - manager.popBackStack(); - confirmContentClose = null; - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); + .setMessage(confirmContentClose) + .setCancelable(false) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + manager.popBackStack(); + confirmContentClose = null; + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); } else { //fragment is open, but it may be closed without warning manager.popBackStack(); @@ -365,8 +361,8 @@ public boolean onNavigationItemSelected(MenuItem item) { assert drawer != null; - switch (id){ - case(R.id.nav_world_show_map): + switch (id) { + case (R.id.nav_world_show_map): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -374,14 +370,14 @@ public void onOpen() { } }); break; - case(R.id.nav_world_select): + case (R.id.nav_world_select): //close activity; back to world selection screen closeWorldActivity(); break; - case(R.id.nav_singleplayer_nbt): + case (R.id.nav_singleplayer_nbt): openPlayerEditor(); break; - case(R.id.nav_multiplayer_nbt): + case (R.id.nav_multiplayer_nbt): openMultiplayerEditor(); break; /*case(R.id.nav_inventory): @@ -389,72 +385,72 @@ public void onOpen() { //This feature is planned, but not yet implemented, // use the generic NBT editor for now... break;*/ - case(R.id.nav_world_nbt): + case (R.id.nav_world_nbt): openLevelEditor(); break; /*case(R.id.nav_tools): //TODO open tools menu (world downloader/importer/exporter maybe?) break;*/ - case(R.id.nav_overworld_satellite): + case (R.id.nav_overworld_satellite): changeMapType(MapType.OVERWORLD_SATELLITE, Dimension.OVERWORLD); break; - case(R.id.nav_overworld_cave): + case (R.id.nav_overworld_cave): changeMapType(MapType.OVERWORLD_CAVE, Dimension.OVERWORLD); break; - case(R.id.nav_overworld_slime_chunk): + case (R.id.nav_overworld_slime_chunk): changeMapType(MapType.OVERWORLD_SLIME_CHUNK, Dimension.OVERWORLD); break; /*case(R.id.nav_overworld_debug): changeMapType(MapType.DEBUG); //for debugging tiles positions, rendering, etc. break;*/ - case(R.id.nav_overworld_heightmap): + case (R.id.nav_overworld_heightmap): changeMapType(MapType.OVERWORLD_HEIGHTMAP, Dimension.OVERWORLD); break; - case(R.id.nav_overworld_biome): + case (R.id.nav_overworld_biome): changeMapType(MapType.OVERWORLD_BIOME, Dimension.OVERWORLD); break; - case(R.id.nav_overworld_grass): + case (R.id.nav_overworld_grass): changeMapType(MapType.OVERWORLD_GRASS, Dimension.OVERWORLD); break; - case(R.id.nav_overworld_xray): + case (R.id.nav_overworld_xray): changeMapType(MapType.OVERWORLD_XRAY, Dimension.OVERWORLD); break; - case(R.id.nav_overworld_block_light): + case (R.id.nav_overworld_block_light): changeMapType(MapType.OVERWORLD_BLOCK_LIGHT, Dimension.OVERWORLD); break; - case(R.id.nav_nether_map): + case (R.id.nav_nether_map): changeMapType(MapType.NETHER, Dimension.NETHER); break; - case(R.id.nav_nether_xray): + case (R.id.nav_nether_xray): changeMapType(MapType.NETHER_XRAY, Dimension.NETHER); break; - case(R.id.nav_nether_block_light): + case (R.id.nav_nether_block_light): changeMapType(MapType.NETHER_BLOCK_LIGHT, Dimension.NETHER); break; - case(R.id.nav_end_satellite): + case (R.id.nav_end_satellite): changeMapType(MapType.END_SATELLITE, Dimension.END); break; - case(R.id.nav_end_heightmap): + case (R.id.nav_end_heightmap): changeMapType(MapType.END_HEIGHTMAP, Dimension.END); break; - case(R.id.nav_end_block_light): + case (R.id.nav_end_block_light): changeMapType(MapType.END_BLOCK_LIGHT, Dimension.END); break; - case(R.id.nav_map_opt_toggle_grid): + case (R.id.nav_map_opt_toggle_grid): //toggle the grid this.showGrid = !this.showGrid; //rerender tiles (tiles will render with toggled grid on it now) - if(this.mapFragment != null) this.mapFragment.resetTileView(); + if (this.mapFragment != null) this.mapFragment.resetTileView(); break; - case(R.id.nav_map_opt_filter_markers): + case (R.id.nav_map_opt_filter_markers): //toggle the grid this.mapFragment.openMarkerFilter(); break; - case(R.id.nav_map_opt_toggle_markers): + case (R.id.nav_map_opt_toggle_markers): //toggle markers - if(this.mapFragment != null) this.mapFragment.toggleMarkers(); + if (this.mapFragment != null) this.mapFragment.toggleMarkers(); break; - case(R.id.nav_biomedata_nbt): + case (R.id.nav_biomedata_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -462,7 +458,7 @@ public void onOpen() { } }); break; - case(R.id.nav_overworld_nbt): + case (R.id.nav_overworld_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -470,7 +466,7 @@ public void onOpen() { } }); break; - case(R.id.nav_villages_nbt): + case (R.id.nav_villages_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -478,7 +474,7 @@ public void onOpen() { } }); break; - case(R.id.nav_portals_nbt): + case (R.id.nav_portals_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -486,7 +482,7 @@ public void onOpen() { } }); break; - case(R.id.nav_dimension0_nbt): + case (R.id.nav_dimension0_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -494,7 +490,7 @@ public void onOpen() { } }); break; - case(R.id.nav_dimension1_nbt): + case (R.id.nav_dimension1_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -502,7 +498,7 @@ public void onOpen() { } }); break; - case(R.id.nav_dimension2_nbt): + case (R.id.nav_dimension2_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -510,7 +506,7 @@ public void onOpen() { } }); break; - case(R.id.nav_autonomous_entities_nbt): + case (R.id.nav_autonomous_entities_nbt): changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -518,7 +514,7 @@ public void onOpen() { } }); break; - case(R.id.nav_open_nbt_by_name): { + case (R.id.nav_open_nbt_by_name): { //TODO put this bit in its own method @@ -539,7 +535,7 @@ public void onOpen() { Editable keyNameEditable = keyEditText.getText(); String keyName = keyNameEditable == null ? null : keyNameEditable.toString(); - if(keyName == null || keyName.equals("")){ + if (keyName == null || keyName.equals("")) { Snackbar.make(drawer, R.string.invalid_keyname, Snackbar.LENGTH_LONG) @@ -552,7 +548,7 @@ public void onOpen() { Snackbar.LENGTH_LONG) .setAction("Action", null).show();//TODO maybe add option to create it? else openNBTEditor(dbEntry); - } catch (Exception e){ + } catch (Exception e) { Snackbar.make(drawer, R.string.invalid_keyname, Snackbar.LENGTH_LONG) @@ -600,11 +596,11 @@ public EditableNBT openEditableNbtDbEntry(final String keyName) throws IOExcepti WorldData worldData = world.getWorldData(); try { worldData.openDB(); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } byte[] entryData = worldData.db.get(keyBytes); - if(entryData == null) return null; + if (entryData == null) return null; final ArrayList workCopy = DataConverter.read(entryData); @@ -651,6 +647,7 @@ public void removeRootTag(Tag tag) { /** * Loads local player data "~local-player" or level.dat>"Player" into an EditableNBT. + * * @return EditableNBT, local player NBT data wrapped in a handle to use for saving + metadata * @throws Exception */ @@ -670,27 +667,30 @@ public EditableNBT getEditablePlayer() throws Exception { EditableNBT editableNBT; try { editableNBT = openSpecialEditableNbtDbEntry(World.SpecialDBEntryType.LOCAL_PLAYER); - } catch (IOException e){ + } catch (IOException e) { e.printStackTrace(); throw new Exception("Failed to read \"~local_player\" from the database."); } //check if it is not found in the DB - if(editableNBT == null) editableNBT = openEditableNbtLevel("Player"); + if (editableNBT == null) editableNBT = openEditableNbtLevel("Player"); //check if it is not found in level.dat as well - if(editableNBT == null) throw new Exception("Failed to find \"~local_player\" in DB and \"Player\" in level.dat!"); + if (editableNBT == null) + throw new Exception("Failed to find \"~local_player\" in DB and \"Player\" in level.dat!"); return editableNBT; } - /** Open NBT editor fragment for special database entry */ - public void openSpecialDBEntry(final World.SpecialDBEntryType entryType){ + /** + * Open NBT editor fragment for special database entry + */ + public void openSpecialDBEntry(final World.SpecialDBEntryType entryType) { try { EditableNBT editableEntry = openSpecialEditableNbtDbEntry(entryType); - if(editableEntry == null){ + if (editableEntry == null) { this.openWorldMap(); //TODO better handling of db problems //throw new Exception("\"" + entryType.keyName + "\" not found in DB."); @@ -704,7 +704,8 @@ public void openSpecialDBEntry(final World.SpecialDBEntryType entryType){ e.printStackTrace(); String msg = e.getMessage(); - if(e instanceof IOException) msg = String.format(getString(R.string.failed_to_read_x_from_db), entryType.keyName); + if (e instanceof IOException) + msg = String.format(getString(R.string.failed_to_read_x_from_db), entryType.keyName); new AlertDialog.Builder(WorldActivity.this) .setMessage(msg) @@ -723,7 +724,7 @@ public void onOpen() { } - public void openMultiplayerEditor(){ + public void openMultiplayerEditor() { //takes some time to find all players... @@ -733,8 +734,8 @@ public void openMultiplayerEditor(){ final String[] players = getWorld().getWorldData().getPlayers(); final View content = WorldActivity.this.findViewById(R.id.world_content); - if(players.length == 0){ - if(content != null) Snackbar.make(content, + if (players.length == 0) { + if (content != null) Snackbar.make(content, R.string.no_multiplayer_data_found, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); @@ -771,32 +772,32 @@ public void onClick(DialogInterface dialog, int whichButton) { public void onOpen() { try { openNBTEditor(editableNBT); - } catch (Exception e){ + } catch (Exception e) { new AlertDialog.Builder(WorldActivity.this) - .setMessage(e.getMessage()) - .setCancelable(false) - .setNeutralButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int id) { - changeContentFragment( - new OpenFragmentCallback() { - @Override - public void onOpen() { - openWorldMap(); - } - }); - } - }).show(); + .setMessage(e.getMessage()) + .setCancelable(false) + .setNeutralButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int id) { + changeContentFragment( + new OpenFragmentCallback() { + @Override + public void onOpen() { + openWorldMap(); + } + }); + } + }).show(); } } }); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); - Log.d("Failed to open player entry in DB. key: "+playerKey); - if(content != null) Snackbar.make(content, + Log.d("Failed to open player entry in DB. key: " + playerKey); + if (content != null) Snackbar.make(content, String.format(getString(R.string.failed_read_player_from_db_with_key_x), playerKey), Snackbar.LENGTH_LONG) .setAction("Action", null).show(); @@ -808,52 +809,53 @@ public void onOpen() { .show(); } - public void openPlayerEditor(){ + public void openPlayerEditor() { changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { try { openNBTEditor(getEditablePlayer()); - } catch (Exception e){ + } catch (Exception e) { new AlertDialog.Builder(WorldActivity.this) .setMessage(e.getMessage()) .setCancelable(false) .setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - changeContentFragment(new OpenFragmentCallback() { - @Override - public void onOpen() { - openWorldMap(); + public void onClick(DialogInterface dialog, int id) { + changeContentFragment(new OpenFragmentCallback() { + @Override + public void onOpen() { + openWorldMap(); + } + }); } - }); - } - }).show(); + }).show(); } } }); } - /** Opens an editableNBT for just the subTag if it is not null. - Opens the whole level.dat if subTag is null. **/ - public EditableNBT openEditableNbtLevel(String subTagName){ + /** + * Opens an editableNBT for just the subTag if it is not null. + * Opens the whole level.dat if subTag is null. + **/ + public EditableNBT openEditableNbtLevel(String subTagName) { //make a copy first, the user might not want to save changed tags. final CompoundTag workCopy = world.level.getDeepCopy(); final ArrayList workCopyContents; final String contentTitle; - if(subTagName == null){ + if (subTagName == null) { workCopyContents = workCopy.getValue(); contentTitle = "level.dat"; - } - else{ + } else { workCopyContents = new ArrayList<>(); Tag subTag = workCopy.getChildTagByKey(subTagName); - if(subTag == null) return null; + if (subTag == null) return null; workCopyContents.add(subTag); - contentTitle = "level.dat>"+subTagName; + contentTitle = "level.dat>" + subTagName; } EditableNBT editableNBT = new EditableNBT() { @@ -899,7 +901,7 @@ public void removeRootTag(Tag tag) { return editableNBT; } - public void openLevelEditor(){ + public void openLevelEditor() { changeContentFragment(new OpenFragmentCallback() { @Override public void onOpen() { @@ -913,13 +915,13 @@ public void onOpen() { // splitting allows to pass more sophisticated use of [MapRenderer]s private Dimension dimension = Dimension.OVERWORLD; - public Dimension getDimension(){ + public Dimension getDimension() { return this.dimension; } private MapType mapType = dimension.defaultMapType; - public MapType getMapType(){ + public MapType getMapType() { return this.mapType; } @@ -936,6 +938,7 @@ public boolean getShowGrid() { private boolean fatal = false; + @Override public void onFatalDBError(WorldData.WorldDBException worldDBException) { @@ -943,7 +946,7 @@ public void onFatalDBError(WorldData.WorldDBException worldDBException) { worldDBException.printStackTrace(); //already dead? (happens on multiple onFatalDBError(e) calls) - if(fatal) return; + if (fatal) return; fatal = true; @@ -960,30 +963,31 @@ public void onClick(DialogInterface dialog, int id) { } @Override - public void changeMapType(MapType mapType, Dimension dimension){ + public void changeMapType(MapType mapType, Dimension dimension) { this.mapType = mapType; this.dimension = dimension; //don't forget to do a reset-tileview, the mapfragment should know of this change ASAP. mapFragment.resetTileView(); } - public void closeWorldActivity(){ + public void closeWorldActivity() { //TODO not translation-friendly new AlertDialog.Builder(this) - .setMessage(R.string.confirm_close_world) - .setCancelable(false) - .setIcon(R.drawable.ic_action_exit) - .setPositiveButton(android.R.string.yes, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - //finish this activity - finish(); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); + .setMessage(R.string.confirm_close_world) + .setCancelable(false) + .setIcon(R.drawable.ic_action_exit) + .setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + //finish this activity + mapFragment.closeChunks(); + finish(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); } public interface OpenFragmentCallback { @@ -992,25 +996,25 @@ public interface OpenFragmentCallback { public String confirmContentClose = null; - public void changeContentFragment(final OpenFragmentCallback callback){ + public void changeContentFragment(final OpenFragmentCallback callback) { final FragmentManager manager = getFragmentManager(); // confirmContentClose shouldn't be both used as boolean and as close-message, // this is a bad pattern - if(confirmContentClose != null){ + if (confirmContentClose != null) { new AlertDialog.Builder(this) - .setMessage(confirmContentClose) - .setCancelable(false) - .setPositiveButton(android.R.string.yes, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); - callback.onOpen(); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); + .setMessage(confirmContentClose) + .setCancelable(false) + .setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); + callback.onOpen(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); } else { manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); callback.onOpen(); @@ -1018,8 +1022,10 @@ public void onClick(DialogInterface dialog, int id) { } - /** Open NBT editor fragment for the given editableNBT */ - public void openNBTEditor(EditableNBT editableNBT){ + /** + * Open NBT editor fragment for the given editableNBT + */ + public void openNBTEditor(EditableNBT editableNBT) { // see changeContentFragment(callback) this.confirmContentClose = getString(R.string.confirm_close_nbt_editor); @@ -1035,8 +1041,10 @@ public void openNBTEditor(EditableNBT editableNBT){ } - /** Replace current content fragment with a fresh MapFragment */ - public void openWorldMap(){ + /** + * Replace current content fragment with a fresh MapFragment + */ + public void openWorldMap() { //TODO should this use cached world-position etc.? @@ -1057,18 +1065,19 @@ public World getWorld() { @Override - public void addMarker(AbstractMarker marker){ + public void addMarker(AbstractMarker marker) { mapFragment.addMarker(marker); } - - /** Open a dialog; user chooses chunk-type -> open editor for this type **/ + /** + * Open a dialog; user chooses chunk-type -> open editor for this type + **/ @Override - public void openChunkNBTEditor(final int chunkX, final int chunkZ, final NBTChunkData nbtChunkData, final ViewGroup viewGroup){ + public void openChunkNBTEditor(final int chunkX, final int chunkZ, final NBTChunkData nbtChunkData, final ViewGroup viewGroup) { - if(nbtChunkData == null){ + if (nbtChunkData == null) { //should never happen Log.w("User tried to open null chunkData in the nbt-editor!!!"); return; @@ -1077,14 +1086,14 @@ public void openChunkNBTEditor(final int chunkX, final int chunkZ, final NBTChun try { nbtChunkData.load(); - } catch (Exception e){ + } catch (Exception e) { Snackbar.make(viewGroup, this.getString(R.string.failed_to_load_x, this.getString(R.string.nbt_chunk_data)), Snackbar.LENGTH_LONG) .setAction("Action", null).show(); return; } final List tags = nbtChunkData.tags; - if(tags == null) { + if (tags == null) { new AlertDialog.Builder(this) .setTitle(R.string.nbt_editor) .setMessage(R.string.data_does_not_exist_for_chunk_ask_if_create) @@ -1100,7 +1109,7 @@ public void onClick(DialogInterface dialog, int whichButton) { Snackbar.make(viewGroup, R.string.created_and_saved_chunk_NBT_data, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); WorldActivity.this.openChunkNBTEditor(chunkX, chunkZ, nbtChunkData, viewGroup); - } catch (Exception e){ + } catch (Exception e) { Snackbar.make(viewGroup, R.string.failed_to_create_or_save_chunk_NBT_data, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } @@ -1112,7 +1121,6 @@ public void onClick(DialogInterface dialog, int whichButton) { } - //open nbt editor for entity data changeContentFragment(new OpenFragmentCallback() { @Override @@ -1120,7 +1128,7 @@ public void onOpen() { //make a copy first, the user might not want to save changed tags. final List workCopy = new ArrayList<>(); - for(Tag tag : tags){ + for (Tag tag : tags) { workCopy.add(tag.getDeepCopy()); } @@ -1135,7 +1143,7 @@ public Iterable getTags() { public boolean save() { try { final List saveCopy = new ArrayList<>(); - for(Tag tag : workCopy){ + for (Tag tag : workCopy) { saveCopy.add(tag.getDeepCopy()); } nbtChunkData.tags = saveCopy; @@ -1150,10 +1158,13 @@ public boolean save() { @Override public String getRootTitle() { final String format = "%s (cX:%d;cZ:%d)"; - switch ((nbtChunkData).dataType){ - case ENTITY: return String.format(format, getString(R.string.entity_chunk_data) , chunkX, chunkZ); - case BLOCK_ENTITY: return String.format(format, getString(R.string.tile_entity_chunk_data), chunkX, chunkZ); - default: return String.format(format, getString(R.string.nbt_chunk_data), chunkX, chunkZ); + switch ((nbtChunkData).dataType) { + case ENTITY: + return String.format(format, getString(R.string.entity_chunk_data), chunkX, chunkZ); + case BLOCK_ENTITY: + return String.format(format, getString(R.string.tile_entity_chunk_data), chunkX, chunkZ); + default: + return String.format(format, getString(R.string.nbt_chunk_data), chunkX, chunkZ); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java index 49785ae8..2be46212 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java @@ -8,6 +8,7 @@ import com.litl.leveldb.DB; import java.io.File; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -16,48 +17,53 @@ */ public class WorldData { - public static class WorldDataLoadException extends Exception { - private static final long serialVersionUID = 659185044124115547L; + //another method for debugging, makes it easy to print a readable byte array + private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); - public WorldDataLoadException(String msg) { - super(msg); - } - } + public DB db; - public static class WorldDBException extends Exception { - private static final long serialVersionUID = -3299282170140961220L; + private WeakReference world; - public WorldDBException(String msg) { - super(msg); - } + public WorldData(World world) { + this.world = new WeakReference<>(world); } - public static class WorldDBLoadException extends Exception { - private static final long serialVersionUID = 4412238820886423076L; - - public WorldDBLoadException(String msg) { - super(msg); + static String bytesToHex(byte[] bytes, int start, int end) { + char[] hexChars = new char[(end - start) * 2]; + for (int j = start; j < end; j++) { + int v = bytes[j] & 0xFF; + hexChars[(j - start) * 2] = hexArray[v >>> 4]; + hexChars[(j - start) * 2 + 1] = hexArray[v & 0x0F]; } + return new String(hexChars); } - ///Meow - private boolean mIsMeow; - - private World world; - - public DB db; - - public WorldData(World world, boolean isMeow) { - this.world = world; - - //Meow - this.mIsMeow = isMeow; - android.util.Log.e("233", "mIsMeow=" + isMeow); + private static byte[] getChunkDataKey(int x, int z, ChunkTag type, Dimension dimension, byte subChunk, boolean asSubChunk) { + if (dimension == Dimension.OVERWORLD) { + byte[] key = new byte[asSubChunk ? 10 : 9]; + System.arraycopy(getReversedBytes(x), 0, key, 0, 4); + System.arraycopy(getReversedBytes(z), 0, key, 4, 4); + key[8] = type.dataID; + if (asSubChunk) key[9] = subChunk; + return key; + } else { + byte[] key = new byte[asSubChunk ? 14 : 13]; + System.arraycopy(getReversedBytes(x), 0, key, 0, 4); + System.arraycopy(getReversedBytes(z), 0, key, 4, 4); + System.arraycopy(getReversedBytes(dimension.id), 0, key, 8, 4); + key[12] = type.dataID; + if (asSubChunk) key[13] = subChunk; + return key; + } } - ///Meow - public boolean isMeow() { - return mIsMeow; + private static byte[] getReversedBytes(int i) { + return new byte[]{ + (byte) i, + (byte) (i >> 8), + (byte) (i >> 16), + (byte) (i >> 24) + }; } //load db when needed (does not load it!) @@ -66,7 +72,9 @@ public void load() throws WorldDataLoadException { if (db != null) return; - File dbFile = new File(this.world.worldFolder, "db"); + World world = this.world.get(); + + File dbFile = new File(world.worldFolder, "db"); if (!dbFile.canRead()) { if (!dbFile.setReadable(true, false)) throw new WorldDataLoadException("World-db folder is not readable! World-db folder: " + dbFile.getAbsolutePath()); @@ -76,7 +84,7 @@ public void load() throws WorldDataLoadException { throw new WorldDataLoadException("World-db folder is not writable! World-db folder: " + dbFile.getAbsolutePath()); } - Log.d("WorldFolder: " + this.world.worldFolder.getAbsolutePath()); + Log.d("WorldFolder: " + world.worldFolder.getAbsolutePath()); Log.d("WorldFolder permissions: read: " + dbFile.canRead() + " write: " + dbFile.canWrite()); if (dbFile.listFiles() == null) @@ -106,19 +114,6 @@ public void openDB() throws WorldDBException { } - //another method for debugging, makes it easy to print a readable byte array - final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); - - public static String bytesToHex(byte[] bytes, int start, int end) { - char[] hexChars = new char[(end - start) * 2]; - for (int j = start; j < end; j++) { - int v = bytes[j] & 0xFF; - hexChars[(j - start) * 2] = hexArray[v >>> 4]; - hexChars[(j - start) * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - //close db to make it available for other apps (Minecraft itself!) public void closeDB() throws WorldDBException { if (this.db == null) @@ -188,32 +183,28 @@ public List getDBKeysStartingWith(String startWith) { return items; } - public static byte[] getChunkDataKey(int x, int z, ChunkTag type, Dimension dimension, byte subChunk, boolean asSubChunk) { - if (dimension == Dimension.OVERWORLD) { - byte[] key = new byte[asSubChunk ? 10 : 9]; - System.arraycopy(getReversedBytes(x), 0, key, 0, 4); - System.arraycopy(getReversedBytes(z), 0, key, 4, 4); - key[8] = type.dataID; - if (asSubChunk) key[9] = subChunk; - return key; - } else { - byte[] key = new byte[asSubChunk ? 14 : 13]; - System.arraycopy(getReversedBytes(x), 0, key, 0, 4); - System.arraycopy(getReversedBytes(z), 0, key, 4, 4); - System.arraycopy(getReversedBytes(dimension.id), 0, key, 8, 4); - key[12] = type.dataID; - if (asSubChunk) key[13] = subChunk; - return key; + public static class WorldDataLoadException extends Exception { + private static final long serialVersionUID = 659185044124115547L; + + public WorldDataLoadException(String msg) { + super(msg); } } - public static byte[] getReversedBytes(int i) { - return new byte[]{ - (byte) i, - (byte) (i >> 8), - (byte) (i >> 16), - (byte) (i >> 24) - }; + public static class WorldDBException extends Exception { + private static final long serialVersionUID = -3299282170140961220L; + + public WorldDBException(String msg) { + super(msg); + } + } + + public static class WorldDBLoadException extends Exception { + private static final long serialVersionUID = 4412238820886423076L; + + public WorldDBLoadException(String msg) { + super(msg); + } } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java index fc91f599..1dce4d73 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java @@ -1,44 +1,37 @@ package com.mithrilmania.blocktopograph.chunk; +import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.WorldData; -import com.mithrilmania.blocktopograph.chunk.terrain.MeowTeChData; import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; +import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicReferenceArray; public class Chunk { - public final WorldData worldData; + public final WeakReference worldData; public final int x, z; public final Dimension dimension; private Version version; - ///Meow private AtomicReferenceArray terrain; - private MeowTeChData meowTeChData; private volatile NBTChunkData entity, blockEntity; public Chunk(WorldData worldData, int x, int z, Dimension dimension) { - this.worldData = worldData; + this.worldData = new WeakReference<>(worldData); this.x = x; this.z = z; this.dimension = dimension; - - ///Meow - if (worldData.isMeow()) - meowTeChData = new MeowTeChData(this); - else - terrain = new AtomicReferenceArray<>(256); + terrain = new AtomicReferenceArray<>(16); + Log.e("new Chunk " + x + "," + z); } public TerrainChunkData getTerrain(byte subChunk) throws Version.VersionException { - ///Meow - if (worldData.isMeow()) return meowTeChData; TerrainChunkData data = terrain.get(subChunk & 0xff); if (data == null) { data = this.getVersion().createTerrainChunkData(this, subChunk); @@ -60,10 +53,12 @@ public NBTChunkData getBlockEntity() throws Version.VersionException { public Version getVersion() { ///Meow - if (worldData.isMeow()) return Version.V1_1; + ///if (worldData.isMeow()) return Version.V1_1; if (this.version == null) try { - byte[] data = this.worldData.getChunkData(x, z, ChunkTag.VERSION, dimension, (byte) 0, false); + WorldData worldData = this.worldData.get(); + if (worldData == null) return Version.ERROR; + byte[] data = worldData.getChunkData(x, z, ChunkTag.VERSION, dimension, (byte) 0, false); this.version = Version.getVersion(data); } catch (WorldData.WorldDBLoadException | WorldData.WorldDBException e) { e.printStackTrace(); @@ -77,7 +72,7 @@ public Version getVersion() { //TODO: should we use the heightmap??? public int getHighestBlockYAt(int x, int z) throws Version.VersionException { ///Meow - if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, 255, z); + ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, 255, z); Version cVersion = getVersion(); TerrainChunkData data; @@ -95,7 +90,7 @@ public int getHighestBlockYAt(int x, int z) throws Version.VersionException { public int getHighestBlockYUnderAt(int x, int z, int y) throws Version.VersionException { ///Meow - if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); + ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); Version cVersion = getVersion(); int offset = y % cVersion.subChunkHeight; @@ -119,7 +114,7 @@ public int getHighestBlockYUnderAt(int x, int z, int y) throws Version.VersionEx public int getCaveYUnderAt(int x, int z, int y) throws Version.VersionException { ///Meow - if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); + ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); Version cVersion = getVersion(); int offset = y % cVersion.subChunkHeight; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkData.java index f0f496f3..01ed759a 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkData.java @@ -4,15 +4,15 @@ import com.mithrilmania.blocktopograph.WorldData; import java.io.IOException; +import java.lang.ref.WeakReference; public abstract class ChunkData { - public final Chunk chunk; + public final WeakReference chunk; - - public ChunkData(Chunk chunk){ - this.chunk = chunk; + public ChunkData(Chunk chunk) { + this.chunk = new WeakReference<>(chunk); } public abstract void createEmpty(); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java index fbdf8a06..b6e84941 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java @@ -1,49 +1,60 @@ package com.mithrilmania.blocktopograph.chunk; -import android.annotation.SuppressLint; +import android.util.LruCache; import com.mithrilmania.blocktopograph.WorldData; -import com.mithrilmania.blocktopograph.chunk.terrain.V0_9_TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; -import java.util.HashMap; -import java.util.Map; +import java.lang.ref.WeakReference; public class ChunkManager { - @SuppressLint("UseSparseArrays") - private Map chunks = new HashMap<>(); - - private WorldData worldData; + private LruCache chunks = new LruCache(256) { + @Override + protected Chunk create(Key key) { + return new Chunk(worldData.get(), key.x, key.z, key.dim); + } + }; - public final Dimension dimension; + private WeakReference worldData; - public ChunkManager(WorldData worldData, Dimension dimension){ - this.worldData = worldData; - this.dimension = dimension; + public ChunkManager(WorldData worldData) { + this.worldData = new WeakReference<>(worldData); } - - public static long xzToKey(int x, int z){ - return (((long) x) << 32) | (((long) z) & 0xFFFFFFFFL); + public Chunk getChunk(int cX, int cZ, Dimension dimension) { + Key key = new Key(cX, cZ, dimension); + return chunks.get(key); } - public Chunk getChunk(int cX, int cZ) { - long key = xzToKey(cX, cZ); - Chunk chunk = chunks.get(key); - if(chunk == null) { - chunk = new Chunk(worldData, cX, cZ, dimension); - this.chunks.put(key, chunk); - } - return chunk; + public void disposeAll() { + this.chunks.evictAll(); } - public void disposeAll(){ - this.chunks.clear(); - } + static class Key { + public int x, z; + Dimension dim; + public Key(int x, int z, Dimension dim) { + this.x = x; + this.z = z; + this.dim = dim; + } + + @Override + public int hashCode() { + return (x * 31 + z) * 31 + dim.id; + } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Key)) return false; + Key another = (Key) obj; + return ((x == another.x) && (z == another.z) && (dim != null) + && (another.dim != null) && (dim.id == another.dim.id)); + } + } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java index ac685f61..c40bf6c4 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java @@ -22,7 +22,8 @@ public NBTChunkData(Chunk chunk, ChunkTag dataType) { } public void load() throws WorldData.WorldDBLoadException, WorldData.WorldDBException, IOException { - loadFromByteArray(chunk.worldData.getChunkData(chunk.x, chunk.z, dataType, this.chunk.dimension, (byte) 0, false)); + Chunk chunk = this.chunk.get(); + loadFromByteArray(chunk.worldData.get().getChunkData(chunk.x, chunk.z, dataType, chunk.dimension, (byte) 0, false)); } public void loadFromByteArray(byte[] data) throws IOException { @@ -32,12 +33,13 @@ public void loadFromByteArray(byte[] data) throws IOException { public void write() throws WorldData.WorldDBException, IOException { if (this.tags == null) this.tags = new ArrayList<>(); byte[] data = DataConverter.write(this.tags); - this.chunk.worldData.writeChunkData(this.chunk.x, this.chunk.z, this.dataType, this.chunk.dimension, (byte) 0, false, data); + Chunk chunk = this.chunk.get(); + chunk.worldData.get().writeChunkData(chunk.x, chunk.z, this.dataType, chunk.dimension, (byte) 0, false, data); } @Override public void createEmpty() { - if(this.tags == null) this.tags = new ArrayList<>(); + if (this.tags == null) this.tags = new ArrayList<>(); this.tags.add(new IntTag("Placeholder", 42)); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java index 5d3c57d7..b129b665 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java @@ -3,12 +3,11 @@ import android.util.SparseArray; -import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.chunk.terrain.V0_9_TerrainChunkData; import com.mithrilmania.blocktopograph.chunk.terrain.V1_0_TerrainChunkData; import com.mithrilmania.blocktopograph.chunk.terrain.V1_1_TerrainChunkData; -import com.mithrilmania.blocktopograph.util.ConvertUtil; +import com.mithrilmania.blocktopograph.chunk.terrain.V1_2_Plus_TerrainChunkData; public enum Version { @@ -17,15 +16,16 @@ public enum Version { OLD_LIMITED("v0.2.0", "classic mcpe, 16x16x16x16x18 world, level.dat; introduced in v0.2.0", 1, 128, 1), v0_9("v0.9.0", "infinite xz, zlib leveldb; introduced in v0.9.0", 2, 128, 1), V1_0("v1.0.0", "Stacked sub-chunks, 256 world-height, 16 high sub-chunks; introduced in alpha v1.0.0 (v0.17)", 3, 16, 16), - V1_1("v1.1.0", "Block-light is not stored anymore", 4, 16, 16); + V1_1("v1.1.0", "Block-light is not stored anymore", 4, 16, 16), + V1_2_PLUS("v1.2.0.13", "Global numeric id replaced with string id and per-chunk numeric id", 7, 16, 16); - public static final int LATEST_SUPPORTED_VERSION = V1_1.id; + public static final int LATEST_SUPPORTED_VERSION = V1_2_PLUS.id; public final String displayName, description; public final int id, subChunkHeight, subChunks; - Version(String displayName, String description, int id, int subChunkHeight, int subChunks){ + Version(String displayName, String description, int id, int subChunkHeight, int subChunks) { this.displayName = displayName; this.description = description; this.id = id; @@ -34,25 +34,28 @@ public enum Version { } private static final SparseArray versionMap; + static { versionMap = new SparseArray<>(); - for(Version b : Version.values()){ + for (Version b : Version.values()) { versionMap.put(b.id, b); } } - public static Version getVersion(byte[] data){ + + public static Version getVersion(byte[] data) { //Log.d("Data version: "+ ConvertUtil.bytesToHexStr(data)); //`data` is supposed to be one byte, // but it might grow to contain more data later on, or larger version ids. // Looking up the first byte is sufficient for now. - if(data == null || data.length <= 0) { + if (data == null || data.length <= 0) { return NULL; } else { int versionNumber = data[0] & 0xff; //fallback version - if(versionNumber > LATEST_SUPPORTED_VERSION) { + //You can't just do this... + if (versionNumber > LATEST_SUPPORTED_VERSION) { versionNumber = LATEST_SUPPORTED_VERSION; } @@ -63,7 +66,7 @@ public static Version getVersion(byte[] data){ } public TerrainChunkData createTerrainChunkData(Chunk chunk, byte subChunk) throws VersionException { - switch (this){ + switch (this) { case ERROR: case NULL: return null; @@ -74,15 +77,17 @@ public TerrainChunkData createTerrainChunkData(Chunk chunk, byte subChunk) throw case V1_0: return new V1_0_TerrainChunkData(chunk, subChunk); case V1_1: - return new V1_1_TerrainChunkData(chunk, subChunk); - default: + //This was used as default and the author said: //use the latest version, like nothing will ever happen... return new V1_1_TerrainChunkData(chunk, subChunk); + case V1_2_PLUS: + default: + return new V1_2_Plus_TerrainChunkData(chunk, subChunk); } } public NBTChunkData createEntityChunkData(Chunk chunk) throws VersionException { - switch (this){ + switch (this) { case ERROR: case NULL: return null; @@ -95,7 +100,7 @@ public NBTChunkData createEntityChunkData(Chunk chunk) throws VersionException { } public NBTChunkData createBlockEntityChunkData(Chunk chunk) throws VersionException { - switch (this){ + switch (this) { case ERROR: case NULL: return null; @@ -108,13 +113,13 @@ public NBTChunkData createBlockEntityChunkData(Chunk chunk) throws VersionExcept } @Override - public String toString(){ - return "[MCPE version \""+displayName+"\" (version-code: "+id+")]"; + public String toString() { + return "[MCPE version \"" + displayName + "\" (version-code: " + id + ")]"; } public static class VersionException extends Exception { - VersionException(String msg, Version version){ + VersionException(String msg, Version version) { super(msg + " " + version.toString()); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java deleted file mode 100644 index debd199b..00000000 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/MeowTeChData.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.mithrilmania.blocktopograph.chunk.terrain; - -import android.support.v4.graphics.ColorUtils; -import android.util.Log; - -import com.mithrilmania.blocktopograph.WorldData; -import com.mithrilmania.blocktopograph.chunk.Chunk; -import com.mithrilmania.blocktopograph.util.Color; - -import java.io.IOException; - -public class MeowTeChData extends TerrainChunkData { - - private com.litl.leveldb.Chunk nativeChunk; - - public MeowTeChData(Chunk chunk) { - super(chunk, (byte) 1); - } - - @Override - public boolean loadTerrain() { - if (nativeChunk == null) - nativeChunk = new com.litl.leveldb.Chunk( - this.chunk.worldData.db, this.chunk.x << 4, - this.chunk.z << 4, this.chunk.dimension.id); - if (nativeChunk.isDead()) { - nativeChunk = null; - return false; - } - return true; - } - - @Override - public boolean load2DData() { - return loadTerrain(); - } - - @Override - public byte getBlockTypeId(int x, int y, int z) { - int val = nativeChunk.getBlock(x, y, z); - byte b = (byte) (val >> 8); - return b; - } - - public int getHighestBlockYUnderAt(int x, int y, int z) { - for (int yy = y; yy >= 0; yy--) { - int val = nativeChunk.getBlock(x, y, z); - if (val != 0) - return yy; - } - return -1; - } - - @Override - public byte getBlockData(int x, int y, int z) { - return 0; - } - - @Override - public byte getSkyLightValue(int x, int y, int z) { - return 0; - } - - @Override - public byte getBlockLightValue(int x, int y, int z) { - return 0; - } - - @Override - public boolean supportsBlockLightValues() { - return false; - } - - @Override - public void setBlockTypeId(int x, int y, int z, int type) { - - } - - @Override - public void setBlockData(int x, int y, int z, int newData) { - - } - - @Override - public byte getBiome(int x, int z) { - return 1; - } - - private Color getColor(int x, int z) { - double amp = Math.sin(x * z * z) + 1.2 * Math.cos(z) + 0.2 * Math.sin(x / 2.0) + 0.1 * Math.sin(z / 4.0); - float[] arr = new float[]{96, (float) (0.54 + amp * 2.0 / 25.0), (float) (0.48 + (0.54 + amp * 2.0 / 30.0))}; - return Color.fromRGB(ColorUtils.HSLToColor(arr));//Wrong - } - - @Override - public byte getGrassR(int x, int z) { - //Color c = getColor(x, z); - //Log.e("123", c.toString()); - return 0x56;//(byte) c.red; - } - - @Override - public byte getGrassG(int x, int z) { - return (byte) 0xf9;//(byte) getColor(x, z).green; - } - - @Override - public byte getGrassB(int x, int z) { - return 0x1a;//(byte) getColor(x, z).blue; - } - - @Override - public int getHeightMapValue(int x, int z) { - return getHighestBlockYUnderAt(x, 255, z); - } - - @Override - public void createEmpty() { - - } - - @Override - public void write() throws IOException, WorldData.WorldDBException { - - } -} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java index 83954845..50e3d450 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java @@ -2,6 +2,7 @@ import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkData; +import com.mithrilmania.blocktopograph.util.Noise; public abstract class TerrainChunkData extends ChunkData { @@ -41,5 +42,21 @@ public TerrainChunkData(Chunk chunk, byte subChunk) { public abstract int getHeightMapValue(int x, int z); - + protected int getNoise(int base, int x, int z) { + // noise values are between -1 and 1 + // 0.0001 is added to the coordinates because integer values result in 0 + Chunk chunk = this.chunk.get(); + double oct1 = Noise.noise( + ((double) (chunk.x * 16 + x) / 100.0) + 0.0001, + ((double) (chunk.z * 16 + z) / 100.0) + 0.0001); + double oct2 = Noise.noise( + ((double) (chunk.x * 16 + x) / 20.0) + 0.0001, + ((double) (chunk.z * 16 + z) / 20.0) + 0.0001); + double oct3 = Noise.noise( + ((double) (chunk.x * 16 + x) / 3.0) + 0.0001, + ((double) (chunk.z * 16 + z) / 3.0) + 0.0001); + return (int) (base + 60 + (40 * oct1) + (14 * oct2) + (6 * oct3)); + } + + } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java index ca177522..57888f7d 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java @@ -35,32 +35,33 @@ public V0_9_TerrainChunkData(Chunk chunk, byte subChunk) { @Override public void write() throws IOException, WorldData.WorldDBException { - this.chunk.worldData.writeChunkData(chunk.x, chunk.z, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.dimension, subChunk, false, toByteArray()); + Chunk chunk = this.chunk.get(); + chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.dimension, subChunk, false, toByteArray()); } @Override - public boolean loadTerrain(){ + public boolean loadTerrain() { return tryLoad(); } @Override - public boolean load2DData(){ + public boolean load2DData() { return tryLoad(); } public boolean tryLoad() { - if(buf == null){ + if (buf == null) { try { - byte[] rawData = this.chunk.worldData.getChunkData(chunk.x, chunk.z, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.dimension, subChunk, false); - if(rawData == null) return false; + Chunk chunk = this.chunk.get(); + byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.dimension, subChunk, false); + if (rawData == null) return false; this.buf = ByteBuffer.wrap(rawData); return true; - } catch (Exception e){ + } catch (Exception e) { //data is not present return false; } - } - else return true; + } else return true; } public byte[] toByteArray() throws IOException { @@ -77,36 +78,36 @@ public void createEmpty() { byte sandstone = (byte) 24; //generate super basic terrain (one layer of bedrock, 31 layers of sandstone) - for(x = 0; x < chunkW; x++){ - for(z = 0; z < chunkL; z++){ - for(y = 0; y < chunkH; y++, i++){ + for (x = 0; x < chunkW; x++) { + for (z = 0; z < chunkL; z++) { + for (y = 0; y < chunkH; y++, i++) { chunk[i] = (y == 0 ? bedrock : (y < 32 ? sandstone : 0)); } } } //fill meta-data with 0 - for(; i < POS_SKY_LIGHT; i++){ + for (; i < POS_SKY_LIGHT; i++) { chunk[i] = 0; } //fill blocklight with 0xff - for(; i < POS_BLOCK_LIGHT; i++){ + for (; i < POS_BLOCK_LIGHT; i++) { chunk[i] = (byte) 0xff; } //fill block-light with 0xff - for(; i < POS_HEIGHTMAP; i++){ + for (; i < POS_HEIGHTMAP; i++) { chunk[i] = (byte) 0xff; } //fill heightmap - for(; i < POS_BIOME_DATA; i++){ + for (; i < POS_BIOME_DATA; i++) { chunk[i] = 32; } //fill biome data - for(; i < LENGTH;){ + for (; i < LENGTH; ) { chunk[i++] = 1;//biome: plains chunk[i++] = (byte) 42;//r chunk[i++] = (byte) 42;//g diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java index a31cb3b5..fe73714d 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java @@ -37,40 +37,41 @@ public V1_0_TerrainChunkData(Chunk chunk, byte subChunk) { @Override public void write() throws WorldData.WorldDBException { - this.chunk.worldData.writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); - this.chunk.worldData.writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); + Chunk chunk = this.chunk.get(); + chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); + chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); } @Override public boolean loadTerrain() { - if(terrainData == null){ + if (terrainData == null) { try { - byte[] rawData = this.chunk.worldData.getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); - if(rawData == null) return false; + Chunk chunk = this.chunk.get(); + byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); + if (rawData == null) return false; this.terrainData = ByteBuffer.wrap(rawData); return true; - } catch (Exception e){ + } catch (Exception e) { //data is not present return false; } - } - else return true; + } else return true; } @Override public boolean load2DData() { - if(data2D == null){ + if (data2D == null) { try { - byte[] rawData = this.chunk.worldData.getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); - if(rawData == null) return false; + Chunk chunk = this.chunk.get(); + byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); + if (rawData == null) return false; this.data2D = ByteBuffer.wrap(rawData); return true; - } catch (Exception e){ + } catch (Exception e) { //data is not present return false; } - } - else return true; + } else return true; } @@ -87,9 +88,9 @@ public void createEmpty() { byte sandstone = (byte) 24; //generate super basic terrain (one layer of bedrock, 31 layers of sandstone) - for(x = 0; x < chunkW; x++){ - for(z = 0; z < chunkL; z++){ - for(y = 0, realY = chunkH * this.subChunk; y < chunkH; y++, i++, realY++){ + for (x = 0; x < chunkW; x++) { + for (z = 0; z < chunkL; z++) { + for (y = 0, realY = chunkH * this.subChunk; y < chunkH; y++, i++, realY++) { terrain[i] = (realY == 0 ? bedrock : (realY < 32 ? sandstone : 0)); } } @@ -97,17 +98,17 @@ public void createEmpty() { //fill meta-data with 0 - for(; i < POS_META_DATA; i++){ + for (; i < POS_META_DATA; i++) { terrain[i] = (byte) 0; } //fill blocklight with 0xff - for(; i < POS_BLOCK_LIGHT; i++){ + for (; i < POS_BLOCK_LIGHT; i++) { terrain[i] = (byte) 0xff; } //fill block-light with 0xff - for(; i < TERRAIN_LENGTH; i++){ + for (; i < TERRAIN_LENGTH; i++) { terrain[i] = (byte) 0xff; } @@ -115,18 +116,18 @@ public void createEmpty() { i = 0; - if(this.subChunk == (byte) 0){ + if (this.subChunk == (byte) 0) { byte[] data2d = new byte[DATA2D_LENGTH]; //fill heightmap - for(; i < POS_BIOME_DATA;){ + for (; i < POS_BIOME_DATA; ) { data2d[i++] = 0; data2d[i++] = 32; } //fill biome data - for(; i < DATA2D_LENGTH;){ + for (; i < DATA2D_LENGTH; ) { data2d[i++] = 1;//biome: plains data2d[i++] = (byte) 42;//r data2d[i++] = (byte) 42;//g @@ -218,21 +219,6 @@ public byte getBiome(int x, int z) { return data2D.get(POS_BIOME_DATA + get2Di(x, z)); } - private int getNoise(int base, int x, int z){ - // noise values are between -1 and 1 - // 0.0001 is added to the coordinates because integer values result in 0 - double oct1 = Noise.noise( - ((double) (this.chunk.x * chunkW + x) / 100.0) + 0.0001, - ((double) (this.chunk.z * chunkL + z) / 100.0) + 0.0001); - double oct2 = Noise.noise( - ((double) (this.chunk.x * chunkW + x) / 20.0) + 0.0001, - ((double) (this.chunk.z * chunkL + z) / 20.0) + 0.0001); - double oct3 = Noise.noise( - ((double) (this.chunk.x * chunkW + x) / 3.0) + 0.0001, - ((double) (this.chunk.z * chunkL + z) / 3.0) + 0.0001); - return (int) (base + 60 + (40 * oct1) + (14 * oct2) + (6 * oct3)); - } - /* MCPE 1.0 stopped embedding foliage color data in the chunk data, so now we fake the colors by combining biome colors with Perlin noise diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java index 6d9dc4c4..09b44308 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java @@ -35,40 +35,41 @@ public V1_1_TerrainChunkData(Chunk chunk, byte subChunk) { @Override public void write() throws WorldData.WorldDBException { - this.chunk.worldData.writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); - this.chunk.worldData.writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); + Chunk chunk = this.chunk.get(); + chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); + chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); } @Override public boolean loadTerrain() { - if(terrainData == null){ + if (terrainData == null) { try { - byte[] rawData = this.chunk.worldData.getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); - if(rawData == null) return false; + Chunk chunk = this.chunk.get(); + byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); + if (rawData == null) return false; this.terrainData = ByteBuffer.wrap(rawData); return true; - } catch (Exception e){ + } catch (Exception e) { //data is not present return false; } - } - else return true; + } else return true; } @Override public boolean load2DData() { - if(data2D == null){ + if (data2D == null) { try { - byte[] rawData = this.chunk.worldData.getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); - if(rawData == null) return false; + Chunk chunk = this.chunk.get(); + byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); + if (rawData == null) return false; this.data2D = ByteBuffer.wrap(rawData); return true; - } catch (Exception e){ + } catch (Exception e) { //data is not present return false; } - } - else return true; + } else return true; } @@ -85,9 +86,9 @@ public void createEmpty() { byte sandstone = (byte) 24; //generate super basic terrain (one layer of bedrock, 31 layers of sandstone) - for(x = 0; x < chunkW; x++){ - for(z = 0; z < chunkL; z++){ - for(y = 0, realY = chunkH * this.subChunk; y < chunkH; y++, i++, realY++){ + for (x = 0; x < chunkW; x++) { + for (z = 0; z < chunkL; z++) { + for (y = 0, realY = chunkH * this.subChunk; y < chunkH; y++, i++, realY++) { terrain[i] = (realY == 0 ? bedrock : (realY < 32 ? sandstone : 0)); } } @@ -95,12 +96,12 @@ public void createEmpty() { //fill meta-data with 0 - for(; i < POS_META_DATA; i++){ + for (; i < POS_META_DATA; i++) { terrain[i] = (byte) 0; } //fill block-light with 0xff - for(; i < TERRAIN_LENGTH; i++){ + for (; i < TERRAIN_LENGTH; i++) { terrain[i] = (byte) 0xff; } @@ -108,18 +109,18 @@ public void createEmpty() { i = 0; - if(this.subChunk == (byte) 0){ + if (this.subChunk == (byte) 0) { byte[] data2d = new byte[DATA2D_LENGTH]; //fill heightmap - for(; i < POS_BIOME_DATA;){ + for (; i < POS_BIOME_DATA; ) { data2d[i++] = 0; data2d[i++] = 32; } //fill biome data - for(; i < DATA2D_LENGTH;){ + for (; i < DATA2D_LENGTH; ) { data2d[i++] = 1;//biome: plains data2d[i++] = (byte) 42;//r data2d[i++] = (byte) 42;//g @@ -152,8 +153,7 @@ public byte getBlockData(int x, int y, int z) { //dualData = terrainData.get(POS_META_DATA + (offset >>> 1)); dualData = terrainData.get(terrainData.limit() - (offset >>> 1)); return (byte) ((offset & 1) == 1 ? ((dualData >>> 4) & 0xf) : (dualData & 0xf)); - } - catch (Exception e) { + } catch (Exception e) { return (byte) ((offset & 1) == 1 ? ((dualData >>> 4) & 0xf) : (dualData & 0xf)); } @@ -216,21 +216,6 @@ public byte getBiome(int x, int z) { return data2D.get(POS_BIOME_DATA + get2Di(x, z)); } - private int getNoise(int base, int x, int z){ - // noise values are between -1 and 1 - // 0.0001 is added to the coordinates because integer values result in 0 - double oct1 = Noise.noise( - ((double) (this.chunk.x * chunkW + x) / 100.0) + 0.0001, - ((double) (this.chunk.z * chunkL + z) / 100.0) + 0.0001); - double oct2 = Noise.noise( - ((double) (this.chunk.x * chunkW + x) / 20.0) + 0.0001, - ((double) (this.chunk.z * chunkL + z) / 20.0) + 0.0001); - double oct3 = Noise.noise( - ((double) (this.chunk.x * chunkW + x) / 3.0) + 0.0001, - ((double) (this.chunk.z * chunkL + z) / 3.0) + 0.0001); - return (int) (base + 60 + (40 * oct1) + (14 * oct2) + (6 * oct3)); - } - /* MCPE 1.0 stopped embedding foliage color data in the chunk data, so now we fake the colors by combining biome colors with Perlin noise diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java new file mode 100644 index 00000000..14eff67e --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java @@ -0,0 +1,267 @@ +package com.mithrilmania.blocktopograph.chunk.terrain; + +import com.mithrilmania.blocktopograph.Log; +import com.mithrilmania.blocktopograph.WorldData; +import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.ChunkTag; +import com.mithrilmania.blocktopograph.map.Biome; +import com.mithrilmania.blocktopograph.map.Block; +import com.mithrilmania.blocktopograph.map.BlockNameResolver; +import com.mithrilmania.blocktopograph.nbt.convert.NBTInputStream; +import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; +import com.mithrilmania.blocktopograph.nbt.tags.ShortTag; +import com.mithrilmania.blocktopograph.nbt.tags.StringTag; +import com.mithrilmania.blocktopograph.util.Noise; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +public class V1_2_Plus_TerrainChunkData extends TerrainChunkData { + + //There could be multiple BlockStorage but we can just display the main. + public volatile IntBuffer mainStorage; + public volatile ByteBuffer data2D; + public volatile List palette; + public volatile int blockTypes, blockCodeLenth; + + public static final int chunkW = 16, chunkL = 16, chunkH = 16; + + public static final int area = chunkW * chunkL; + public static final int vol = area * chunkH; + + public static final int POS_HEIGHTMAP = 0; + // it looks like each biome takes 2 bytes, and the first 1 byte of every 2 bytes is always 0!? + public static final int POS_BIOME_DATA = POS_HEIGHTMAP + area + area; + public static final int DATA2D_LENGTH = POS_BIOME_DATA + area; + + private static final int[] msk = {0b1, 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, + 0b11111111, + 0b111111111, 0b1111111111, 0b11111111111, + 0b111111111111, + 0b1111111111111, 0b11111111111111, 0b11111111111111}; + + public V1_2_Plus_TerrainChunkData(Chunk chunk, byte subChunk) { + super(chunk, subChunk); + } + + @Override + public void write() throws WorldData.WorldDBException { + Chunk chunk = this.chunk.get(); + //this.chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); + chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); + } + + @Override + public boolean loadTerrain() { + if (mainStorage == null) { + try { + Chunk chunk = this.chunk.get(); + byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); + if (rawData == null) return false; + ByteBuffer raw = ByteBuffer.wrap(rawData); + raw.order(ByteOrder.LITTLE_ENDIAN); + switch (rawData[0]) { + case 0: + raw.position(1); + break; + case 8: + raw.position(2); + break; + default: + return false; + } + loadBlockStorage(raw); + return true; + } catch (Exception e) { + //data is not present + return false; + } + } else return true; + } + + private void loadBlockStorage(ByteBuffer raw) throws IOException { + + //Read BlockState length. + //this byte = (length << 2) | serializedType. + blockCodeLenth = (raw.get() & 0xff) >> 1; + + //We use this much of bytes to store BlockStates. + int bufsize = (4095 / (32 / blockCodeLenth) + 1) << 2; + ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + mainStorage = byteBuffer.asIntBuffer(); + + //No convenient way copy these stuff. + byteBuffer.put(raw.array(), raw.position(), bufsize); + raw.position(raw.position() + bufsize); + + //Palette items count. + int psize = raw.getInt(); + + //Construct the palette. Each is a piece of nbt data. + palette = new ArrayList<>(16); + ByteArrayInputStream bais = new ByteArrayInputStream(raw.array()); + bais.skip(raw.position()); + NBTInputStream nis = new NBTInputStream(bais, false); + for (int i = 0; i < psize; i++) { + CompoundTag tag = (CompoundTag) nis.readTag(); + String name = ((StringTag) tag.getChildTagByKey("name")).getValue(); + int data = ((ShortTag) tag.getChildTagByKey("val")).getValue(); + palette.add(BlockNameResolver.resolve(name) << 8 | data); + } + raw.position(raw.position() + nis.getReadCount()); + } + + @Override + public boolean load2DData() { + if (data2D == null) { + try { + Chunk chunk = this.chunk.get(); + byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); + if (rawData == null) return false; + this.data2D = ByteBuffer.wrap(rawData); + return true; + } catch (Exception e) { + //data is not present + return false; + } + } else return true; + } + + + @Override + public void createEmpty() { + + int i = 0; + + if (this.subChunk == (byte) 0) { + + byte[] data2d = new byte[DATA2D_LENGTH]; + + //fill heightmap + for (; i < POS_BIOME_DATA; ) { + data2d[i++] = 0; + data2d[i++] = 32; + } + + //fill biome data + for (; i < DATA2D_LENGTH; ) { + data2d[i++] = 1;//biome: plains + data2d[i++] = (byte) 42;//r + data2d[i++] = (byte) 42;//g + data2d[i++] = (byte) 42;//b + } + + this.data2D = ByteBuffer.wrap(data2d); + } + + + } + + private int getBlockState(int x, int y, int z) { + int codeOffset = getOffset(x, y, z); + int intCapa = 32 / blockCodeLenth; + int stick = mainStorage.get(codeOffset / intCapa); + int ind = (stick >> (codeOffset % intCapa * blockCodeLenth)) & msk[blockCodeLenth - 1]; + return palette.get(ind); + } + + + @Override + public byte getBlockTypeId(int x, int y, int z) { + if (x >= chunkW || y >= chunkH || z >= chunkL || x < 0 || y < 0 || z < 0) { + return 0; + } + return (byte) (getBlockState(x, y, z) >>> 8); + } + + @Override + public byte getBlockData(int x, int y, int z) { + if (x >= chunkW || y >= chunkH || z >= chunkL || x < 0 || y < 0 || z < 0) { + return 0; + } + return (byte) (getBlockState(x, y, z) & 0xf); + } + + @Override + public byte getSkyLightValue(int x, int y, int z) { + if (x >= chunkW || y >= chunkH || z >= chunkL || x < 0 || y < 0 || z < 0) { + return 0; + } + return 0; + } + + @Override + public byte getBlockLightValue(int x, int y, int z) { + //block light is not stored anymore + return 0; + } + + @Override + public boolean supportsBlockLightValues() { + return false; + } + + /** + * Sets a block type, and also set the corresponding dirty table entry and set the saving flag. + */ + @Override + public void setBlockTypeId(int x, int y, int z, int type) { + if (x >= chunkW || y >= chunkH || z >= chunkL || x < 0 || y < 0 || z < 0) { + return; + } + } + + @Override + public void setBlockData(int x, int y, int z, int newData) { + if (x >= chunkW || y >= chunkH || z >= chunkL || x < 0 || y < 0 || z < 0) { + return; + } + } + + private int getOffset(int x, int y, int z) { + return (((x << 4) | z) << 4) | y; + } + + @Override + public byte getBiome(int x, int z) { + return data2D.get(POS_BIOME_DATA + get2Di(x, z)); + } + + @Override + public byte getGrassR(int x, int z) { + Biome biome = Biome.getBiome(getBiome(x, z) & 0xff); + int res = getNoise(30 + (biome.color.red / 5), x, z); + return (byte) (res > 0xff ? 0xff : (res < 0 ? 0 : res)); + } + + @Override + public byte getGrassG(int x, int z) { + Biome biome = Biome.getBiome(getBiome(x, z) & 0xff); + int res = getNoise(120 + (biome.color.green / 5), x, z); + return (byte) (res > 0xff ? 0xff : (res < 0 ? 0 : res)); + } + + @Override + public byte getGrassB(int x, int z) { + Biome biome = Biome.getBiome(getBiome(x, z) & 0xff); + int res = getNoise(30 + (biome.color.blue / 5), x, z); + return (byte) (res > 0xff ? 0xff : (res < 0 ? 0 : res)); + } + + private int get2Di(int x, int z) { + return z * chunkL + x; + } + + @Override + public int getHeightMapValue(int x, int z) { + short h = data2D.getShort(POS_HEIGHTMAP + (get2Di(x, z) << 1)); + return ((h & 0xff) << 8) | ((h >> 8) & 0xff);//little endian to big endian + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/BlockNameResolver.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/BlockNameResolver.java new file mode 100644 index 00000000..2dcf839e --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/BlockNameResolver.java @@ -0,0 +1,276 @@ +package com.mithrilmania.blocktopograph.map; + +public final class BlockNameResolver { + + static private final String[] NAMES = new String[]{ + "air", + "stone", + "grass", + "dirt", + "cobblestone", + "planks", + "sapling", + "bedrock", + "flowing_water", + "water", + "flowing_lava", + "lava", + "sand", + "gravel", + "gold_ore", + "iron_ore", + "coal_ore", + "log", + "leaves", + "sponge", + "glass", + "lapis_ore", + "lapis_block", + "dispenser", + "sandstone", + "noteblock", + "bed", + "golden_rail", + "detector_rail", + "sticky_piston", + "web", + "tallgrass", + "deadbush", + "piston", + "pistonarmcollision", + "wool", + "air", + "yellow_flower", + "red_flower", + "brown_mushroom", + "red_mushroom", + "gold_block", + "iron_block", + "double_stone_slab", + "stone_slab", + "brick_block", + "tnt", + "bookshelf", + "mossy_cobblestone", + "obsidian", + "torch", + "fire", + "mob_spawner", + "oak_stairs", + "chest", + "redstone_wire", + "diamond_ore", + "diamond_block", + "crafting_table", + "wheat", + "farmland", + "furnace", + "lit_furnace", + "standing_sign", + "wooden_door", + "ladder", + "rail", + "stone_stairs", + "wall_sign", + "lever", + "stone_pressure_plate", + "iron_door", + "oak_pressure_plate", + "redstone_ore", + "lit_redstone_ore", + "unlit_redstone_torch", + "lit_redstone_torch", + "stone_button", + "snow_layer", + "ice", + "snow", + "cactus", + "clay", + "reeds", + "jukebox", + "fence", + "pumpkin", + "netherrack", + "soul_sand", + "glowstone", + "portal", + "lit_pumpkin", + "cake", + "unpowered_repeater", + "powered_repeater", + "invisiblebedrock", + "oak_trapdoor", + "monster_egg", + "stonebrick", + "brown_mushroom_block", + "red_mushroom_block", + "iron_bars", + "glass_pane", + "melon_block", + "pumpkin_stem", + "melon_stem", + "vine", + "fence_gate", + "brick_stairs", + "stone_brick_stairs", + "mycelium", + "waterlily", + "nether_brick", + "nether_brick_fence", + "nether_brick_stairs", + "nether_wart", + "enchanting_table", + "brewing_stand", + "cauldron", + "end_portal", + "end_portal_frame", + "end_stone", + "dragon_egg", + "redstone_lamp", + "lit_redstone_lamp", + "dropper", + "activator_rail", + "cocoa", + "sandstone_stairs", + "emerald_ore", + "ender_chest", + "tripwire_hook", + "tripwire", + "emerald_block", + "spruce_stairs", + "birch_stairs", + "jungle_stairs", + "command_block", + "beacon", + "cobblestone_wall", + "flower_pot", + "carrots", + "potatoes", + "oak_button", + "skull", + "anvil", + "trapped_chest", + "light_weighted_pressure_plate", + "heavy_weighted_pressure_plate", + "unpowered_comparator", + "powered_comparator", + "daylight_detector", + "redstone_block", + "quartz_ore", + "hopper", + "quartz_block", + "quartz_stairs", + "double_wooden_slab", + "wooden_slab", + "stained_hardened_clay", + "stained_glass_pane", + "leaves2", + "log2", + "acacia_stairs", + "dark_oak_stairs", + "slime", + "air", + "iron_trapdoor", + "prismarine", + "sealantern", + "hay_block", + "carpet", + "terracotta", + "coal_block", + "packed_ice", + "double_plant", + "standing_banner", + "wall_banner", + "daylight_detector_inverted", + "red_sandstone", + "red_sandstone_stairs", + "double_stone_slab2", + "stone_slab2", + "spruce_fence_gate", + "birch_fence_gate", + "jungle_fence_gate", + "dark_oak_fence_gate", + "acacia_fence_gate", + "repeating_command_block", + "chain_command_block", + "air", + "air", + "air", + "spruce_door", + "birch_door", + "jungle_door", + "acacia_door", + "dark_oak_door", + "grass_path", + "frame", + "chorus_flower", + "purpur_block", + "purpur_stairs", + "air", + "air", + "undyed_shulker_box", + "end_bricks", + "frosted_ice", + "end_rod", + "end_gateway", + "air", + "air", + "air", + "magma", + "nether_wart_block", + "red_nether_brick", + "bone_block", + "air", + "shulker_box", + "purple_glazed_terracotta", + "white_glazed_terracotta", + "orange_glazed_terracotta", + "magenta_glazed_terracotta", + "light_blue_glazed_terracotta", + "yellow_glazed_terracotta", + "lime_glazed_terracotta", + "pink_glazed_terracotta", + "gray_glazed_terracotta", + "silver_glazed_terracotta", + "cyan_glazed_terracotta", + "air", + "blue_glazed_terracotta", + "brown_glazed_terracotta", + "green_glazed_terracotta", + "red_glazed_terracotta", + "black_glazed_terracotta", + "concrete", + "concretepowder", + "air", + "air", + "chorus_plant", + "stained_glass", + "air", + "podzol", + "beetroot", + "stonecutter", + "glowingobsidian", + "netherreactor", + "info_update", + "info_update2", + "movingblock", + "observer", + "structure_block", + "air", + "air", + "reserved6"}; + + //String id -> global numeric id (deprecated but still works) + static public int resolve(String name) { + if (name.charAt(9) == ':') name = name.substring(10); + for (int i = 0; i < 256; i++) { + if (NAMES[i].equals(name)) return i; + } + return 0; + } + + static public String getName(int legacyNumericId) { + if (legacyNumericId < 0 || legacyNumericId > 255) legacyNumericId = 0; + return NAMES[legacyNumericId]; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java index 0dd9dfb4..00ccc9bf 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java @@ -15,6 +15,8 @@ import com.qozix.tileview.graphics.BitmapProvider; import com.qozix.tileview.tiles.Tile; +import java.lang.ref.WeakReference; + public class MCTileProvider implements BitmapProvider { @@ -27,13 +29,44 @@ public class MCTileProvider implements BitmapProvider { HALF_WORLDSIZE = 1 << 20; public static int worldSizeInBlocks = 2 * HALF_WORLDSIZE, - viewSizeW = worldSizeInBlocks * TILESIZE / Dimension.OVERWORLD.chunkW, - viewSizeL = worldSizeInBlocks * TILESIZE / Dimension.OVERWORLD.chunkL; + viewSizeW = worldSizeInBlocks * TILESIZE / Dimension.OVERWORLD.chunkW, + viewSizeL = worldSizeInBlocks * TILESIZE / Dimension.OVERWORLD.chunkL; + + public final WeakReference worldProvider; + + private WeakReference mChunkManager; + + public MCTileProvider(WorldActivityInterface worldProvider, ChunkManager chunkManager) { + this.worldProvider = new WeakReference<>(worldProvider); + mChunkManager = new WeakReference<>(chunkManager); + } + + public static Bitmap drawText(String text, Bitmap b, int textColor, int bgColor) { + // Get text dimensions + TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG); + textPaint.setStyle(Paint.Style.FILL); + textPaint.setColor(textColor); + textPaint.setTextSize(b.getHeight() / 16f); + StaticLayout mTextLayout = new StaticLayout(text, textPaint, b.getWidth() / 2, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + + // Create bitmap and canvas to draw to + Canvas c = new Canvas(b); + + if (bgColor != 0) { + // Draw background + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG); + paint.setStyle(Paint.Style.FILL); + paint.setColor(bgColor); + c.drawPaint(paint); + } - public final WorldActivityInterface worldProvider; + // Draw text + c.save(); + c.translate(0, 0); + mTextLayout.draw(c); + c.restore(); - public MCTileProvider(WorldActivityInterface worldProvider){ - this.worldProvider = worldProvider; + return b; } @Override @@ -43,14 +76,9 @@ public Bitmap getBitmap(Tile tile, Context context) { try { - - // column and row range from 0 to (worldsize/tilesize) * scale - - Dimension dimension = worldProvider.getDimension(); - // 1 chunk per tile on scale 1.0 - int pixelsPerBlockW_unscaled = TILESIZE / dimension.chunkW; - int pixelsPerBlockL_unscaled = TILESIZE / dimension.chunkL; + int pixelsPerBlockW_unscaled = TILESIZE / 16; + int pixelsPerBlockL_unscaled = TILESIZE / 16; float scale = tile.getDetailLevel().getScale(); @@ -64,27 +92,19 @@ public Bitmap getBitmap(Tile tile, Context context) { // for translating to origin // HALF_WORLDSIZE and TILESIZE must be a power of two - int tilesInHalfWorldW = (HALF_WORLDSIZE * pixelsPerBlockW) / TILESIZE; - int tilesInHalfWorldL = (HALF_WORLDSIZE * pixelsPerBlockL) / TILESIZE; - + int tilesInHalfWorldW = (HALF_WORLDSIZE * pixelsPerBlockW) / TILESIZE; + int tilesInHalfWorldL = (HALF_WORLDSIZE * pixelsPerBlockL) / TILESIZE; // translate tile coord to origin, multiply origin-relative-tile-coordinate with the chunks per tile - int minChunkX = ( tile.getColumn() - tilesInHalfWorldW) * invScale; - int minChunkZ = ( tile.getRow() - tilesInHalfWorldL) * invScale; + int minChunkX = (tile.getColumn() - tilesInHalfWorldW) * invScale; + int minChunkZ = (tile.getRow() - tilesInHalfWorldL) * invScale; int maxChunkX = minChunkX + invScale; int maxChunkZ = minChunkZ + invScale; - - - //scale pixels to dimension scale (Nether 1 : 8 Overworld) - pixelsPerBlockW *= dimension.dimensionScale; - pixelsPerBlockL *= dimension.dimensionScale; - - - ChunkManager cm = new ChunkManager(worldProvider.getWorld().getWorldData(), dimension); + Dimension dimension = worldProvider.get().getDimension(); MapType mapType = (MapType) tile.getDetailLevel().getLevelType(); - if(mapType == null) return null; + if (mapType == null) return null; int x, z, pX, pY; @@ -92,93 +112,53 @@ public Bitmap getBitmap(Tile tile, Context context) { //check if the tile is not aligned with its inner chunks //hacky: it must be a single chunk that is to big for the tile, render just the visible part, easy. - int alignment = invScale % dimension.dimensionScale; - if(alignment > 0){ - - int chunkX = minChunkX / dimension.dimensionScale; - if(minChunkX % dimension.dimensionScale < 0) chunkX -= 1; - int chunkZ = minChunkZ / dimension.dimensionScale; - if(minChunkZ % dimension.dimensionScale < 0) chunkZ -= 1; - - int stepX = dimension.chunkW / dimension.dimensionScale; - int stepZ = dimension.chunkL / dimension.dimensionScale; - int minX = (minChunkX % dimension.dimensionScale) * stepX; - if(minX < 0) minX += dimension.chunkW; - int minZ = (minChunkZ % dimension.dimensionScale) * stepZ; - if(minZ < 0) minZ += dimension.chunkL; - int maxX = (maxChunkX % dimension.dimensionScale) * stepX; - if(maxX <= 0) maxX += dimension.chunkW; - int maxZ = (maxChunkZ % dimension.dimensionScale) * stepZ; - if(maxZ <= 0) maxZ += dimension.chunkL; - - - tileTxt = chunkX+";"+chunkZ+" ("+((chunkX*dimension.chunkW)+minX)+"; "+((chunkZ*dimension.chunkL)+minZ)+")"; - - - mapType.renderer.renderToBitmap(cm, bm, dimension, - chunkX, chunkZ, - minX, minZ , - maxX, maxZ, - 0, 0, - pixelsPerBlockW, pixelsPerBlockL); - - } else { - - minChunkX /= dimension.dimensionScale; - minChunkZ /= dimension.dimensionScale; - maxChunkX /= dimension.dimensionScale; - maxChunkZ /= dimension.dimensionScale; - - tileTxt = "("+(minChunkX*dimension.chunkW)+"; "+(minChunkZ*dimension.chunkL)+")"; - - - int pixelsPerChunkW = pixelsPerBlockW * dimension.chunkW; - int pixelsPerChunkL = pixelsPerBlockL * dimension.chunkL; - - for(z = minChunkZ, pY = 0; z < maxChunkZ; z++, pY += pixelsPerChunkL){ - - for(x = minChunkX, pX = 0; x < maxChunkX; x++, pX += pixelsPerChunkW){ - - try { - mapType.renderer.renderToBitmap(cm, bm, dimension, - x, z, - 0, 0, - dimension.chunkW, dimension.chunkL, - pX, pY, - pixelsPerBlockW, pixelsPerBlockL); - } catch (Exception e){ - MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, - x, z, - 0, 0, - dimension.chunkW, dimension.chunkL, - pX, pY, - pixelsPerBlockW, pixelsPerBlockL); - e.printStackTrace(); - } + + tileTxt = "(" + (minChunkX * 16) + "; " + (minChunkZ * 16) + ")"; + + + int pixelsPerChunkW = pixelsPerBlockW * 16; + int pixelsPerChunkL = pixelsPerBlockL * 16; + + for (z = minChunkZ, pY = 0; z < maxChunkZ; z++, pY += pixelsPerChunkL) { + + for (x = minChunkX, pX = 0; x < maxChunkX; x++, pX += pixelsPerChunkW) { + + try { + mapType.renderer.renderToBitmap(mChunkManager.get(), bm, dimension, + x, z, + pX, pY, + pixelsPerBlockW, pixelsPerBlockL); + } catch (Exception e) { + MapType.ERROR.renderer.renderToBitmap(mChunkManager.get(), bm, dimension, + x, z, + pX, pY, + pixelsPerBlockW, pixelsPerBlockL); + e.printStackTrace(); } + } } //load all those markers with an async task, this task publishes its progress, // the UI thread picks it up and renders the markers - new MarkerAsyncTask(worldProvider, cm, minChunkX, minChunkZ, maxChunkX, maxChunkZ).execute(); + new MarkerAsyncTask(worldProvider.get(), mChunkManager.get(), minChunkX, minChunkZ, maxChunkX, maxChunkZ, dimension).execute(); //draw the grid - if(worldProvider.getShowGrid()){ + if (worldProvider.get().getShowGrid()) { //draw tile-edges white - for(int i = 0; i < TILESIZE; i++){ + for (int i = 0; i < TILESIZE; i++) { //horizontal edges bm.setPixel(i, 0, Color.WHITE); - bm.setPixel(i, TILESIZE-1, Color.WHITE); + bm.setPixel(i, TILESIZE - 1, Color.WHITE); //vertical edges bm.setPixel(0, i, Color.WHITE); - bm.setPixel(TILESIZE-1, i, Color.WHITE); + bm.setPixel(TILESIZE - 1, i, Color.WHITE); } @@ -186,7 +166,7 @@ public Bitmap getBitmap(Tile tile, Context context) { drawText(tileTxt, bm, Color.WHITE, 0); } - } catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); } @@ -195,33 +175,4 @@ public Bitmap getBitmap(Tile tile, Context context) { } - public static Bitmap drawText(String text, Bitmap b, int textColor, int bgColor) { - // Get text dimensions - TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG); - textPaint.setStyle(Paint.Style.FILL); - textPaint.setColor(textColor); - textPaint.setTextSize(b.getHeight() / 16f); - StaticLayout mTextLayout = new StaticLayout(text, textPaint, b.getWidth() / 2, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - - // Create bitmap and canvas to draw to - Canvas c = new Canvas(b); - - if(bgColor != 0){ - // Draw background - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG); - paint.setStyle(Paint.Style.FILL); - paint.setColor(bgColor); - c.drawPaint(paint); - } - - // Draw text - c.save(); - c.translate(0, 0); - mTextLayout.draw(c); - c.restore(); - - return b; - } - - } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java index 8b541597..421a17d7 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java @@ -1,6 +1,5 @@ package com.mithrilmania.blocktopograph.map; -import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; @@ -11,10 +10,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; - -import com.mithrilmania.blocktopograph.Log; - -import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.util.DisplayMetrics; import android.view.LayoutInflater; @@ -33,12 +28,16 @@ import com.github.clans.fab.FloatingActionButton; import com.github.clans.fab.FloatingActionMenu; +import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.R; import com.mithrilmania.blocktopograph.World; import com.mithrilmania.blocktopograph.WorldActivity; import com.mithrilmania.blocktopograph.WorldActivityInterface; import com.mithrilmania.blocktopograph.WorldData; -import com.mithrilmania.blocktopograph.chunk.*; +import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.ChunkManager; +import com.mithrilmania.blocktopograph.chunk.ChunkTag; +import com.mithrilmania.blocktopograph.chunk.NBTChunkData; import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.marker.AbstractMarker; import com.mithrilmania.blocktopograph.map.marker.CustomNamedBitmapProvider; @@ -61,6 +60,7 @@ import com.qozix.tileview.markers.MarkerLayout; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -77,11 +77,26 @@ public class MapFragment extends Fragment { - private WorldActivityInterface worldProvider; + private final static int MARKER_INTERVAL_CHECK = 50; + //static, remember choice while app is open. + static Map markerFilter = new HashMap<>(); - private MCTileProvider minecraftTileProvider; + static { + //entities are enabled by default + for (Entity v : Entity.values()) { + //skip things without a bitmap (dropped items etc.) + //skip entities with placeholder ids (900+) + if (v.sheetPos < 0 || v.id >= 900) continue; + markerFilter.put(v.getNamedBitmapProvider(), + new BitmapChoiceListAdapter.NamedBitmapChoice(v, true)); + } + //tile-entities are disabled by default + for (TileEntity v : TileEntity.values()) { + markerFilter.put(v.getNamedBitmapProvider(), + new BitmapChoiceListAdapter.NamedBitmapChoice(v, false)); + } - private TileView tileView; + } //procedural markers can be iterated while other threads add things to it; // iterating performance is not really affected because of the small size; @@ -91,7 +106,12 @@ public class MapFragment extends Fragment { public AbstractMarker spawnMarker; public AbstractMarker localPlayerMarker; - + private WeakReference worldProvider; + private MCTileProvider minecraftTileProvider; + private TileView tileView; + private ChunkManager mChunkManager; + private int proceduralMarkersInterval = 0; + private volatile AsyncTask shrinkProceduralMarkersTask; @Override public void onPause() { @@ -104,8 +124,8 @@ public void onPause() { @Override public void onStart() { super.onStart(); - - getActivity().setTitle(this.worldProvider.getWorld().getWorldDisplayName()); + WorldActivityInterface worldProvider = this.worldProvider.get(); + getActivity().setTitle(worldProvider.getWorld().getWorldDisplayName()); worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_OPEN); } @@ -117,7 +137,11 @@ public void onResume() { //resume drawing the map this.tileView.resume(); - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_RESUME); + worldProvider.get().logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_RESUME); + } + + public void closeChunks() { + mChunkManager.disposeAll(); } @Override @@ -125,10 +149,9 @@ public void onDestroyView() { super.onDestroyView(); } - public DimensionVector3 getMultiPlayerPos(String dbKey) throws Exception { try { - WorldData wData = worldProvider.getWorld().getWorldData(); + WorldData wData = worldProvider.get().getWorld().getWorldData(); wData.openDB(); byte[] data = wData.db.get(dbKey.getBytes(NBTConstants.CHARSET)); if (data == null) throw new Exception("no data!"); @@ -163,6 +186,7 @@ public DimensionVector3 getMultiPlayerPos(String dbKey) throws Exception public DimensionVector3 getPlayerPos() throws Exception { try { + WorldActivityInterface worldProvider = this.worldProvider.get(); WorldData wData = worldProvider.getWorld().getWorldData(); wData.openDB(); byte[] data = wData.db.get(World.SpecialDBEntryType.LOCAL_PLAYER.keyBytes); @@ -202,14 +226,12 @@ public DimensionVector3 getPlayerPos() throws Exception { public DimensionVector3 getSpawnPos() throws Exception { try { - CompoundTag level = this.worldProvider.getWorld().level; + CompoundTag level = this.worldProvider.get().getWorld().level; int spawnX = ((IntTag) level.getChildTagByKey("SpawnX")).getValue(); int spawnY = ((IntTag) level.getChildTagByKey("SpawnY")).getValue(); int spawnZ = ((IntTag) level.getChildTagByKey("SpawnZ")).getValue(); if (spawnY == 256) { - TerrainChunkData data = new ChunkManager( - this.worldProvider.getWorld().getWorldData(), Dimension.OVERWORLD) - .getChunk(spawnX >> 4, spawnZ >> 4) + TerrainChunkData data = mChunkManager.getChunk(spawnX >> 4, spawnZ >> 4, Dimension.OVERWORLD) .getTerrain((byte) 0); if (data.load2DData()) spawnY = data.getHeightMapValue(spawnX % 16, spawnZ % 16) + 1; @@ -222,58 +244,6 @@ public DimensionVector3 getSpawnPos() throws Exception { } } - public static class MarkerListAdapter extends ArrayAdapter { - - - MarkerListAdapter(Context context, List objects) { - super(context, 0, objects); - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - - RelativeLayout v = (RelativeLayout) convertView; - - if (v == null) { - LayoutInflater vi; - vi = LayoutInflater.from(getContext()); - v = (RelativeLayout) vi.inflate(R.layout.marker_list_entry, parent, false); - } - - AbstractMarker m = getItem(position); - - if (m != null) { - TextView name = (TextView) v.findViewById(R.id.marker_name); - TextView xz = (TextView) v.findViewById(R.id.marker_xz); - ImageView icon = (ImageView) v.findViewById(R.id.marker_icon); - - name.setText(m.getNamedBitmapProvider().getBitmapDisplayName()); - String xzStr = String.format(Locale.ENGLISH, "x: %d, y: %d, z: %d", m.x, m.y, m.z); - xz.setText(xzStr); - - m.loadIcon(icon); - - } - - return v; - } - - } - - public enum MarkerTapOption { - - TELEPORT_LOCAL_PLAYER(R.string.teleport_local_player), - REMOVE_MARKER(R.string.remove_custom_marker); - - public final int stringId; - - MarkerTapOption(int id) { - this.stringId = id; - } - - } - public String[] getMarkerTapOptions() { MarkerTapOption[] values = MarkerTapOption.values(); int len = values.length; @@ -314,6 +284,7 @@ public void onClick(View view) { Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); + WorldActivityInterface worldProvider = MapFragment.this.worldProvider.get(); if (playerPos.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); } @@ -347,6 +318,7 @@ public void onClick(View view) { Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); + WorldActivityInterface worldProvider = MapFragment.this.worldProvider.get(); if (spawnPos.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(spawnPos.dimension.defaultMapType, spawnPos.dimension); } @@ -372,7 +344,7 @@ public void onClick(View view) { } try { //noinspection ConstantConditions - worldProvider = (WorldActivityInterface) activity; + worldProvider = new WeakReference<>((WorldActivityInterface) activity); } catch (ClassCastException e) { new Exception("MapFragment: activity is not an worldprovider, cannot set worldProvider!", e).printStackTrace(); return null; @@ -384,6 +356,7 @@ public void onClick(View view) { fabMenu.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() { @Override public void onMenuToggle(boolean opened) { + WorldActivityInterface worldProvider = MapFragment.this.worldProvider.get(); if (opened) worldProvider.showActionBar(); else worldProvider.hideActionBar(); } @@ -396,7 +369,7 @@ public void onMenuToggle(boolean opened) { @Override public void onClick(View view) { try { - Collection markers = worldProvider.getWorld().getMarkerManager().getMarkers(); + Collection markers = worldProvider.get().getWorld().getMarkerManager().getMarkers(); if (markers.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); @@ -435,6 +408,7 @@ public void onClick(DialogInterface dialog, int which) { Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); + WorldActivityInterface worldProvider = MapFragment.this.worldProvider.get(); if (m.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(m.dimension.defaultMapType, m.dimension); } @@ -476,92 +450,7 @@ public void onClick(final View view) { .setAction("Action", null).show(); //this can take some time... ...do it in the background - (new AsyncTask() { - - @Override - protected String[] doInBackground(Void... arg0) { - try { - return worldProvider.getWorld().getWorldData().getPlayers(); - } catch (Exception e) { - return null; - } - } - - protected void onPostExecute(final String[] players) { - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - - if (players == null) { - Snackbar.make(view, R.string.failed_to_retrieve_player_data, Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - return; - } - - if (players.length == 0) { - Snackbar.make(view, R.string.no_multiplayer_data_found, Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - return; - } - - - //NBT tag type spinner - final Spinner spinner = new Spinner(activity); - ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(activity, - android.R.layout.simple_spinner_item, players); - - spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(spinnerArrayAdapter); - - - //wrap layout in alert - new AlertDialog.Builder(activity) - .setTitle(R.string.go_to_player) - .setView(spinner) - .setPositiveButton(R.string.go_loud, new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int whichButton) { - - //new tag type - int spinnerIndex = spinner.getSelectedItemPosition(); - String playerKey = players[spinnerIndex]; - - try { - DimensionVector3 playerPos = getMultiPlayerPos(playerKey); - - Snackbar.make(tileView, - getString(R.string.something_at_xyz_dim_float, - playerKey, - playerPos.x, - playerPos.y, - playerPos.z, - playerPos.dimension.name), - Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_MULTIPLAYER); - - if (playerPos.dimension != worldProvider.getDimension()) { - worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); - } - - frameTo((double) playerPos.x, (double) playerPos.z); - - } catch (Exception e) { - Snackbar.make(view, e.getMessage(), Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - } - - } - }) - //or alert is cancelled - .setNegativeButton(android.R.string.cancel, null) - .show(); - } - }); - } - - }).execute(); + new GetPlayerTask(MapFragment.this, view, activity).execute(); } }); @@ -605,7 +494,7 @@ public void onClick(DialogInterface dialog, int whichButton) { return; } - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_COORD); + worldProvider.get().logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_COORD); frameTo((double) inX, (double) inZ); } @@ -650,7 +539,7 @@ public void onClick(DialogInterface dialog, int whichButton) { @Override public void onLongPress(MotionEvent event) { - Dimension dimension = worldProvider.getDimension(); + Dimension dimension = worldProvider.get().getDimension(); // 1 chunk per tile on scale 1.0 int pixelsPerBlockW_unscaled = MCTileProvider.TILESIZE / dimension.chunkW; @@ -670,7 +559,7 @@ public void onLongPress(MotionEvent event) { this.tileView.setId(ViewID.generateViewId()); //set the map-type - tileView.getDetailLevelManager().setLevelType(worldProvider.getMapType()); + tileView.getDetailLevelManager().setLevelType(worldProvider.get().getMapType()); /* Add tile view to main layout @@ -683,7 +572,8 @@ public void onLongPress(MotionEvent event) { /* Create tile(=bitmap) provider */ - this.minecraftTileProvider = new MCTileProvider(worldProvider); + this.mChunkManager = new ChunkManager(worldProvider.get().getWorld().getWorldData()); + this.minecraftTileProvider = new MCTileProvider(worldProvider.get(), mChunkManager); @@ -736,6 +626,7 @@ Create tile(=bitmap) provider this.staticMarkers.add(localPlayerMarker); addMarker(localPlayerMarker); + WorldActivityInterface worldProvider = this.worldProvider.get(); if (localPlayerMarker.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(localPlayerMarker.dimension.defaultMapType, localPlayerMarker.dimension); } @@ -759,6 +650,7 @@ Create tile(=bitmap) provider if (!framedToPlayer) { + WorldActivityInterface worldProvider = this.worldProvider.get(); if (spawnMarker.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(spawnMarker.dimension.defaultMapType, spawnMarker.dimension); } @@ -792,7 +684,6 @@ public void onMarkerTap(View view, int tapX, int tapY) { .setTitle(String.format(getString(R.string.marker_info), marker.getNamedBitmapProvider().getBitmapDisplayName(), marker.getNamedBitmapProvider().getBitmapDataName(), marker.x, marker.y, marker.z, marker.dimension)) .setItems(getMarkerTapOptions(), new DialogInterface.OnClickListener() { @SuppressWarnings("RedundantCast") - @SuppressLint("SetTextI18n") public void onClick(DialogInterface dialog, int which) { final MarkerTapOption chosen = MarkerTapOption.values()[which]; @@ -800,7 +691,7 @@ public void onClick(DialogInterface dialog, int which) { switch (chosen) { case TELEPORT_LOCAL_PLAYER: { try { - final EditableNBT playerEditable = worldProvider.getEditablePlayer(); + final EditableNBT playerEditable = worldProvider.get().getEditablePlayer(); if (playerEditable == null) throw new Exception("Player is null"); @@ -862,7 +753,7 @@ public void onClick(DialogInterface dialog, int which) { case REMOVE_MARKER: { if (marker.isCustom) { MapFragment.this.removeMarker(marker); - MarkerManager mng = MapFragment.this.worldProvider.getWorld().getMarkerManager(); + MarkerManager mng = MapFragment.this.worldProvider.get().getWorld().getMarkerManager(); mng.removeMarker(marker, true); mng.save(); @@ -918,7 +809,7 @@ public AbstractMarker moveMarker(AbstractMarker marker, int x, int y, int z, Dim this.addMarker(newMarker); if (marker.isCustom) { - MarkerManager mng = this.worldProvider.getWorld().getMarkerManager(); + MarkerManager mng = this.worldProvider.get().getWorld().getMarkerManager(); mng.removeMarker(marker, true); mng.addMarker(newMarker, true); } @@ -926,11 +817,6 @@ public AbstractMarker moveMarker(AbstractMarker marker, int x, int y, int z, Dim return newMarker; } - private final static int MARKER_INTERVAL_CHECK = 50; - private int proceduralMarkersInterval = 0; - private volatile AsyncTask shrinkProceduralMarkersTask; - - /** * Calculates viewport of tileview, expressed in blocks. * @@ -940,7 +826,7 @@ public AbstractMarker moveMarker(AbstractMarker marker, int x, int y, int z, Dim */ public Object[] calculateViewPort(int marginX, int marginZ) { - Dimension dimension = this.worldProvider.getDimension(); + Dimension dimension = this.worldProvider.get().getDimension(); // 1 chunk per tile on scale 1.0 int pixelsPerBlockW_unscaled = MCTileProvider.TILESIZE / dimension.chunkW; @@ -963,40 +849,7 @@ public Object[] calculateViewPort(int marginX, int marginZ) { public AsyncTask retainViewPortMarkers(final Runnable callback) { DisplayMetrics displayMetrics = this.getActivity().getResources().getDisplayMetrics(); - return new AsyncTask() { - @Override - protected Void doInBackground(Object... params) { - long minX = (long) params[0], - maxX = (long) params[1], - minY = (long) params[2], - maxY = (long) params[3]; - Dimension reqDim = (Dimension) params[4]; - - for (AbstractMarker p : MapFragment.this.proceduralMarkers) { - - // do not remove static markers - if (MapFragment.this.staticMarkers.contains(p)) continue; - - if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY || p.dimension != reqDim) { - this.publishProgress(p); - } - } - return null; - } - - @Override - protected void onProgressUpdate(final AbstractMarker... values) { - for (AbstractMarker v : values) { - MapFragment.this.removeMarker(v); - } - } - - @Override - protected void onPostExecute(Void aVoid) { - callback.run(); - } - - }.execute(this.calculateViewPort( + return new RetainViewPortMarkersTask(this, callback).execute(this.calculateViewPort( displayMetrics.widthPixels / 2, displayMetrics.heightPixels / 2) ); @@ -1062,25 +915,6 @@ public void toggleMarkers() { tileView.getMarkerLayout().setVisibility(visibility == View.VISIBLE ? View.GONE : View.VISIBLE); } - - public enum LongClickOption { - - TELEPORT_LOCAL_PLAYER(R.string.teleport_local_player, null), - CREATE_MARKER(R.string.create_custom_marker, null), - //TODO TELEPORT_MULTI_PLAYER("Teleport other player", null), - ENTITY(R.string.open_chunk_entity_nbt, ChunkTag.ENTITY), - TILE_ENTITY(R.string.open_chunk_tile_entity_nbt, ChunkTag.BLOCK_ENTITY); - - public final int stringId; - public final ChunkTag dataType; - - LongClickOption(int id, ChunkTag dataType) { - this.stringId = id; - this.dataType = dataType; - } - - } - public String[] getLongClickOptions() { LongClickOption[] values = LongClickOption.values(); int len = values.length; @@ -1091,14 +925,13 @@ public String[] getLongClickOptions() { return options; } - public void onLongClick(final double worldX, final double worldZ) { final Activity activity = MapFragment.this.getActivity(); - final Dimension dim = this.worldProvider.getDimension(); + final Dimension dim = this.worldProvider.get().getDimension(); double chunkX = worldX / dim.chunkW; double chunkZ = worldZ / dim.chunkL; @@ -1117,7 +950,6 @@ public void onLongClick(final double worldX, final double worldZ) { new AlertDialog.Builder(activity) .setTitle(getString(R.string.postion_2D_floats_with_chunkpos, worldX, worldZ, chunkXint, chunkZint, dim.name)) .setItems(getLongClickOptions(), new DialogInterface.OnClickListener() { - @SuppressLint("SetTextI18n") public void onClick(DialogInterface dialog, int which) { final LongClickOption chosen = LongClickOption.values()[which]; @@ -1127,7 +959,7 @@ public void onClick(DialogInterface dialog, int which) { case TELEPORT_LOCAL_PLAYER: { try { - final EditableNBT playerEditable = MapFragment.this.worldProvider.getEditablePlayer(); + final EditableNBT playerEditable = MapFragment.this.worldProvider.get().getEditablePlayer(); if (playerEditable == null) throw new Exception("Player is null"); @@ -1222,15 +1054,15 @@ public void onClick(DialogInterface dialog, int which) { View createMarkerForm = LayoutInflater.from(activity).inflate(R.layout.create_marker_form, null); - final EditText markerNameInput = (EditText) createMarkerForm.findViewById(R.id.marker_displayname_input); + final EditText markerNameInput = createMarkerForm.findViewById(R.id.marker_displayname_input); markerNameInput.setText(R.string.default_custom_marker_name); - final EditText markerIconNameInput = (EditText) createMarkerForm.findViewById(R.id.marker_iconname_input); + final EditText markerIconNameInput = createMarkerForm.findViewById(R.id.marker_iconname_input); markerIconNameInput.setText("blue_marker"); - final EditText xInput = (EditText) createMarkerForm.findViewById(R.id.x_input); + final EditText xInput = createMarkerForm.findViewById(R.id.x_input); xInput.setText(String.valueOf((int) worldX)); - final EditText yInput = (EditText) createMarkerForm.findViewById(R.id.y_input); + final EditText yInput = createMarkerForm.findViewById(R.id.y_input); yInput.setText(String.valueOf(64)); - final EditText zInput = (EditText) createMarkerForm.findViewById(R.id.z_input); + final EditText zInput = createMarkerForm.findViewById(R.id.z_input); zInput.setText(String.valueOf((int) worldZ)); @@ -1239,7 +1071,7 @@ public void onClick(DialogInterface dialog, int which) { .setView(createMarkerForm) .setPositiveButton("Create marker", new DialogInterface.OnClickListener() { - public void failParseSnackbarReport(int msg) { + void failParseSnackbarReport(int msg) { Snackbar.make(container, msg, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); @@ -1286,7 +1118,7 @@ public void onClick(DialogInterface dialog, int which) { } AbstractMarker marker = MarkerManager.markerFromData(displayName, iconName, xM, yM, zM, dim); - MarkerManager mng = MapFragment.this.worldProvider.getWorld().getMarkerManager(); + MarkerManager mng = MapFragment.this.worldProvider.get().getWorld().getMarkerManager(); mng.addMarker(marker, true); MapFragment.this.addMarker(marker); @@ -1314,9 +1146,9 @@ public void onClick(DialogInterface dialog, int which) { case ENTITY: case TILE_ENTITY: { - final World world = MapFragment.this.worldProvider.getWorld(); - final ChunkManager chunkManager = new ChunkManager(world.getWorldData(), dim); - final Chunk chunk = chunkManager.getChunk(chunkXint, chunkZint); + final World world = MapFragment.this.worldProvider.get().getWorld(); + ; + final Chunk chunk = mChunkManager.getChunk(chunkXint, chunkZint, dim); if (!chunkDataNBT(chunk, chosen == LongClickOption.ENTITY)) { Snackbar.make(container, String.format(getString(R.string.failed_to_load_x), getString(chosen.stringId)), @@ -1354,11 +1186,10 @@ private boolean chunkDataNBT(Chunk chunk, boolean entity) { } //just open the editor if the data is there for us to edit it - this.worldProvider.openChunkNBTEditor(chunk.x, chunk.z, chunkData, this.tileView); + this.worldProvider.get().openChunkNBTEditor(chunk.x, chunk.z, chunkData, this.tileView); return true; } - @Override public void onDestroy() { super.onDestroy(); @@ -1370,85 +1201,6 @@ public void onDestroy() { } - public static class BitmapChoiceListAdapter extends ArrayAdapter { - - public static class NamedBitmapChoice { - - //Using a handle to facilitate bitmap swapping without breaking the filter. - final NamedBitmapProviderHandle namedBitmap; - boolean enabledTemp; - boolean enabled; - - public NamedBitmapChoice(NamedBitmapProviderHandle namedBitmap, boolean enabled) { - this.namedBitmap = namedBitmap; - this.enabled = this.enabledTemp = enabled; - } - } - - public BitmapChoiceListAdapter(Context context, List objects) { - super(context, 0, objects); - } - - @NonNull - @Override - public View getView(final int position, View v, @NonNull ViewGroup parent) { - - final NamedBitmapChoice m = getItem(position); - if (m == null) return new RelativeLayout(getContext()); - - if (v == null) v = LayoutInflater - .from(getContext()) - .inflate(R.layout.img_name_check_list_entry, parent, false); - - - ImageView img = (ImageView) v.findViewById(R.id.entry_img); - TextView text = (TextView) v.findViewById(R.id.entry_text); - final CheckBox check = (CheckBox) v.findViewById(R.id.entry_check); - - img.setImageBitmap(m.namedBitmap.getNamedBitmapProvider().getBitmap()); - text.setText(m.namedBitmap.getNamedBitmapProvider().getBitmapDisplayName()); - check.setTag(position); - check.setChecked(m.enabledTemp); - check.setOnCheckedChangeListener(changeListener); - - return v; - } - - private CompoundButton.OnCheckedChangeListener changeListener = new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - Object tag = compoundButton.getTag(); - if (tag == null) return; - int position = (int) tag; - final NamedBitmapChoice m = getItem(position); - if (m == null) return; - m.enabledTemp = b; - } - }; - - } - - - //static, remember choice while app is open. - static Map markerFilter = new HashMap<>(); - - static { - //entities are enabled by default - for (Entity v : Entity.values()) { - //skip things without a bitmap (dropped items etc.) - //skip entities with placeholder ids (900+) - if (v.sheetPos < 0 || v.id >= 900) continue; - markerFilter.put(v.getNamedBitmapProvider(), - new BitmapChoiceListAdapter.NamedBitmapChoice(v, true)); - } - //tile-entities are disabled by default - for (TileEntity v : TileEntity.values()) { - markerFilter.put(v.getNamedBitmapProvider(), - new BitmapChoiceListAdapter.NamedBitmapChoice(v, false)); - } - - } - public void openMarkerFilter() { final Activity activity = this.getActivity(); @@ -1508,6 +1260,7 @@ public void updateMarkerFilter() { } public void filterMarker(AbstractMarker marker) { + WorldActivityInterface worldProvider = this.worldProvider.get(); BitmapChoiceListAdapter.NamedBitmapChoice choice = markerFilter.get(marker.getNamedBitmapProvider()); if (choice != null) { marker.getView(this.getActivity()).setVisibility( @@ -1522,6 +1275,7 @@ public void filterMarker(AbstractMarker marker) { public void resetTileView() { if (this.tileView != null) { + WorldActivityInterface worldProvider = this.worldProvider.get(); worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_RESET); updateMarkerFilter(); @@ -1533,6 +1287,7 @@ public void resetTileView() { } public void invalidateTileView() { + WorldActivityInterface worldProvider = this.worldProvider.get(); MapType redo = worldProvider.getMapType(); DetailLevelManager manager = tileView.getDetailLevelManager(); //just swap mapType twice; it is not rendered, but it invalidates all tiles. @@ -1541,7 +1296,6 @@ public void invalidateTileView() { //all tiles will now reload as soon as the tileView is drawn (user scrolls -> redraw) } - /** * This is a convenience method to scrollToAndCenter after layout (which won't happen if called directly in onCreate * see https://github.com/moagrius/TileView/wiki/FAQ @@ -1550,7 +1304,7 @@ public void frameTo(final double worldX, final double worldZ) { this.tileView.post(new Runnable() { @Override public void run() { - Dimension dimension = worldProvider.getDimension(); + Dimension dimension = worldProvider.get().getDimension(); if (tileView != null) tileView.scrollToAndCenter( dimension.dimensionScale * worldX / (double) MCTileProvider.HALF_WORLDSIZE, dimension.dimensionScale * worldZ / (double) MCTileProvider.HALF_WORLDSIZE); @@ -1558,5 +1312,277 @@ public void run() { }); } + public enum MarkerTapOption { + + TELEPORT_LOCAL_PLAYER(R.string.teleport_local_player), + REMOVE_MARKER(R.string.remove_custom_marker); + + public final int stringId; + + MarkerTapOption(int id) { + this.stringId = id; + } + + } + + public enum LongClickOption { + + TELEPORT_LOCAL_PLAYER(R.string.teleport_local_player, null), + CREATE_MARKER(R.string.create_custom_marker, null), + //TODO TELEPORT_MULTI_PLAYER("Teleport other player", null), + ENTITY(R.string.open_chunk_entity_nbt, ChunkTag.ENTITY), + TILE_ENTITY(R.string.open_chunk_tile_entity_nbt, ChunkTag.BLOCK_ENTITY); + + public final int stringId; + public final ChunkTag dataType; + + LongClickOption(int id, ChunkTag dataType) { + this.stringId = id; + this.dataType = dataType; + } + + } + + public static class MarkerListAdapter extends ArrayAdapter { + + + MarkerListAdapter(Context context, List objects) { + super(context, 0, objects); + } + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + + RelativeLayout v = (RelativeLayout) convertView; + + if (v == null) { + LayoutInflater vi; + vi = LayoutInflater.from(getContext()); + v = (RelativeLayout) vi.inflate(R.layout.marker_list_entry, parent, false); + } + + AbstractMarker m = getItem(position); + + if (m != null) { + TextView name = v.findViewById(R.id.marker_name); + TextView xz = v.findViewById(R.id.marker_xz); + ImageView icon = v.findViewById(R.id.marker_icon); + + name.setText(m.getNamedBitmapProvider().getBitmapDisplayName()); + String xzStr = String.format(Locale.ENGLISH, "x: %d, y: %d, z: %d", m.x, m.y, m.z); + xz.setText(xzStr); + + m.loadIcon(icon); + + } + + return v; + } + + } + + public static class BitmapChoiceListAdapter extends ArrayAdapter { + + private CompoundButton.OnCheckedChangeListener changeListener = new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + Object tag = compoundButton.getTag(); + if (tag == null) return; + int position = (int) tag; + final NamedBitmapChoice m = getItem(position); + if (m == null) return; + m.enabledTemp = b; + } + }; + + BitmapChoiceListAdapter(Context context, List objects) { + super(context, 0, objects); + } + + @NonNull + @Override + public View getView(final int position, View v, @NonNull ViewGroup parent) { + + final NamedBitmapChoice m = getItem(position); + if (m == null) return new RelativeLayout(getContext()); + + if (v == null) v = LayoutInflater + .from(getContext()) + .inflate(R.layout.img_name_check_list_entry, parent, false); + + + ImageView img = (ImageView) v.findViewById(R.id.entry_img); + TextView text = (TextView) v.findViewById(R.id.entry_text); + final CheckBox check = (CheckBox) v.findViewById(R.id.entry_check); + + img.setImageBitmap(m.namedBitmap.getNamedBitmapProvider().getBitmap()); + text.setText(m.namedBitmap.getNamedBitmapProvider().getBitmapDisplayName()); + check.setTag(position); + check.setChecked(m.enabledTemp); + check.setOnCheckedChangeListener(changeListener); + + return v; + } + + static class NamedBitmapChoice { + + //Using a handle to facilitate bitmap swapping without breaking the filter. + final NamedBitmapProviderHandle namedBitmap; + boolean enabledTemp; + boolean enabled; + + NamedBitmapChoice(NamedBitmapProviderHandle namedBitmap, boolean enabled) { + this.namedBitmap = namedBitmap; + this.enabled = this.enabledTemp = enabled; + } + } + + } + + private static class GetPlayerTask extends AsyncTask { + + private final WeakReference owner; + private final WeakReference view; + private final WeakReference activity; + + private GetPlayerTask(MapFragment owner, View view, Activity activity) { + this.owner = new WeakReference<>(owner); + this.view = new WeakReference<>(view); + this.activity = new WeakReference<>(activity); + } + + @Override + protected String[] doInBackground(Void... arg0) { + try { + return owner.get().worldProvider.get().getWorld().getWorldData().getPlayers(); + } catch (Exception e) { + return null; + } + } + + protected void onPostExecute(final String[] players) { + owner.get().getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + + if (players == null) { + Snackbar.make(view.get(), R.string.failed_to_retrieve_player_data, Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + return; + } + + if (players.length == 0) { + Snackbar.make(view.get(), R.string.no_multiplayer_data_found, Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + return; + } + + + //NBT tag type spinner + final Spinner spinner = new Spinner(activity.get()); + ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>(activity.get(), + android.R.layout.simple_spinner_item, players); + + spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(spinnerArrayAdapter); + + + //wrap layout in alert + new AlertDialog.Builder(activity.get()) + .setTitle(R.string.go_to_player) + .setView(spinner) + .setPositiveButton(R.string.go_loud, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int whichButton) { + + //new tag type + int spinnerIndex = spinner.getSelectedItemPosition(); + String playerKey = players[spinnerIndex]; + + try { + DimensionVector3 playerPos = owner.get().getMultiPlayerPos(playerKey); + + Snackbar.make(owner.get().tileView, + owner.get().getString(R.string.something_at_xyz_dim_float, + playerKey, + playerPos.x, + playerPos.y, + playerPos.z, + playerPos.dimension.name), + Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + + WorldActivityInterface worldProvider = owner.get().worldProvider.get(); + worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_MULTIPLAYER); + + if (playerPos.dimension != worldProvider.getDimension()) { + worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); + } + + owner.get().frameTo((double) playerPos.x, (double) playerPos.z); + + } catch (Exception e) { + Snackbar.make(view.get(), e.getMessage(), Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + + } + }) + //or alert is cancelled + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + }); + } + + } + + private static class RetainViewPortMarkersTask extends AsyncTask { + + private final WeakReference owner; + private final Runnable callback; + + private RetainViewPortMarkersTask(MapFragment owner, Runnable callback) { + this.owner = new WeakReference<>(owner); + this.callback = callback; + } + + @Override + protected Void doInBackground(Object... params) { + long minX = (long) params[0], + maxX = (long) params[1], + minY = (long) params[2], + maxY = (long) params[3]; + Dimension reqDim = (Dimension) params[4]; + + CopyOnWriteArraySet proceduralMarkers = owner.get().proceduralMarkers; + + for (AbstractMarker p : proceduralMarkers) { + + // do not remove static markers + if (owner.get().staticMarkers.contains(p)) continue; + + if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY || p.dimension != reqDim) { + this.publishProgress(p); + } + } + return null; + } + + @Override + protected void onProgressUpdate(final AbstractMarker... values) { + for (AbstractMarker v : values) { + owner.get().removeMarker(v); + } + } + + @Override + protected void onPostExecute(Void aVoid) { + callback.run(); + } + + } + } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java index eed95f97..9029a058 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java @@ -1,6 +1,7 @@ package com.mithrilmania.blocktopograph.map; import android.os.AsyncTask; + import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.WorldActivityInterface; @@ -13,6 +14,7 @@ import com.mithrilmania.blocktopograph.nbt.tags.StringTag; import com.mithrilmania.blocktopograph.nbt.tags.Tag; +import java.lang.ref.WeakReference; import java.util.Collection; import java.util.List; @@ -21,28 +23,31 @@ */ public class MarkerAsyncTask extends AsyncTask { - private final WorldActivityInterface worldProvider; - private final ChunkManager chunkManager; + private final WeakReference worldProvider; + private final WeakReference chunkManager; private final int minChunkX, minChunkZ, maxChunkX, maxChunkZ; + private final Dimension dimension; - public MarkerAsyncTask(WorldActivityInterface worldProvider, ChunkManager chunkManager, int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ){ + public MarkerAsyncTask(WorldActivityInterface worldProvider, ChunkManager chunkManager, + int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ, Dimension dimension) { this.minChunkX = minChunkX; this.minChunkZ = minChunkZ; this.maxChunkX = maxChunkX; this.maxChunkZ = maxChunkZ; + this.dimension = dimension; - this.worldProvider = worldProvider; - this.chunkManager = chunkManager; + this.worldProvider = new WeakReference<>(worldProvider); + this.chunkManager = new WeakReference<>(chunkManager); } @Override protected Void doInBackground(Void... v) { int cX, cZ; - for(cZ = minChunkZ; cZ < maxChunkZ; cZ++){ - for(cX = minChunkX; cX < maxChunkX; cX++){ + for (cZ = minChunkZ; cZ < maxChunkZ; cZ++) { + for (cX = minChunkX; cX < maxChunkX; cX++) { loadEntityMarkers(cX, cZ); loadTileEntityMarkers(cX, cZ); loadCustomMarkers(cX, cZ); @@ -52,9 +57,9 @@ protected Void doInBackground(Void... v) { return null; } - private void loadEntityMarkers(int chunkX, int chunkZ){ + private void loadEntityMarkers(int chunkX, int chunkZ) { try { - Chunk chunk = chunkManager.getChunk(chunkX, chunkZ); + Chunk chunk = chunkManager.get().getChunk(chunkX, chunkZ, dimension); NBTChunkData entityData = chunk.getEntity(); @@ -62,15 +67,15 @@ private void loadEntityMarkers(int chunkX, int chunkZ){ entityData.load(); - if(entityData.tags == null) return; + if (entityData.tags == null) return; for (Tag tag : entityData.tags) { if (tag instanceof CompoundTag) { CompoundTag compoundTag = (CompoundTag) tag; //int id = ((IntTag) compoundTag.getChildTagByKey("id")).getValue(); String tempName = compoundTag.getChildTagByKey("identifier").getValue().toString(); - tempName = tempName.replace("minecraft:",""); - tempName = tempName.substring(0, 1).toUpperCase() + tempName.substring(1); + tempName = tempName.replace("minecraft:", ""); + tempName = tempName.substring(0, 1).toUpperCase() + tempName.substring(1); int id = Entity.getEntity(tempName).id; Entity e = Entity.getEntity(id & 0xff); if (e != null && e.bitmap != null) { @@ -79,19 +84,19 @@ private void loadEntityMarkers(int chunkX, int chunkZ){ float yf = ((FloatTag) pos.get(1)).getValue(); float zf = ((FloatTag) pos.get(2)).getValue(); - this.publishProgress(new AbstractMarker(Math.round(xf), Math.round(yf), Math.round(zf), chunkManager.dimension, e, false)); + this.publishProgress(new AbstractMarker(Math.round(xf), Math.round(yf), Math.round(zf), dimension, e, false)); } } } - } catch (Exception e){ + } catch (Exception e) { Log.w(e.getMessage()); } } - private void loadTileEntityMarkers(int chunkX, int chunkZ){ + private void loadTileEntityMarkers(int chunkX, int chunkZ) { try { - Chunk chunk = chunkManager.getChunk(chunkX, chunkZ); + Chunk chunk = chunkManager.get().getChunk(chunkX, chunkZ, dimension); NBTChunkData tileEntityData = chunk.getBlockEntity(); @@ -99,7 +104,7 @@ private void loadTileEntityMarkers(int chunkX, int chunkZ){ tileEntityData.load(); - if(tileEntityData.tags == null) return; + if (tileEntityData.tags == null) return; for (Tag tag : tileEntityData.tags) { if (tag instanceof CompoundTag) { @@ -111,18 +116,18 @@ private void loadTileEntityMarkers(int chunkX, int chunkZ){ int eY = ((IntTag) compoundTag.getChildTagByKey("y")).getValue(); int eZ = ((IntTag) compoundTag.getChildTagByKey("z")).getValue(); - this.publishProgress(new AbstractMarker(Math.round(eX), Math.round(eY), Math.round(eZ), chunkManager.dimension, te, false)); + this.publishProgress(new AbstractMarker(Math.round(eX), Math.round(eY), Math.round(eZ), dimension, te, false)); } } } - } catch (Exception e){ + } catch (Exception e) { Log.w(e.getMessage()); } } - private void loadCustomMarkers(int chunkX, int chunkZ){ - Collection chunk = worldProvider.getWorld().getMarkerManager() + private void loadCustomMarkers(int chunkX, int chunkZ) { + Collection chunk = worldProvider.get().getWorld().getMarkerManager() .getMarkersOfChunk(chunkX, chunkZ); AbstractMarker[] markers = new AbstractMarker[chunk.size()]; this.publishProgress(chunk.toArray(markers)); @@ -130,8 +135,8 @@ private void loadCustomMarkers(int chunkX, int chunkZ){ @Override protected void onProgressUpdate(AbstractMarker... values) { - for(AbstractMarker marker : values){ - worldProvider.addMarker(marker); + for (AbstractMarker marker : values) { + worldProvider.get().addMarker(marker); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java index 2df8b669..9383241c 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java @@ -26,7 +26,6 @@ /** * TODO docs - * */ public class MarkerManager { @@ -51,6 +50,7 @@ public class MarkerManager { // // example: // 1 "Test \" marker" "default \" \"_ma 1 rker 1" 1234 64 4321 overworld ; + // Why bother inventing a format? private static final Pattern formatRegex = Pattern.compile("(\\d+)\\s+\"(?:(?:(.+?[^\\\\]))|)\"\\s+\"(?:(?:(.+?[^\\\\]))|)\"\\s+(\\d+?)\\s+(\\d+?)\\s+(\\d+?)\\s+(.+?)\\s*;"); @@ -62,12 +62,12 @@ public class MarkerManager { private boolean dirty; - public MarkerManager(File markerFile){ + public MarkerManager(File markerFile) { this.markerFile = markerFile; executorService = Executors.newSingleThreadExecutor(); } - public void load(){ + public void load() { executorService.submit(new Runnable() { @Override public void run() { @@ -76,31 +76,35 @@ public void run() { }); } - public void removeMarker(AbstractMarker marker, boolean dirty){ + private static long xzToKey(int x, int z) { + return (((long) x) << 32) | z; + } + + public void removeMarker(AbstractMarker marker, boolean dirty) { - long chunkKey = ChunkManager.xzToKey(marker.x >> 4, marker.z >> 4); + long chunkKey = xzToKey(marker.x >> 4, marker.z >> 4); Set chunk = chunks.get(chunkKey); - if(chunk != null) chunk.remove(marker); + if (chunk != null) chunk.remove(marker); //only set it to dirty if the marker is removed. this.dirty |= markers.remove(marker) && dirty; } - public void addMarker(AbstractMarker marker, boolean dirty){ + public void addMarker(AbstractMarker marker, boolean dirty) { markers.add(marker); - long chunkKey = ChunkManager.xzToKey(marker.x >> 4, marker.z >> 4); + long chunkKey = xzToKey(marker.x >> 4, marker.z >> 4); Set chunk = chunks.get(chunkKey); - if(chunk == null) chunks.put(chunkKey, chunk = new HashSet<>()); + if (chunk == null) chunks.put(chunkKey, chunk = new HashSet<>()); chunk.add(marker); this.dirty |= dirty; } - private synchronized Set loadFromFile(){ + private synchronized Set loadFromFile() { markers = new HashSet<>(); - if(this.markerFile.exists()) { + if (this.markerFile.exists()) { try { BufferedReader br = new BufferedReader(new FileReader(this.markerFile)); String line; @@ -148,8 +152,8 @@ private synchronized Set loadFromFile(){ /** * Saves the current markers, if and only if the markers were changed. */ - public void save(){ - if(dirty) executorService.submit(new Runnable() { + public void save() { + if (dirty) executorService.submit(new Runnable() { @Override public void run() { MarkerManager.this.saveToFile(); @@ -158,15 +162,15 @@ public void run() { }); } - private synchronized void saveToFile(){ + private synchronized void saveToFile() { try { - if(markerFile.createNewFile()) Log.d("Created "+this.markerFile.getAbsolutePath()); + if (markerFile.createNewFile()) Log.d("Created " + this.markerFile.getAbsolutePath()); //append to file PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(this.markerFile, false))); - for(AbstractMarker marker : markers){ + for (AbstractMarker marker : markers) { out.format("1 \"%s\", \"%s\", %d %d %d %s ;\n", marker.getNamedBitmapProvider().getBitmapDisplayName().replace("\"", "\\\""), marker.getNamedBitmapProvider().getBitmapDataName().replace("\"", "\\\""), @@ -180,27 +184,27 @@ private synchronized void saveToFile(){ } } - public Collection getMarkers(){ + public Collection getMarkers() { return Collections.unmodifiableSet(markers); } - public Collection getMarkersOfChunk(int chunkX, int chunkZ){ - long key = ChunkManager.xzToKey(chunkX, chunkZ); + public Collection getMarkersOfChunk(int chunkX, int chunkZ) { + long key = xzToKey(chunkX, chunkZ); Set chunk = chunks.get(key); - if(chunk == null) chunks.put(key, chunk = new HashSet<>()); + if (chunk == null) chunks.put(key, chunk = new HashSet<>()); return chunk; } - public static AbstractMarker markerFromData(String displayName, String iconName, int x, int y, int z, Dimension dimension){ + public static AbstractMarker markerFromData(String displayName, String iconName, int x, int y, int z, Dimension dimension) { NamedBitmapProvider nbp = Block.getByDataName(iconName); - if(nbp == null || nbp.getBitmap() == null) nbp = Entity.getEntity(iconName); - if(nbp == null || nbp.getBitmap() == null) nbp = TileEntity.getTileEntity(iconName); - if(nbp == null || nbp.getBitmap() == null) nbp = CustomIcon.getCustomIcon(iconName); - if(nbp == null || nbp.getBitmap() == null) nbp = CustomIcon.DEFAULT_MARKER; + if (nbp == null || nbp.getBitmap() == null) nbp = Entity.getEntity(iconName); + if (nbp == null || nbp.getBitmap() == null) nbp = TileEntity.getTileEntity(iconName); + if (nbp == null || nbp.getBitmap() == null) nbp = CustomIcon.getCustomIcon(iconName); + if (nbp == null || nbp.getBitmap() == null) nbp = CustomIcon.DEFAULT_MARKER; return new AbstractMarker(x, y, z, dimension, - new CustomNamedBitmapProvider(nbp, displayName), true); + new CustomNamedBitmapProvider(nbp, displayName), true); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java index 1c93d71e..f4d5ea87 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java @@ -10,46 +10,44 @@ import com.mithrilmania.blocktopograph.map.Dimension; - public class BiomeRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); //the bottom sub-chunk is sufficient to get biome data. TerrainChunkData data = chunk.getTerrain((byte) 0); - if(data == null || !data.load2DData()) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (data == null || !data.load2DData()) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); int x, z, biomeID, color, i, j, tX, tY; Biome biome; - for (z = bZ, tY = pY ; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { biomeID = data.getBiome(x, z) & 0xff; @@ -57,8 +55,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in color = biome == null ? 0xff000000 : (biome.color.red << 16) | (biome.color.green << 8) | (biome.color.blue) | 0xff000000; - for(i = 0; i < pL; i++){ - for(j = 0; j < pW; j++){ + for (i = 0; i < pL; i++) { + for (j = 0; j < pW; j++) { bm.setPixel(tX + j, tY + i, color); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java index bc12c772..c87e9432 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java @@ -9,67 +9,64 @@ import com.mithrilmania.blocktopograph.map.Dimension; - public class BlockLightRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); int x, y, z, subChunk, color, i, j, tX, tY; //render width in blocks - int rW = eX - bX; - int[] light = new int[rW * (eZ - bZ)]; + int rW = 16 - 0; + int[] light = new int[rW * (16 - 0)]; - for(subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { + for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { TerrainChunkData data = chunk.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) break; - for (z = bZ; z < eZ; z++) { - for (x = bX; x < eX; x++) { + for (z = 0; z < 16; z++) { + for (x = 0; x < 16; x++) { for (y = 0; y < cVersion.subChunkHeight; y++) { - light[((z - bZ) * rW) + (x - bX)] += data.getBlockLightValue(x, y, z) & 0xff; + light[((z - 0) * rW) + (x - 0)] += data.getBlockLightValue(x, y, z) & 0xff; } } } } int l; - for (z = bZ, tY = pY; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { - l = light[((z - bZ) * rW) + (x - bX)]; + l = light[((z - 0) * rW) + (x - 0)]; l = l < 0 ? 0 : ((l > 0xff) ? 0xff : l); color = (l << 16) | (l << 8) | (l) | 0xff000000; - for(i = 0; i < pL; i++){ - for(j = 0; j < pW; j++){ + for (i = 0; i < pL; i++) { + for (j = 0; j < pW; j++) { bm.setPixel(tX + j, tY + i, color); } } @@ -78,7 +75,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } - if(subChunk == 0) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (subChunk == 0) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); return bm; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java index 7aa2beae..d176822e 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java @@ -1,7 +1,6 @@ package com.mithrilmania.blocktopograph.map.renderer; - import android.graphics.Bitmap; import com.mithrilmania.blocktopograph.chunk.Chunk; @@ -12,35 +11,32 @@ import com.mithrilmania.blocktopograph.map.Dimension; - public class CaveRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); boolean solid, intoSurface; int id, meta, cavyness, layers, offset; @@ -49,12 +45,13 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData floorData = chunk.getTerrain((byte) 0); - if(floorData == null || !floorData.load2DData()) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (floorData == null || !floorData.load2DData()) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); TerrainChunkData data; - for (z = bZ, tY = pY; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { solid = false; @@ -80,10 +77,11 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in r = g = b = 0; - subChunkLoop: for(; subChunk >= 0; subChunk--) { + subChunkLoop: + for (; subChunk >= 0; subChunk--) { data = chunk.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()){ + if (data == null || !data.loadTerrain()) { //start at the top of the next chunk! (current offset might differ) offset = cVersion.subChunkHeight - 1; continue; @@ -103,11 +101,11 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in switch (id) { case 0: //count the number of times it goes from solid to air - if(solid) layers++; + if (solid) layers++; //count the air blocks underground, // but avoid trees by skipping the first layer - if(intoSurface) cavyness++; + if (intoSurface) cavyness++; break; case 66://rail if (b < 150) { @@ -151,7 +149,7 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - if (g == 0 && layers > 0){ + if (g == 0 && layers > 0) { g = (r + 2) * cavyness; r *= 32 * layers; b = 16 * cavyness * (layers - 1); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java index c840cd5d..188e9d53 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java @@ -1,53 +1,53 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; + import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; import com.mithrilmania.blocktopograph.map.Dimension; - public class ChessPatternRenderer implements MapRenderer { public final int darkShade, lightShade;// int DARK_SHADE = 0xFF2B2B2B, LIGHT_SHADE = 0xFF585858; - ChessPatternRenderer(int darkShade, int lightShade){ + ChessPatternRenderer(int darkShade, int lightShade) { this.darkShade = darkShade; this.lightShade = lightShade; } /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - int x, z, i, j, tX, tY; + int x, z, tX, tY; int color; - for (z = bZ, tY = pY ; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); + + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { color = ((x + z) & 1) == 1 ? darkShade : lightShade; - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); + //This would get hardware acceleration. } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java index 7c505051..30f9604e 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java @@ -7,39 +7,34 @@ import com.mithrilmania.blocktopograph.map.Dimension; - public class DebugRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { int x, z, i, j, tX, tY; int offsetX = chunkX * dimension.chunkW; int offsetZ = chunkZ * dimension.chunkL; - for (z = bZ, tY = pY ; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { - for(i = 0; i < pL; i++){ - for(j = 0; j < pW; j++){ + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { + for (i = 0; i < pL; i++) { + for (j = 0; j < pW; j++) { bm.setPixel(tX + j, tY + i, 0xff000000 | ((offsetX + x) ^ (offsetZ + z))); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java index a908e567..838feed0 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java @@ -13,40 +13,39 @@ public class GrassRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); //the bottom sub-chunk is sufficient to get grass data. TerrainChunkData data = chunk.getTerrain((byte) 0); - if(data == null || !data.load2DData()) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (data == null || !data.load2DData()) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); int x, z, color, i, j, tX, tY; - for (z = bZ, tY = pY; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { color = ((data.getGrassR(x, z) & 0xff) << 16) | ((data.getGrassG(x, z) & 0xff) << 8) | (data.getGrassB(x, z) & 0xff) | 0xff000000; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java index 5c91b532..e65b747b 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java @@ -13,38 +13,37 @@ public class HeightmapRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData data = chunk.getTerrain((byte) 0); - if(data == null || !data.load2DData()) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (data == null || !data.load2DData()) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - TerrainChunkData dataW = cm.getChunk(chunkX - 1, chunkZ).getTerrain((byte) 0); - TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ-1).getTerrain((byte) 0); + TerrainChunkData dataW = cm.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); + TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); boolean west = dataW != null && dataW.load2DData(), north = dataN != null && dataN.load2DData(); @@ -54,16 +53,16 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int r, g, b; float yNorm, yNorm2, heightShading; - for (z = bZ, tY = pY ; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { //smooth step function: 6x^5 - 15x^4 + 10x^3 y = data.getHeightMapValue(x, z); yNorm = (float) y / (float) dimension.chunkH; - yNorm2 = yNorm*yNorm; - yNorm = ((6f*yNorm2) - (15f*yNorm) + 10f)*yNorm2*yNorm; + yNorm2 = yNorm * yNorm; + yNorm = ((6f * yNorm2) - (15f * yNorm) + 10f) * yNorm2 * yNorm; yW = (x == 0) ? (west ? dataW.getHeightMapValue(dimension.chunkW - 1, z) : y)//chunk edge : data.getHeightMapValue(x - 1, z);//within chunk @@ -73,10 +72,10 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in heightShading = SatelliteRenderer.getHeightShading(y, yW, yN); - r = (int) (yNorm*heightShading*256f); - g = (int) (70f*heightShading); - b = (int) (256f*(1f-yNorm)/(yNorm + 1f)); - + r = (int) (yNorm * heightShading * 256f); + g = (int) (70f * heightShading); + b = (int) (256f * (1f - yNorm) / (yNorm + 1f)); + r = r < 0 ? 0 : r > 255 ? 255 : r; g = g < 0 ? 0 : g > 255 ? 255 : g; b = b < 0 ? 0 : b > 255 ? 255 : b; @@ -84,8 +83,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in color = (r << 16) | (g << 8) | b | 0xff000000; - for(i = 0; i < pL; i++){ - for(j = 0; j < pW; j++){ + for (i = 0; i < pL; i++) { + for (j = 0; j < pW; j++) { bm.setPixel(tX + j, tY + i, color); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java index d567e222..e1e537bb 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java @@ -20,23 +20,19 @@ will just make things worse on some (most?) phones. /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException; + Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java index 9d0dd824..e8f9f4b2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java @@ -1,8 +1,8 @@ package com.mithrilmania.blocktopograph.map.renderer; - import android.graphics.Bitmap; + import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.chunk.Chunk; @@ -17,38 +17,37 @@ public class NetherRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); //bottom chunk must be present TerrainChunkData floorData = chunk.getTerrain((byte) 0); - if(floorData == null || !floorData.load2DData()) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (floorData == null || !floorData.load2DData()) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - Chunk chunkW = cm.getChunk(chunkX - 1, chunkZ); - Chunk chunkN = cm.getChunk(chunkX, chunkZ-1); + Chunk chunkW = cm.getChunk(chunkX - 1, chunkZ, dimension); + Chunk chunkN = cm.getChunk(chunkX, chunkZ - 1, dimension); TerrainChunkData data; @@ -67,8 +66,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int subChunk; int stopSubChunk; - for (z = bZ, tY = pY ; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { worth = 0; shadingSum = 0; @@ -99,7 +98,7 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in : 0; //check if it is supported, default to full brightness to not lose details. - if(data.supportsBlockLightValues()) { + if (data.supportsBlockLightValues()) { lightShading = (float) lightValue / 15f + 1; } else { lightShading = 2f; @@ -115,8 +114,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in shadingSum += shading; - - a = 1f; offset = caveceil % cVersion.subChunkHeight; @@ -125,7 +122,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in stopSubChunk = caveceil / cVersion.subChunkHeight; - subChunkLoop: for(; subChunk >= stopSubChunk; subChunk--) { + subChunkLoop: + for (; subChunk >= stopSubChunk; subChunk--) { if (subChunk == stopSubChunk) stop = cavefloor % cVersion.subChunkHeight; data = chunk.getTerrain((byte) subChunk); @@ -179,7 +177,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } - layers++; } @@ -196,7 +193,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in b = b < 0 ? 0 : b > 255 ? 255 : b; - subChunkLoop: for(subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { + subChunkLoop: + for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { data = chunk.getTerrain((byte) subChunk); if (data == null || data.loadTerrain()) break; @@ -236,8 +234,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in color = (r << 16) | (g << 8) | b | 0xff000000; - for(i = 0; i < pL; i++){ - for(j = 0; j < pW; j++){ + for (i = 0; i < pL; i++) { + for (j = 0; j < pW; j++) { bm.setPixel(tX + j, tY + i, color); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java index a3db6887..e679a281 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java @@ -1,6 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.Log; @@ -22,10 +25,6 @@ public class SatelliteRenderer implements MapRenderer { * @param dimension Mapped dimension * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge * @param pX texture X pixel coord to start rendering to * @param pY texture Y pixel coord to start rendering to * @param pW width (X) of one block in pixels @@ -33,31 +32,33 @@ public class SatelliteRenderer implements MapRenderer { * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData data = chunk.getTerrain((byte) 0); if (data == null || !data.load2DData()) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - TerrainChunkData dataW = cm.getChunk(chunkX - 1, chunkZ).getTerrain((byte) 0); - TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ - 1).getTerrain((byte) 0); + TerrainChunkData dataW = cm.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); + TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); boolean west = dataW != null && dataW.load2DData(), north = dataN != null && dataN.load2DData(); int x, y, z, color, i, j, tX, tY; - for (z = bZ, tY = pY; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { y = data.getHeightMapValue(x, z); @@ -67,12 +68,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in (z == 0) ? (north ? dataN.getHeightMapValue(x, dimension.chunkL - 1) : y)//chunk edge : data.getHeightMapValue(x, z - 1)//within chunk ); - - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } @@ -118,8 +115,7 @@ private static int getColumnColour(Chunk chunk, TerrainChunkData floorData, int continue; } - ///Meow - for (y = 255; y >= 0; y--) { + for (y = offset; y >= 0; y--) { id = data.getBlockTypeId(x, y, z) & 0xff; @@ -231,4 +227,4 @@ public static float getHeightShading(int height, int heightW, int heightN) { return ((float) (Math.atan(heightDiff) / Math.PI) * shadingAmp) + 1f; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java index 53c98511..2f040cb2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java @@ -8,46 +8,41 @@ import com.mithrilmania.blocktopograph.util.MTwister; - public class SlimeChunkRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { int x, z, i, j, tX, tY; - MapType.OVERWORLD_SATELLITE.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + MapType.OVERWORLD_SATELLITE.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); boolean isSlimeChunk = isSlimeChunk(chunkX, chunkZ); int color, r, g, b, avg; //make slimeChunks much more green - for (z = bZ, tY = pY ; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { color = bm.getPixel(tX, tY); r = (color >> 16) & 0xff; g = (color >> 8) & 0xff; b = color & 0xff; avg = (r + g + b) / 3; - if(isSlimeChunk){ + if (isSlimeChunk) { r = b = avg; g = (g + 0xff) >> 1; } else { @@ -55,8 +50,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } color = (color & 0xFF000000) | (r << 16) | (g << 8) | b; - for(i = 0; i < pL; i++){ - for(j = 0; j < pW; j++){ + for (i = 0; i < pL; i++) { + for (j = 0; j < pW; j++) { bm.setPixel(tX + j, tY + i, color); } } @@ -69,7 +64,7 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in // See: https://gist.github.com/mithrilmania/00b85bf34a75fd8176342b1ad28bfccc - public static boolean isSlimeChunk(int cX, int cZ){ + public static boolean isSlimeChunk(int cX, int cZ) { // // MCPE slime-chunk checker // From Minecraft: Pocket Edition 0.15.0 (0.15.0.50_V870150050) @@ -119,7 +114,7 @@ public static boolean isSlimeChunk(int cX, int cZ){ // Multiply with 10 (3 bits) // ---> effect: the 3 bit randomness decrease expresses a 1 in a 10 chance. - long res = (((hi_shift3 + (hi_shift3 * 0x4)) & 0xffffffffL) * 0x2) & 0xffffffffL; + long res = (((hi_shift3 + (hi_shift3 * 0x4)) & 0xffffffffL) * 0x2) & 0xffffffffL; // Final check: is the input equal to 10 times less random, but comparable, output. // Every chunk has a 1 in 10 chance to be a slime-chunk. diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java index d241f8e3..e878cf96 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java @@ -18,30 +18,28 @@ public class XRayRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * + * @param cm ChunkManager, provides chunks, which provide chunk-data + * @param bm Bitmap to render to * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param bX begin block X coordinate, relative to chunk edge - * @param bZ begin block Z coordinate, relative to chunk edge - * @param eX end block X coordinate, relative to chunk edge - * @param eZ end block Z coordinate, relative to chunk edge - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels * @return bm is returned back - * * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int bX, int bZ, int eX, int eZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ); + Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); Version cVersion = chunk.getVersion(); - if(cVersion == Version.ERROR) return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); - if(cVersion == Version.NULL) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (cVersion == Version.ERROR) + return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (cVersion == Version.NULL) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData data; @@ -50,8 +48,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int x, y, z, color, i, j, tX, tY; //render width in blocks - int rW = eX - bX; - int size2D = rW * (eZ - bZ); + int rW = 16; + int size2D = rW * (16); int index2D; Block[] bestBlock = new Block[size2D]; @@ -63,17 +61,17 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int r, g, b; int subChunk; - for(subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { + for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { data = chunk.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) break; - for (z = bZ; z < eZ; z++) { - for (x = bX; x < eX; x++) { + for (z = 0; z < 16; z++) { + for (x = 0; x < 16; x++) { for (y = 0; y < cVersion.subChunkHeight; y++) { block = Block.getBlock(data.getBlockTypeId(x, y, z) & 0xff, 0); - index2D = ((z - bZ) * rW) + (x - bX); + index2D = ((z - 0) * rW) + (x - 0); if (block == null || block.id <= 1) continue; else if (block == Block.B_56_0_DIAMOND_ORE) { @@ -98,11 +96,12 @@ else if (block == Block.B_56_0_DIAMOND_ORE) { } } - if(subChunk == 0) return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, bX, bZ, eX, eZ, pX, pY, pW, pL); + if (subChunk == 0) + return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - for (z = bZ, tY = pY; z < eZ; z++, tY += pL) { - for (x = bX, tX = pX; x < eX; x++, tX += pW) { - block = bestBlock[((z - bZ) * rW) + (x - bX)]; + for (z = 0, tY = pY; z < 16; z++, tY += pL) { + for (x = 0, tX = pX; x < 16; x++, tX += pW) { + block = bestBlock[((z - 0) * rW) + (x - 0)]; if (block == null || block.color == null) { color = 0xff000000; } else { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/nbt/convert/NBTInputStream.java b/app/src/main/java/com/mithrilmania/blocktopograph/nbt/convert/NBTInputStream.java index 937b9894..b4eeb62d 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/nbt/convert/NBTInputStream.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/nbt/convert/NBTInputStream.java @@ -14,6 +14,8 @@ public final class NBTInputStream private final DataInputStream is; private final boolean littleEndian; + private int readCount; + public NBTInputStream(InputStream is) throws IOException { this(is, false, true); @@ -28,6 +30,7 @@ public NBTInputStream(InputStream is, boolean compressed, boolean littleEndian) throws IOException { this.littleEndian = littleEndian; this.is = new DataInputStream(compressed ? new GZIPInputStream(is) : is); + this.readCount = 0; } public ArrayList readTopLevelTags() { @@ -52,12 +55,15 @@ public Tag readTag() private Tag readTag(int depth) throws IOException { int type = this.is.readByte() & 0xFF; + readCount++; String name; if (type != NBTConstants.NBTType.END.id) { short shLen = this.is.readShort(); + readCount += 2; int nameLength = (this.littleEndian ? Short.reverseBytes(shLen) : shLen) & 0xFFFF; byte[] nameBytes = new byte[nameLength]; this.is.readFully(nameBytes); + readCount += nameLength; name = new String(nameBytes, NBTConstants.CHARSET.name()); } else { name = ""; @@ -70,7 +76,7 @@ private Tag readTagPayload(int type, String name, int depth) NBTConstants.NBTType nbtType = NBTConstants.NBTType.typesByID.get(type); - if(nbtType == null) throw new IOException("Invalid tag type: " + type + "."); + if (nbtType == null) throw new IOException("Invalid tag type: " + type + "."); int length; byte[] bytes; @@ -81,35 +87,47 @@ private Tag readTagPayload(int type, String name, int depth) } return new EndTag(); case BYTE: + readCount++; return new ByteTag(name, this.is.readByte()); case SHORT: + readCount += 2; return new ShortTag(name, this.littleEndian ? Short.reverseBytes(this.is.readShort()) : this.is.readShort()); case INT: + readCount += 4; return new IntTag(name, this.littleEndian ? Integer.reverseBytes(this.is.readInt()) : this.is.readInt()); case LONG: + readCount += 8; return new LongTag(name, this.littleEndian ? Long.reverseBytes(this.is.readLong()) : this.is.readLong()); case FLOAT: + readCount += 4; return new FloatTag(name, this.littleEndian ? Float.intBitsToFloat(Integer.reverseBytes(this.is.readInt())) : this.is.readFloat()); case DOUBLE: + readCount += 8; return new DoubleTag(name, this.littleEndian ? Double.longBitsToDouble(Long.reverseBytes(this.is.readLong())) : this.is.readDouble()); case BYTE_ARRAY: { length = this.littleEndian ? Integer.reverseBytes(this.is.readInt()) : this.is.readInt(); + readCount += 4; bytes = new byte[length]; this.is.readFully(bytes); + readCount += length; return new ByteArrayTag(name, bytes); } case STRING: { length = this.littleEndian ? Short.reverseBytes(this.is.readShort()) : this.is.readShort(); + readCount+=2; bytes = new byte[length]; this.is.readFully(bytes); + readCount += length; return new StringTag(name, new String(bytes, NBTConstants.CHARSET.name())); } case LIST: { int childType = this.is.readByte(); + readCount++; length = this.littleEndian ? Integer.reverseBytes(this.is.readInt()) : this.is.readInt(); + readCount += 4; NBTConstants.NBTType childNbtType = NBTConstants.NBTType.typesByID.get(childType); - if(childNbtType.id == 0) return new ListTag(name, new ArrayList()); + if (childNbtType.id == 0) return new ListTag(name, new ArrayList()); Class clazz = childNbtType.tagClazz; ArrayList tagList = new ArrayList<>(); for (int i = 0; i < length; i++) { @@ -134,26 +152,32 @@ private Tag readTagPayload(int type, String name, int depth) } case INT_ARRAY: { length = this.littleEndian ? Integer.reverseBytes(this.is.readInt()) : this.is.readInt(); + readCount += 4; int[] ints = new int[length]; - if(this.littleEndian) for (int i = 0; i < length; i++) { + readCount += length << 2; + if (this.littleEndian) for (int i = 0; i < length; i++) { ints[i] = Integer.reverseBytes(this.is.readInt()); - } else for (int i = 0; i < length; i++) { + } + else for (int i = 0; i < length; i++) { ints[i] = this.is.readInt(); } return new IntArrayTag(name, ints); } case SHORT_ARRAY: { length = this.littleEndian ? Integer.reverseBytes(this.is.readInt()) : this.is.readInt(); + readCount += 4; short[] shorts = new short[length]; - if(this.littleEndian) for (int i = 0; i < length; i++) { + readCount += length << 1; + if (this.littleEndian) for (int i = 0; i < length; i++) { shorts[i] = Short.reverseBytes(this.is.readShort()); - } else for (int i = 0; i < length; i++) { + } + else for (int i = 0; i < length; i++) { shorts[i] = this.is.readShort(); } return new ShortArrayTag(name, shorts); } default: { - throw new IOException("Unhandled NBT type!!! type: "+type); + throw new IOException("Unhandled NBT type!!! type: " + type); } } } @@ -163,6 +187,14 @@ public void close() this.is.close(); } + public int getReadCount() { + return readCount; + } + + public void resetReadCount() { + readCount = 0; + } + public boolean isLittleEndian() { return this.littleEndian; } diff --git a/leveldb/build.gradle b/leveldb/build.gradle index f463cedd..70571592 100644 --- a/leveldb/build.gradle +++ b/leveldb/build.gradle @@ -13,14 +13,15 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk { - abiFilters 'armeabi-v7a' + abiFilters 'armeabi-v7a','arm64-v8a' + //abiFilters 'x86' } } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk { - abiFilters 'armeabi-v7a' + abiFilters 'armeabi-v7a','arm64-v8a','x86' } } } diff --git a/leveldb/src/main/java/com/litl/leveldb/Chunk.java b/leveldb/src/main/java/com/litl/leveldb/Chunk.java deleted file mode 100644 index 6e11ec51..00000000 --- a/leveldb/src/main/java/com/litl/leveldb/Chunk.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.litl.leveldb; - -import java.io.File; -import java.nio.ByteBuffer; - -public class Chunk extends NativeObject { - - public Chunk(DB db, int x, int z, int dim) { - super(); - mPtr = nativeOpen(db.getPtr(), x, z, dim); - } - - public boolean isDead() { - return mPtr == 0; - } - - public int getBlock(int x, int y, int z) { - return nativeGetBlock(mPtr, x, y, z); - } - - public void close() { - closeNativeObject(mPtr); - } - - @Override - protected void closeNativeObject(long ptr) { - nativeClose(ptr); - } - - private static native long nativeOpen(long dbptr, int x, int z, int dim); - - private static native int nativeGetBlock(long ckptr, int x, int y, int z); - - private static native void nativeClose(long ckPtr); - - { - System.loadLibrary("leveldbjni"); - } -} diff --git a/leveldb/src/main/jni/Android.mk b/leveldb/src/main/jni/Android.mk index 37bf2171..60619bde 100644 --- a/leveldb/src/main/jni/Android.mk +++ b/leveldb/src/main/jni/Android.mk @@ -12,18 +12,24 @@ LOCAL_CPP_EXTENSION := .cc LOCAL_CFLAGS := -DLEVELDB_PLATFORM_ANDROID -std=gnu++11 -DDLLX= +#Asan +#LOCAL_CFLAGS += -fsanitize=leak -fno-omit-frame-pointer +#LOCAL_LDFLAGS := -fsanitize=leak + LOCAL_SRC_FILES := com_litl_leveldb_DB.cc\ com_litl_leveldb_Iterator.cc\ com_litl_leveldb_WriteBatch.cc\ leveldbjni.cc\ - com_litl_leveldb_Chunk.cc\ - subchunk.cc Chunk.cc\ - blocknames.cc LOCAL_STATIC_LIBRARIES += leveldb-mcpe LOCAL_LDLIBS += -llog -ldl -lz +LOCAL_CPP_FEATURES += exceptions + +#Asan +#LD_PRELOAD += libclang_rt.asan-i686-android.so + include $(BUILD_SHARED_LIBRARY) #################################################################################################### diff --git a/leveldb/src/main/jni/Application.mk b/leveldb/src/main/jni/Application.mk index 8d557f97..5e6279e6 100644 --- a/leveldb/src/main/jni/Application.mk +++ b/leveldb/src/main/jni/Application.mk @@ -1,2 +1,6 @@ APP_PLATFORM=android-16 + APP_STL := c++_static + +#Asan +#APP_STL := c++_shared diff --git a/leveldb/src/main/jni/Chunk.cc b/leveldb/src/main/jni/Chunk.cc deleted file mode 100644 index 19e8fab1..00000000 --- a/leveldb/src/main/jni/Chunk.cc +++ /dev/null @@ -1,78 +0,0 @@ -// -// Created by barco on 2018/3/23. -// - - -#include -#include "Chunk.h" -#include "mapkey.h" -#include "qstr.h" -#include -#include -#include -#include "subchunk.h" - -#ifdef LOG_CHUNK_LOADSAVE -#define LOGE_LS(x, ...) LOGE(CAT("Chunk: ", x), ##__VA_ARGS__); -#else -#define LOGE_LS(x, ...) -#endif - -#ifdef LOG_CHUNK_OPERATION -#define LOGE_OP(x, ...) LOGE(CAT("Chunk: ", x), ##__VA_ARGS__); -#else -#define LOGE_OP(x, ...) -#endif - -//Init constants. - -leveldb::ReadOptions Chunk::readOptions; - -Chunk::Chunk(leveldb::DB *db, mapkey_t key) - : key(key) { - std::string str; - memset(subchunks, 0, sizeof(subchunks)); - for (int i = 0; i < 16; i++) { - loadSubChunk(db, i); - } -} - -void Chunk::loadSubChunk(leveldb::DB *db, unsigned char which) { - LDBKEY_SUBCHUNK(this->key, which) - leveldb::Slice slice(key, 0 == this->key.dimension ? 10 : 14); - std::string val; - bool hit = db->Get(readOptions, slice, &val).ok(); - if (hit) {//Found, detect version. - switch (val[0]) { - case 0://Aligned subchunk. - case 2://All - case 3://The - case 4://Same - case 5://Really - case 6://Wierd - case 7://Ahhh - break; - case 8://Current paletted format - subchunks[which] = new SubChunk(val, true); - break; - case 1: //Previous paletted version - subchunks[which] = new SubChunk(val, false); - break; - default: - //Unsupported format. - break; - } - } -} - -uint16_t Chunk::getBlock(unsigned char x, unsigned char y, unsigned char z) { - unsigned char sub = y >> 4; - if (subchunks[sub] == nullptr)return 0; - return subchunks[sub]->getBlock(x, static_cast(y & 0xf), z); -} - -Chunk::~Chunk() { - for (int i = 0; i < 16; i++) { - delete subchunks[i]; - } -} diff --git a/leveldb/src/main/jni/Chunk.h b/leveldb/src/main/jni/Chunk.h deleted file mode 100644 index 568ff7d5..00000000 --- a/leveldb/src/main/jni/Chunk.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Created by barco on 2018/3/23. -// - -#ifndef CONVARTER_CHUNK_H -#define CONVARTER_CHUNK_H - -#include -#include -#include -#include "mapkey.h" -#include "debug_conf.h" -#include "qstr.h" - -class SubChunk; - -class Chunk { -private: - - //Member vars. - - mapkey_t key; - - SubChunk *subchunks[16]; - - void loadSubChunk(leveldb::DB *db, unsigned char which); - -public: - - static leveldb::ReadOptions readOptions; - - Chunk(leveldb::DB *db, mapkey_t key); - - ~Chunk(); - - uint16_t getBlock(unsigned char x, unsigned char y, unsigned char z); -}; - -#endif //CONVARTER_CHUNK_H diff --git a/leveldb/src/main/jni/blocknames.cc b/leveldb/src/main/jni/blocknames.cc deleted file mode 100644 index a6e27e7e..00000000 --- a/leveldb/src/main/jni/blocknames.cc +++ /dev/null @@ -1,277 +0,0 @@ -// -// Created by barco on 2018/3/30. -// - -#include -#include -#include "blocknames.h" - -char BlockNames::names[256][32] = {"air", - "stone", - "grass", - "dirt", - "cobblestone", - "planks", - "sapling", - "bedrock", - "flowing_water", - "water", - "flowing_lava", - "lava", - "sand", - "gravel", - "gold_ore", - "iron_ore", - "coal_ore", - "log", - "leaves", - "sponge", - "glass", - "lapis_ore", - "lapis_block", - "dispenser", - "sandstone", - "noteblock", - "bed", - "golden_rail", - "detector_rail", - "sticky_piston", - "web", - "tallgrass", - "deadbush", - "piston", - "pistonarmcollision", - "wool", - "air", - "yellow_flower", - "red_flower", - "brown_mushroom", - "red_mushroom", - "gold_block", - "iron_block", - "double_stone_slab", - "stone_slab", - "brick_block", - "tnt", - "bookshelf", - "mossy_cobblestone", - "obsidian", - "torch", - "fire", - "mob_spawner", - "oak_stairs", - "chest", - "redstone_wire", - "diamond_ore", - "diamond_block", - "crafting_table", - "wheat", - "farmland", - "furnace", - "lit_furnace", - "standing_sign", - "wooden_door", - "ladder", - "rail", - "stone_stairs", - "wall_sign", - "lever", - "stone_pressure_plate", - "iron_door", - "oak_pressure_plate", - "redstone_ore", - "lit_redstone_ore", - "unlit_redstone_torch", - "lit_redstone_torch", - "stone_button", - "snow_layer", - "ice", - "snow", - "cactus", - "clay", - "reeds", - "jukebox", - "fence", - "pumpkin", - "netherrack", - "soul_sand", - "glowstone", - "portal", - "lit_pumpkin", - "cake", - "unpowered_repeater", - "powered_repeater", - "invisiblebedrock", - "oak_trapdoor", - "monster_egg", - "stonebrick", - "brown_mushroom_block", - "red_mushroom_block", - "iron_bars", - "glass_pane", - "melon_block", - "pumpkin_stem", - "melon_stem", - "vine", - "fence_gate", - "brick_stairs", - "stone_brick_stairs", - "mycelium", - "waterlily", - "nether_brick", - "nether_brick_fence", - "nether_brick_stairs", - "nether_wart", - "enchanting_table", - "brewing_stand", - "cauldron", - "end_portal", - "end_portal_frame", - "end_stone", - "dragon_egg", - "redstone_lamp", - "lit_redstone_lamp", - "dropper", - "activator_rail", - "cocoa", - "sandstone_stairs", - "emerald_ore", - "ender_chest", - "tripwire_hook", - "tripwire", - "emerald_block", - "spruce_stairs", - "birch_stairs", - "jungle_stairs", - "command_block", - "beacon", - "cobblestone_wall", - "flower_pot", - "carrots", - "potatoes", - "oak_button", - "skull", - "anvil", - "trapped_chest", - "light_weighted_pressure_plate", - "heavy_weighted_pressure_plate", - "unpowered_comparator", - "powered_comparator", - "daylight_detector", - "redstone_block", - "quartz_ore", - "hopper", - "quartz_block", - "quartz_stairs", - "double_wooden_slab", - "wooden_slab", - "stained_hardened_clay", - "stained_glass_pane", - "leaves2", - "log2", - "acacia_stairs", - "dark_oak_stairs", - "slime", - "air", - "iron_trapdoor", - "prismarine", - "sealantern", - "hay_block", - "carpet", - "terracotta", - "coal_block", - "packed_ice", - "double_plant", - "standing_banner", - "wall_banner", - "daylight_detector_inverted", - "red_sandstone", - "red_sandstone_stairs", - "double_stone_slab2", - "stone_slab2", - "spruce_fence_gate", - "birch_fence_gate", - "jungle_fence_gate", - "dark_oak_fence_gate", - "acacia_fence_gate", - "repeating_command_block", - "chain_command_block", - "air", - "air", - "air", - "spruce_door", - "birch_door", - "jungle_door", - "acacia_door", - "dark_oak_door", - "grass_path", - "frame", - "chorus_flower", - "purpur_block", - "purpur_stairs", - "air", - "air", - "undyed_shulker_box", - "end_bricks", - "frosted_ice", - "end_rod", - "end_gateway", - "air", - "air", - "air", - "magma", - "nether_wart_block", - "red_nether_brick", - "bone_block", - "air", - "shulker_box", - "purple_glazed_terracotta", - "white_glazed_terracotta", - "orange_glazed_terracotta", - "magenta_glazed_terracotta", - "light_blue_glazed_terracotta", - "yellow_glazed_terracotta", - "lime_glazed_terracotta", - "pink_glazed_terracotta", - "gray_glazed_terracotta", - "silver_glazed_terracotta", - "cyan_glazed_terracotta", - "air", - "blue_glazed_terracotta", - "brown_glazed_terracotta", - "green_glazed_terracotta", - "red_glazed_terracotta", - "black_glazed_terracotta", - "concrete", - "concretepowder", - "air", - "air", - "chorus_plant", - "stained_glass", - "air", - "podzol", - "beetroot", - "stonecutter", - "glowingobsidian", - "netherreactor", - "info_update", - "info_update2", - "movingblock", - "observer", - "structure_block", - "air", - "air", - "reserved6"}; - -unsigned char BlockNames::resolve(qstr name) { - if (name.str[9] == ':') { - name.str += 10; - name.length -= 10; - } - for (int i = 0; i < 256; i++) { - char *nam = names[i]; - if (memcmp(nam, name.str, name.length) != 0)continue; - return static_cast(i); - } - return 0; -} diff --git a/leveldb/src/main/jni/blocknames.h b/leveldb/src/main/jni/blocknames.h deleted file mode 100644 index 663e61a8..00000000 --- a/leveldb/src/main/jni/blocknames.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// Created by barco on 2018/3/29. -// - -#ifndef CONVARTER_BLOCKNAMES_H -#define CONVARTER_BLOCKNAMES_H - -#include "qstr.h" - -class BlockNames { -public: - static char names[256][32]; - - static unsigned char resolve(qstr name); -}; - -#endif //CONVARTER_BLOCKNAMES_H diff --git a/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc b/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc deleted file mode 100644 index 6b4814ce..00000000 --- a/leveldb/src/main/jni/com_litl_leveldb_Chunk.cc +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include - -#include -#include -#include -#include - -#include "leveldbjni.h" -#include "Chunk.h" - -static jlong nativeOpen(JNIEnv *env, jclass clazz, jlong dbptr, jint x, jint z, jint dim) { - leveldb::DB *db = reinterpret_cast(dbptr); - mapkey_t mapkey = LDBKEY_STRUCT(x, z, dim); - LDBKEY_VERSION(mapkey) - leveldb::Slice skey(key_db, mapkey.dimension == 0 ? 9 : 13); - std::string str; - leveldb::Status status = db->Get(Chunk::readOptions, skey, &str); - if (status.ok()) { - if (str[0] < 7)return NULL; - } else return NULL; - Chunk *chunk = new Chunk(db, mapkey); - return reinterpret_cast(chunk); -} - -static jint nativeGetBlock(JNIEnv *env, jclass clazz, jlong ckptr, jint x, jint y, jint z) { - Chunk *chunk = reinterpret_cast(ckptr); - return chunk->getBlock(static_cast(x), static_cast(y), - static_cast(z)); -} - -static void nativeClose(JNIEnv *env, jclass clazz, jlong ckptr) { - Chunk *chunk = reinterpret_cast(ckptr); - delete chunk; -} - -static JNINativeMethod sMethods[] = - { - {"nativeOpen", "(JIII)J", (void *) nativeOpen}, - {"nativeGetBlock", "(JIII)I", (void *) nativeGetBlock}, - {"nativeClose", "(J)V", (void *) nativeClose} - }; - -int -register_com_litl_leveldb_Chunk(JNIEnv *env) { - jclass clazz = env->FindClass("com/litl/leveldb/Chunk"); - if (!clazz) { - LOGE("Can't find class com.litl.leveldb.Chunk"); - return 0; - } - - return env->RegisterNatives(clazz, sMethods, NELEM(sMethods)); -} diff --git a/leveldb/src/main/jni/com_litl_leveldb_DB.cc b/leveldb/src/main/jni/com_litl_leveldb_DB.cc index dfd319c4..639daf51 100644 --- a/leveldb/src/main/jni/com_litl_leveldb_DB.cc +++ b/leveldb/src/main/jni/com_litl_leveldb_DB.cc @@ -21,59 +21,66 @@ static jmethodID gByteBuffer_positionMethodID; static jmethodID gByteBuffer_limitMethodID; static jmethodID gByteBuffer_arrayMethodID; -static leveldb::ZlibCompressor* zlibCompressorInstance; +static leveldb::ZlibCompressor *zlibCompressorInstance; +static leveldb::ZlibCompressorRaw *zlibCompressorRawInstance; +static leveldb::Logger *nullLoggerInstance; +static leveldb::DecompressAllocator *decompressAllocatorInstance; class NullLogger : public leveldb::Logger { - public: - void Logv(const char*, va_list) override { - } - }; +public: + void Logv(const char *, va_list) override { + } +}; static jlong -nativeOpen(JNIEnv* env, +nativeOpen(JNIEnv *env, jclass clazz, - jstring dbpath) -{ + jstring dbpath) { static bool gInited; if (!gInited) { - jclass byteBuffer_Clazz = env->FindClass("java/nio/ByteBuffer"); - gByteBuffer_isDirectMethodID = env->GetMethodID(byteBuffer_Clazz, - "isDirect", "()Z"); - gByteBuffer_positionMethodID = env->GetMethodID(byteBuffer_Clazz, - "position", "()I"); - gByteBuffer_limitMethodID = env->GetMethodID(byteBuffer_Clazz, - "limit", "()I"); - gByteBuffer_arrayMethodID = env->GetMethodID(byteBuffer_Clazz, - "array", "()[B"); - gInited = true; + jclass byteBuffer_Clazz = env->FindClass("java/nio/ByteBuffer"); + gByteBuffer_isDirectMethodID = env->GetMethodID(byteBuffer_Clazz, + "isDirect", "()Z"); + gByteBuffer_positionMethodID = env->GetMethodID(byteBuffer_Clazz, + "position", "()I"); + gByteBuffer_limitMethodID = env->GetMethodID(byteBuffer_Clazz, + "limit", "()I"); + gByteBuffer_arrayMethodID = env->GetMethodID(byteBuffer_Clazz, + "array", "()[B"); + gInited = true; } const char *path = env->GetStringUTFChars(dbpath, 0); LOGI("Opening database %s", path); - leveldb::DB* db; + leveldb::DB *db; leveldb::Options options; options.create_if_missing = true; options.paranoid_checks = true; - if (zlibCompressorInstance == NULL) { + if (zlibCompressorInstance == nullptr) zlibCompressorInstance = new leveldb::ZlibCompressor(); - } + if (zlibCompressorRawInstance == nullptr) + zlibCompressorRawInstance = new leveldb::ZlibCompressorRaw(-1); + if (nullLoggerInstance == nullptr) + nullLoggerInstance = new NullLogger(); + if (decompressAllocatorInstance == nullptr) + decompressAllocatorInstance = new leveldb::DecompressAllocator(); - //create a bloom filter to quickly tell if a key is in the database or not - options.filter_policy = leveldb::NewBloomFilterPolicy(10); + //create a bloom filter to quickly tell if a key is in the database or not + options.filter_policy = leveldb::NewBloomFilterPolicy(10); - //create a 40 mb cache (we use this on ~1gb devices) - options.block_cache = leveldb::NewLRUCache(40 * 1024 * 1024); + //create a 40 mb cache (we use this on ~1gb devices) + options.block_cache = leveldb::NewLRUCache(40 * 1024 * 1024); - //create a 4mb write buffer, to improve compression and touch the disk less - options.write_buffer_size = 4 * 1024 * 1024; + //create a 4mb write buffer, to improve compression and touch the disk less + options.write_buffer_size = 4 * 1024 * 1024; - //disable internal logging. The default logger will still print out things to a file - options.info_log = new NullLogger(); + //disable internal logging. The default logger will still print out things to a file + options.info_log = nullLoggerInstance; - //use the new raw-zip compressor to write (and read) - options.compressors[0] = new leveldb::ZlibCompressorRaw(-1); + //use the new raw-zip compressor to write (and read) + options.compressors[0] = zlibCompressorRawInstance; options.compressors[1] = zlibCompressorInstance; leveldb::Status status = leveldb::DB::Open(options, path, &db); @@ -89,91 +96,88 @@ nativeOpen(JNIEnv* env, } static void -nativeClose(JNIEnv* env, +nativeClose(JNIEnv *env, jclass clazz, - jlong dbPtr) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); - if (db) { - delete db; - } + jlong dbPtr) { + leveldb::DB *db = reinterpret_cast(dbPtr); + delete db; LOGI("Database closed"); } static jbyteArray -nativeGet(JNIEnv * env, +nativeGet(JNIEnv *env, jclass clazz, jlong dbPtr, jlong snapshotPtr, - jbyteArray keyObj) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); + jbyteArray keyObj) { + leveldb::DB *db = reinterpret_cast(dbPtr); leveldb::ReadOptions options = leveldb::ReadOptions(); - options.decompress_allocator = new leveldb::DecompressAllocator(); - options.snapshot = reinterpret_cast(snapshotPtr); + options.decompress_allocator = decompressAllocatorInstance; + //options.snapshot = reinterpret_cast(snapshotPtr); - size_t keyLen = env->GetArrayLength(keyObj); + size_t keyLen = static_cast(env->GetArrayLength(keyObj)); jbyte *buffer = env->GetByteArrayElements(keyObj, NULL); jbyteArray result; - leveldb::Slice key = leveldb::Slice((const char *)buffer, keyLen); - leveldb::Iterator* iter = db->NewIterator(options); - iter->Seek(key); - if (iter->Valid() && key == iter->key()) { - leveldb::Slice value = iter->value(); - size_t len = value.size(); - result = env->NewByteArray(len); - env->SetByteArrayRegion(result, 0, len, (const jbyte *) value.data()); - } else { - result = NULL; - } - + leveldb::Slice key = leveldb::Slice((const char *) buffer, keyLen); + //leveldb::Iterator *iter = db->NewIterator(options); + //iter->Seek(key); + //if (iter->Valid() && key == iter->key()) { + //leveldb::Slice value = iter->value(); + std::string str; + db->Get(options, key, &str); + size_t len = str.size(); + result = env->NewByteArray(static_cast(len)); + env->SetByteArrayRegion(result, 0, static_cast(len), (const jbyte *) str.c_str()); + //} else { + //result = NULL; + //} env->ReleaseByteArrayElements(keyObj, buffer, JNI_ABORT); - delete iter; + //delete iter; return result; } static jbyteArray -nativeGetBB(JNIEnv * env, +nativeGetBB(JNIEnv *env, jclass clazz, jlong dbPtr, jlong snapshotPtr, - jobject keyObj) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); + jobject keyObj) { + leveldb::DB *db = reinterpret_cast(dbPtr); leveldb::ReadOptions options = leveldb::ReadOptions(); - options.snapshot = reinterpret_cast(snapshotPtr); + options.snapshot = reinterpret_cast(snapshotPtr); jint keyPos = env->CallIntMethod(keyObj, gByteBuffer_positionMethodID); jint keyLimit = env->CallIntMethod(keyObj, gByteBuffer_limitMethodID); jboolean keyIsDirect = env->CallBooleanMethod(keyObj, gByteBuffer_isDirectMethodID); jbyteArray keyArray; - void* key; + void *key; if (keyIsDirect) { key = env->GetDirectBufferAddress(keyObj); keyArray = NULL; } else { keyArray = (jbyteArray) env->CallObjectMethod(keyObj, gByteBuffer_arrayMethodID); - key = (void*) env->GetByteArrayElements(keyArray, NULL); + key = (void *) env->GetByteArrayElements(keyArray, NULL); } jbyteArray result; - leveldb::Slice keySlice = leveldb::Slice((const char *) key + keyPos, keyLimit - keyPos); - leveldb::Iterator* iter = db->NewIterator(options); + leveldb::Slice keySlice = leveldb::Slice((const char *) key + keyPos, + static_cast(keyLimit - keyPos)); + leveldb::Iterator *iter = db->NewIterator(options); iter->Seek(keySlice); if (iter->Valid() && keySlice == iter->key()) { leveldb::Slice value = iter->value(); size_t len = value.size(); - result = env->NewByteArray(len); - env->SetByteArrayRegion(result, 0, len, (const jbyte *) value.data()); + result = env->NewByteArray(static_cast(len)); + env->SetByteArrayRegion(result, 0, static_cast(len), (const jbyte *) value.data()); } else { result = NULL; } if (keyArray) { - env->ReleaseByteArrayElements(keyArray, (jbyte*) key, JNI_ABORT); + env->ReleaseByteArrayElements(keyArray, (jbyte *) key, JNI_ABORT); } delete iter; @@ -186,9 +190,8 @@ nativePut(JNIEnv *env, jclass clazz, jlong dbPtr, jbyteArray keyObj, - jbyteArray valObj) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); + jbyteArray valObj) { + leveldb::DB *db = reinterpret_cast(dbPtr); size_t keyLen = env->GetArrayLength(keyObj); jbyte *keyBuf = env->GetByteArrayElements(keyObj, NULL); @@ -197,8 +200,8 @@ nativePut(JNIEnv *env, jbyte *valBuf = env->GetByteArrayElements(valObj, NULL); leveldb::Status status = db->Put(leveldb::WriteOptions(), - leveldb::Slice((const char *) keyBuf, keyLen), - leveldb::Slice((const char *) valBuf, valLen)); + leveldb::Slice((const char *) keyBuf, keyLen), + leveldb::Slice((const char *) valBuf, valLen)); env->ReleaseByteArrayElements(keyObj, keyBuf, JNI_ABORT); env->ReleaseByteArrayElements(valObj, valBuf, JNI_ABORT); @@ -212,14 +215,14 @@ static void nativeDelete(JNIEnv *env, jclass clazz, jlong dbPtr, - jbyteArray keyObj) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); + jbyteArray keyObj) { + leveldb::DB *db = reinterpret_cast(dbPtr); size_t keyLen = env->GetArrayLength(keyObj); jbyte *buffer = env->GetByteArrayElements(keyObj, NULL); - leveldb::Status status = db->Delete(leveldb::WriteOptions(), leveldb::Slice((const char *) buffer, keyLen)); + leveldb::Status status = db->Delete(leveldb::WriteOptions(), + leveldb::Slice((const char *) buffer, keyLen)); env->ReleaseByteArrayElements(keyObj, buffer, JNI_ABORT); if (!status.ok()) { @@ -231,9 +234,8 @@ static void nativeWrite(JNIEnv *env, jclass clazz, jlong dbPtr, - jlong batchPtr) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); + jlong batchPtr) { + leveldb::DB *db = reinterpret_cast(dbPtr); leveldb::WriteBatch *batch = (leveldb::WriteBatch *) batchPtr; leveldb::Status status = db->Write(leveldb::WriteOptions(), batch); @@ -243,14 +245,13 @@ nativeWrite(JNIEnv *env, } static jlong -nativeIterator(JNIEnv* env, +nativeIterator(JNIEnv *env, jclass clazz, jlong dbPtr, - jlong snapshotPtr) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); + jlong snapshotPtr) { + leveldb::DB *db = reinterpret_cast(dbPtr); leveldb::ReadOptions options = leveldb::ReadOptions(); - options.snapshot = reinterpret_cast(snapshotPtr); + options.snapshot = reinterpret_cast(snapshotPtr); leveldb::Iterator *iter = db->NewIterator(options); return reinterpret_cast(iter); @@ -259,10 +260,9 @@ nativeIterator(JNIEnv* env, static jlong nativeGetSnapshot(JNIEnv *env, jclass clazz, - jlong dbPtr) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); - const leveldb::Snapshot* snapshot = db->GetSnapshot(); + jlong dbPtr) { + leveldb::DB *db = reinterpret_cast(dbPtr); + const leveldb::Snapshot *snapshot = db->GetSnapshot(); return reinterpret_cast(snapshot); } @@ -270,19 +270,17 @@ static void nativeReleaseSnapshot(JNIEnv *env, jclass clazz, jlong dbPtr, - jlong snapshotPtr) -{ - leveldb::DB* db = reinterpret_cast(dbPtr); - const leveldb::Snapshot *snapshot = reinterpret_cast(snapshotPtr); + jlong snapshotPtr) { + leveldb::DB *db = reinterpret_cast(dbPtr); + const leveldb::Snapshot *snapshot = reinterpret_cast(snapshotPtr); db->ReleaseSnapshot(snapshot); } static void nativeDestroy(JNIEnv *env, jclass clazz, - jstring dbpath) -{ - const char* path = env->GetStringUTFChars(dbpath,0); + jstring dbpath) { + const char *path = env->GetStringUTFChars(dbpath, 0); leveldb::Options options; options.create_if_missing = true; leveldb::Status status = DestroyDB(path, options); @@ -292,19 +290,19 @@ nativeDestroy(JNIEnv *env, } static JNINativeMethod sMethods[] = -{ - { "nativeOpen", "(Ljava/lang/String;)J", (void*) nativeOpen }, - { "nativeClose", "(J)V", (void*) nativeClose }, - { "nativeGet", "(JJ[B)[B", (void*) nativeGet }, - { "nativeGet", "(JJLjava/nio/ByteBuffer;)[B", (void*) nativeGetBB }, - { "nativePut", "(J[B[B)V", (void*) nativePut }, - { "nativeDelete", "(J[B)V", (void*) nativeDelete }, - { "nativeWrite", "(JJ)V", (void*) nativeWrite }, - { "nativeIterator", "(JJ)J", (void*) nativeIterator }, - { "nativeGetSnapshot", "(J)J", (void*) nativeGetSnapshot }, - { "nativeReleaseSnapshot", "(JJ)V", (void*) nativeReleaseSnapshot }, - { "nativeDestroy", "(Ljava/lang/String;)V", (void*) nativeDestroy } -}; + { + {"nativeOpen", "(Ljava/lang/String;)J", (void *) nativeOpen}, + {"nativeClose", "(J)V", (void *) nativeClose}, + {"nativeGet", "(JJ[B)[B", (void *) nativeGet}, + {"nativeGet", "(JJLjava/nio/ByteBuffer;)[B", (void *) nativeGetBB}, + {"nativePut", "(J[B[B)V", (void *) nativePut}, + {"nativeDelete", "(J[B)V", (void *) nativeDelete}, + {"nativeWrite", "(JJ)V", (void *) nativeWrite}, + {"nativeIterator", "(JJ)J", (void *) nativeIterator}, + {"nativeGetSnapshot", "(J)J", (void *) nativeGetSnapshot}, + {"nativeReleaseSnapshot", "(JJ)V", (void *) nativeReleaseSnapshot}, + {"nativeDestroy", "(Ljava/lang/String;)V", (void *) nativeDestroy} + }; int register_com_litl_leveldb_DB(JNIEnv *env) { diff --git a/leveldb/src/main/jni/debug_conf.h b/leveldb/src/main/jni/debug_conf.h deleted file mode 100644 index a183c6ac..00000000 --- a/leveldb/src/main/jni/debug_conf.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// Created by barco on 2018/4/12. -// - -#ifndef CONVARTER_DEBUG_CONF_H -#define CONVARTER_DEBUG_CONF_H - -//Chunk -//#define LOG_CHUNK_OPERATION -#define LOG_CHUNK_LOADSAVE - -//World -//#define LOG_SAVDB_OPERATION -#define LOG_SAVDB_LOADSAVE -#define LOG_SAVDB_LRU - -#ifdef LOG_CHUNK_OPERATION -#ifndef LOG_CHUNK_LOADSAVE -#define LOG_CHUNK_LOADSAVE -#endif -#endif - -#ifdef LOG_SAVDB_OPERATION -#ifndef LOG_SAVDB_LOADSAVE -#define LOG_SAVDB_LOADSAVE -#endif -#ifndef LOG_SAVDB_LRU -#define LOG_SAVDB_LRU -#endif -#endif - -#define CAT(x, y) x y - -#endif //CONVARTER_DEBUG_CONF_H diff --git a/leveldb/src/main/jni/leveldbjni.cc b/leveldb/src/main/jni/leveldbjni.cc index 28d53a5f..70cffaaf 100644 --- a/leveldb/src/main/jni/leveldbjni.cc +++ b/leveldb/src/main/jni/leveldbjni.cc @@ -1,5 +1,4 @@ #include "leveldbjni.h" -#include "Chunk.h" extern int register_com_litl_leveldb_DB(JNIEnv *env); @@ -7,8 +6,6 @@ extern int register_com_litl_leveldb_WriteBatch(JNIEnv *env); extern int register_com_litl_leveldb_Iterator(JNIEnv *env); -extern int register_com_litl_leveldb_Chunk(JNIEnv *env); - jint throwException(JNIEnv *env, leveldb::Status status) { const char *exceptionClass; @@ -41,9 +38,6 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { register_com_litl_leveldb_DB(env); register_com_litl_leveldb_WriteBatch(env); register_com_litl_leveldb_Iterator(env); - register_com_litl_leveldb_Chunk(env); - - Chunk::readOptions.decompress_allocator = new leveldb::DecompressAllocator; return JNI_VERSION_1_6; } diff --git a/leveldb/src/main/jni/mapkey.h b/leveldb/src/main/jni/mapkey.h deleted file mode 100644 index 5f05e60b..00000000 --- a/leveldb/src/main/jni/mapkey.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// Created by barco on 2018/3/27. -// - -#ifndef CONVARTER_MAPKEY_H -#define CONVARTER_MAPKEY_H - -#include - -typedef struct { - int32_t x_div16; - int32_t z_div16; - int32_t dimension; -} mapkey_t; - -#define LDBKEY_STRUCT(x, z, dim) mapkey_t{x >> 4, z >> 4, dim} - -#define LDBKEY_SUBCHUNK(k, ydiv) \ - char key[14];\ - if(true){\ - char* ptr = key;\ - *(int32_t*)ptr = k.x_div16;\ - ptr+=4;\ - *(int32_t*)ptr = k.z_div16;\ - ptr += 4;\ - if(k.dimension != 0){\ - *(int32_t*)ptr = k.dimension;\ - ptr += 4;\ - }\ - *ptr = 0x2f;\ - ptr++;\ - *ptr = ydiv;\ - } - -#define LDBKEY_VERSION(k) \ - char key_db[14];\ - if(true){\ - char* ptr = key_db;\ - *(int32_t*)ptr = k.x_div16;\ - ptr+=4;\ - *(int32_t*)ptr = k.z_div16;\ - ptr += 4;\ - if(k.dimension != 0){\ - *(int32_t*)ptr = k.dimension;\ - ptr += 4;\ - }\ - *ptr = 0x76;\ - ptr++;\ - } - -#endif //CONVARTER_MAPKEY_H diff --git a/leveldb/src/main/jni/qstr.h b/leveldb/src/main/jni/qstr.h deleted file mode 100644 index 5f436712..00000000 --- a/leveldb/src/main/jni/qstr.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Created by barco on 2018/3/24. -// - -#ifndef CONVARTER_QSTR_H -#define CONVARTER_QSTR_H - -typedef struct { - unsigned int length; - char *str; -} qstr; - -typedef struct { - unsigned int length; - const char *str; -} qcstr; - -typedef struct { - unsigned int length; - unsigned char *str; -} qustr; - -#endif //CONVARTER_QSTR_H diff --git a/leveldb/src/main/jni/subchunk.cc b/leveldb/src/main/jni/subchunk.cc deleted file mode 100644 index 9d92a464..00000000 --- a/leveldb/src/main/jni/subchunk.cc +++ /dev/null @@ -1,163 +0,0 @@ -// -// Created by barco on 2018/12/26. -// - -#include -#include -#include -#include -#include - -//////////////// -// - -char SubChunk::pattern_name[] = {0x0a, 0x00, 0x00, 0x08, 0x04, 0x00, 'n', 'a', 'm', - 'e'}; -char SubChunk::pattern_val[] = {0x02, 0x03, 0x00, 'v', 'a', 'l'}; - -const int32_t SubChunk::msk[] = {0b1, 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, - 0b11111111, - 0b111111111, 0b1111111111, 0b11111111111, - 0b111111111111, - 0b1111111111111, 0b11111111111111, 0b11111111111111}; - -SubChunk::SubChunk(const std::string &buf, bool hasMultiStorage) { - - storages[0].storage = nullptr; - storages[0].palette = nullptr; - - const char *cbuf = buf.c_str(); - const char *ptr = cbuf + 1; - if (hasMultiStorage) { - ptr++; - loadStorage(ptr, cbuf + buf.length(), 0); - } else { - loadStorage(ptr, cbuf + buf.length(), 0); - } -} - -SubChunk::~SubChunk() { - delete[] storages[0].storage; - delete[] storages[0].palette; -} - -const char *SubChunk::loadStorage(const char *ptr, const char *max, int which) { - -#ifdef DEBUG_SUBCHUNK - //lowest bit should be 0. - if (((*ptr) & 1) != 0 || max - ptr < 2) { - // - } -#endif - - //Length of each block, in bits. - storages[which].blen = static_cast(*ptr >> 1); - - ptr++; - - //How many uint32s do it need to store all blocks? - div_t res = div(4096, (32 / storages[which].blen)); - size_t bufsize = static_cast(res.quot); - if (res.rem != 0)bufsize++; - -#ifdef DEBUG_SUBCHUNK - if (max - ptr < (bufsize << 2)) { - // - } -#endif - - //Copy 'em up. bufsize is 4-bytes long and memcpy requires count of bytes so x4. - storages[which].storage = new uint32_t[bufsize]; - memcpy(storages[which].storage, ptr, bufsize << 2); - - //Move the pointer to the end of uint32s. - ptr += bufsize << 2; - - //Here records how many types of blocks are in this subchunk. - storages[which].types = *(uint16_t *) ptr; - ptr += 4; - - storages[which].palette = new uint16_t[storages[which].types]; - - for (uint16_t i = 0; i < storages[which].types; i++) { -#ifdef DEBUG_SUBCHUNK - if (max - ptr < sizeof(pattern_name)) { - // - } - if (memcmp(ptr, pattern_name, sizeof(pattern_name)) != 0) { - //Something has gone wrong. - } -#endif - ptr += sizeof(pattern_name); - qstr name; -#ifdef DEBUG_SUBCHUNK - if (max - ptr < 2) { - // - } -#endif - name.length = *(uint16_t *) ptr; - ptr += 2; -#ifdef DEBUG_SUBCHUNK - if (max - ptr < name.length) { - // - } -#endif - name.str = new char[name.length]; - memcpy(name.str, ptr, name.length); - ptr += name.length; -#ifdef DEBUG_SUBCHUNK - if (max - ptr < sizeof(pattern_val) + 3) { - // - } - if (memcmp(ptr, pattern_val, sizeof(pattern_val)) != 0) { - // - } -#endif - ptr += sizeof(pattern_val); - storages[which].palette[i] = BlockNames::resolve(name); - delete[] name.str; - storages[which].palette[i] <<= 8; - storages[which].palette[i] |= *ptr; - ptr += 3; - } - return ptr; -} - -uint16_t -SubChunk::getBlockCode(unsigned char x, unsigned char y, unsigned char z, uint8_t which) { - - //If there's only one storage than getBlockCode or other storages can just return 0. - if (which == 0 && storages[which].storage == nullptr)return 0; - - BlockStorage &thiz = storages[which]; - - //Get the index among all blocks. - int index = x; - index <<= 4; - index |= z; - index <<= 4; - index |= y; - - //How many blox can each stick hold. - int capa = (32 / thiz.blen); - - //Stick that hold this block. - uint32_t stick = *(thiz.storage + (index / capa)); - - //The bits for this block is index in palette. - //No need care about endian but not very efficiency, i guess? - uint32_t ind = (stick >> (index % capa * thiz.blen)) & msk[thiz.blen - 1]; - - //Return the record. - return thiz.palette[ind]; -} - -uint16_t SubChunk::getBlock(unsigned char x, unsigned char y, unsigned char z) { - return getBlockCode(x, y, z, 0); -} - -uint16_t SubChunk::getBlock3(unsigned char x, unsigned char y, unsigned char z, - unsigned char layer) { - if (layer != 0 && layer != 1)return 0; - return getBlockCode(x, y, z, layer); -} diff --git a/leveldb/src/main/jni/subchunk.h b/leveldb/src/main/jni/subchunk.h deleted file mode 100644 index 887a3e88..00000000 --- a/leveldb/src/main/jni/subchunk.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// Created by barco on 2018/12/24. -// - -#ifndef CONVARTER_SUBCHUNK_H -#define CONVARTER_SUBCHUNK_H - -#include -#include -#include - -#define DEBUG_SUBCHUNK - -struct BlockStorage { - uint8_t blen;//Length per block - uint16_t types;//Types of blocks - uint16_t *palette;//Palette - uint32_t *storage;//Storage -}; - -class SubChunk { -private: - - static const int32_t msk[15]; - - static char pattern_name[10]; - - static char pattern_val[6]; - - BlockStorage storages[1]; - - const char *loadStorage(const char *ptr, const char *max, int which); - - uint16_t getBlockCode(unsigned char x, unsigned char y, unsigned char z, uint8_t which); - -public: - SubChunk(const std::string &buf, bool hasMultiStorage); - - ~SubChunk(); - - uint16_t getBlock(unsigned char x, unsigned char y, unsigned char z); - - uint16_t - getBlock3(unsigned char x, unsigned char y, unsigned char z, unsigned char layer); - -}; - -#endif //CONVARTER_SUBCHUNK_H From 8150fc95be707e55e9b801f6b9d01d4665e59c72 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Sat, 19 Jan 2019 16:31:25 +0800 Subject: [PATCH 13/83] + Use hardware acce for all renderers --- .../map/renderer/BiomeRenderer.java | 18 +++++++++++----- .../map/renderer/BlockLightRenderer.java | 21 +++++++++++-------- .../map/renderer/CaveRenderer.java | 13 ++++++------ .../map/renderer/GrassRenderer.java | 12 ++++++----- .../map/renderer/HeightmapRenderer.java | 12 ++++++----- .../map/renderer/NetherRenderer.java | 12 ++++++----- .../map/renderer/SlimeChunkRenderer.java | 12 ++++++----- .../map/renderer/XRayRenderer.java | 14 ++++++------- 8 files changed, 67 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java index f4d5ea87..dee73031 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java @@ -1,6 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; @@ -46,6 +49,9 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int x, z, biomeID, color, i, j, tX, tY; Biome biome; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); + for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -55,11 +61,13 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in color = biome == null ? 0xff000000 : (biome.color.red << 16) | (biome.color.green << 8) | (biome.color.blue) | 0xff000000; - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } +// for (i = 0; i < pL; i++) { +// for (j = 0; j < pW; j++) { +// bm.setPixel(tX + j, tY + i, color); +// } +// } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java index c87e9432..87d188e7 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java @@ -1,6 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; @@ -38,10 +41,12 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int x, y, z, subChunk, color, i, j, tX, tY; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); //render width in blocks - int rW = 16 - 0; - int[] light = new int[rW * (16 - 0)]; + int rW = 16; + int[] light = new int[rW * 16]; for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { TerrainChunkData data = chunk.getTerrain((byte) subChunk); @@ -50,7 +55,7 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in for (z = 0; z < 16; z++) { for (x = 0; x < 16; x++) { for (y = 0; y < cVersion.subChunkHeight; y++) { - light[((z - 0) * rW) + (x - 0)] += data.getBlockLightValue(x, y, z) & 0xff; + light[(z * rW) + x] += data.getBlockLightValue(x, y, z) & 0xff; } } } @@ -60,16 +65,14 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { - l = light[((z - 0) * rW) + (x - 0)]; + l = light[(z * rW) + x]; l = l < 0 ? 0 : ((l > 0xff) ? 0xff : l); color = (l << 16) | (l << 8) | (l) | 0xff000000; - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java index d176822e..6587780a 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java @@ -2,6 +2,9 @@ import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; @@ -42,6 +45,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int id, meta, cavyness, layers, offset; Block block; int x, y, z, subChunk, color, i, j, tX, tY, r, g, b; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData floorData = chunk.getTerrain((byte) 0); @@ -163,12 +168,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in color = (r << 16) | (g << 8) | b | 0xff000000; - - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java index 838feed0..d7d6138c 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java @@ -1,6 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; @@ -43,17 +46,16 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int x, z, color, i, j, tX, tY; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { color = ((data.getGrassR(x, z) & 0xff) << 16) | ((data.getGrassG(x, z) & 0xff) << 8) | (data.getGrassB(x, z) & 0xff) | 0xff000000; - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java index e65b747b..30609cc8 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java @@ -1,6 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; @@ -49,6 +52,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in north = dataN != null && dataN.load2DData(); int x, y, z, color, i, j, tX, tY; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); int yW, yN; int r, g, b; float yNorm, yNorm2, heightShading; @@ -83,11 +88,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in color = (r << 16) | (g << 8) | b | 0xff000000; - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java index e8f9f4b2..77a2ff3e 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java @@ -2,6 +2,9 @@ import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.Log; @@ -65,6 +68,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int stop; int subChunk; int stopSubChunk; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -234,11 +239,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in color = (r << 16) | (g << 8) | b | 0xff000000; - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java index 2f040cb2..b416add0 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java @@ -1,6 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; @@ -33,6 +36,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in boolean isSlimeChunk = isSlimeChunk(chunkX, chunkZ); int color, r, g, b, avg; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); //make slimeChunks much more green for (z = 0, tY = pY; z < 16; z++, tY += pL) { @@ -50,11 +55,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } color = (color & 0xFF000000) | (r << 16) | (g << 8) | b; - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java index e878cf96..f6cd846f 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java @@ -1,6 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; @@ -59,6 +62,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int average; int r, g, b; + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); int subChunk; for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { @@ -125,13 +130,8 @@ else if (block == Block.B_56_0_DIAMOND_ORE) { color = (r << 16) | (g << 8) | (b) | 0xff000000; } - - - for (i = 0; i < pL; i++) { - for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, color); - } - } + paint.setColor(color); + canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } From 4c6989a2f31fa84171241d50b90205551079023b Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Sat, 19 Jan 2019 16:32:44 +0800 Subject: [PATCH 14/83] * version updated to 1.8 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index b252b39a..497490e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion 16 targetSdkVersion 28 versionCode 10 - versionName "1.7" + versionName "1.8" } buildTypes { From 1273fa1ded755d96c5e79d8716de7babc8e1475c Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Sun, 20 Jan 2019 20:19:11 +0800 Subject: [PATCH 15/83] * Code clean up + Chinese (zh-cn) support added --- README.md | 4 + .../blocktopograph/MenuHelper.java | 10 +- .../blocktopograph/map/MCTileProvider.java | 35 ++- .../map/renderer/BiomeRenderer.java | 30 +- .../map/renderer/BlockLightRenderer.java | 44 +-- .../map/renderer/CaveRenderer.java | 44 ++- .../map/renderer/ChessPatternRenderer.java | 31 +-- .../map/renderer/DebugRenderer.java | 33 ++- .../map/renderer/GrassRenderer.java | 41 +-- .../map/renderer/HeightmapRenderer.java | 43 ++- .../map/renderer/MapRenderer.java | 14 +- .../map/renderer/NetherRenderer.java | 63 ++--- .../map/renderer/SatelliteRenderer.java | 45 ++- .../map/renderer/SlimeChunkRenderer.java | 51 ++-- .../map/renderer/XRayRenderer.java | 49 ++-- app/src/main/res/layout/map_fragment.xml | 125 +++++---- app/src/main/res/values-zh-rCN/strings.xml | 258 ++++++++++++++++++ app/src/main/res/values/strings.xml | 95 ++++--- 18 files changed, 621 insertions(+), 394 deletions(-) create mode 100644 app/src/main/res/values-zh-rCN/strings.xml diff --git a/README.md b/README.md index 3d22ba18..85429758 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ By [@protolambda](https://github.com/protolambda), [@MithrilMania](https://githu [flagmaggot](https://github.com/flagmaggot). This fork is the only one supporting MCPE 1.2~1.9 for now. +## Update paused +Until next change in Minecraft level format. +Pull requests will still be processed in time during the pause unless Meow Cat decays. + ## Download Google play download not available now. Download apk from [the release page](https://github.com/oO0oO0oO0o0o00/blocktopograph/releases). diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java b/app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java index 8880da71..1d6a39cf 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java @@ -3,6 +3,7 @@ import android.app.AlertDialog; import android.content.Context; import android.text.Html; +import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.view.MenuItem; import android.widget.TextView; @@ -27,8 +28,9 @@ public static boolean onOptionsItemSelected(MenuContext menuContext, MenuItem it AlertDialog.Builder builder = new AlertDialog.Builder(ctx); TextView msg = new TextView(ctx); + msg.setEllipsize(TextUtils.TruncateAt.MARQUEE); float dpi = ctx.getResources().getDisplayMetrics().density; - msg.setPadding((int)(19*dpi), (int)(5*dpi), (int)(14*dpi), (int)(5*dpi)); + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); msg.setMaxLines(20); msg.setMovementMethod(LinkMovementMethod.getInstance()); msg.setText(R.string.app_about); @@ -44,7 +46,7 @@ public static boolean onOptionsItemSelected(MenuContext menuContext, MenuItem it AlertDialog.Builder builder = new AlertDialog.Builder(ctx); TextView msg = new TextView(ctx); float dpi = ctx.getResources().getDisplayMetrics().density; - msg.setPadding((int)(19*dpi), (int)(5*dpi), (int)(14*dpi), (int)(5*dpi)); + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); msg.setMaxLines(20); msg.setMovementMethod(LinkMovementMethod.getInstance()); msg.setText(R.string.app_help); @@ -60,7 +62,7 @@ public static boolean onOptionsItemSelected(MenuContext menuContext, MenuItem it AlertDialog.Builder builder = new AlertDialog.Builder(ctx); TextView msg = new TextView(ctx); float dpi = ctx.getResources().getDisplayMetrics().density; - msg.setPadding((int)(19*dpi), (int)(5*dpi), (int)(14*dpi), (int)(5*dpi)); + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); msg.setMaxLines(20); msg.setMovementMethod(LinkMovementMethod.getInstance()); String content = String.format(ctx.getResources().getString(R.string.app_changelog), BuildConfig.VERSION_NAME); @@ -79,5 +81,5 @@ public static boolean onOptionsItemSelected(MenuContext menuContext, MenuItem it } } } - + } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java index 00ccc9bf..77a2d502 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java @@ -10,7 +10,9 @@ import android.text.TextPaint; import com.mithrilmania.blocktopograph.WorldActivityInterface; +import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; +import com.mithrilmania.blocktopograph.chunk.Version; import com.mithrilmania.blocktopograph.map.renderer.MapType; import com.qozix.tileview.graphics.BitmapProvider; import com.qozix.tileview.tiles.Tile; @@ -120,25 +122,38 @@ public Bitmap getBitmap(Tile tile, Context context) { int pixelsPerChunkW = pixelsPerBlockW * 16; int pixelsPerChunkL = pixelsPerBlockL * 16; - for (z = minChunkZ, pY = 0; z < maxChunkZ; z++, pY += pixelsPerChunkL) { + ChunkManager chunkManager = mChunkManager.get(); + Canvas canvas = new Canvas(bm); + Paint paint = new Paint(); + + for (z = minChunkZ, pY = 0; z < maxChunkZ; z++, pY += pixelsPerChunkL) for (x = minChunkX, pX = 0; x < maxChunkX; x++, pX += pixelsPerChunkW) { + Chunk chunk = chunkManager.getChunk(x, z, dimension); try { - mapType.renderer.renderToBitmap(mChunkManager.get(), bm, dimension, - x, z, - pX, pY, - pixelsPerBlockW, pixelsPerBlockL); + + Version cVersion = chunk.getVersion(); + if (cVersion == Version.ERROR) { + MapType.ERROR.renderer.renderToBitmap(chunk, canvas, dimension, + x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, cVersion, chunkManager); + continue; + } + MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, + x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, cVersion, chunkManager); + + mapType.renderer.renderToBitmap(chunk, canvas, dimension, x, z, + pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, cVersion, chunkManager); + } catch (Exception e) { - MapType.ERROR.renderer.renderToBitmap(mChunkManager.get(), bm, dimension, - x, z, - pX, pY, - pixelsPerBlockW, pixelsPerBlockL); + + MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, + x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, null, chunkManager); e.printStackTrace(); + } } - } //load all those markers with an async task, this task publishes its progress, diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java index dee73031..7c279c26 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java @@ -1,6 +1,5 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -18,8 +17,8 @@ public class BiomeRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to * @param dimension Mapped dimension * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) @@ -27,30 +26,21 @@ public class BiomeRenderer implements MapRenderer { * @param pY texture Y pixel coord to start rendering to * @param pW width (X) of one block in pixels * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + int x, z, biomeID, color, i, j, tX, tY; + Biome biome; //the bottom sub-chunk is sufficient to get biome data. TerrainChunkData data = chunk.getTerrain((byte) 0); if (data == null || !data.load2DData()) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - - - int x, z, biomeID, color, i, j, tX, tY; - Biome biome; - - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); + throw new RuntimeException(); for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -72,8 +62,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - - return bm; } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java index 87d188e7..085d4f76 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java @@ -1,6 +1,5 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -17,44 +16,36 @@ public class BlockLightRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, y, z, subChunk, color, i, j, tX, tY; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); //render width in blocks int rW = 16; int[] light = new int[rW * 16]; - for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { + for (subChunk = 0; subChunk < version.subChunks; subChunk++) { TerrainChunkData data = chunk.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) break; for (z = 0; z < 16; z++) { for (x = 0; x < 16; x++) { - for (y = 0; y < cVersion.subChunkHeight; y++) { + for (y = 0; y < version.subChunkHeight; y++) { light[(z * rW) + x] += data.getBlockLightValue(x, y, z) & 0xff; } } @@ -77,11 +68,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - - if (subChunk == 0) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - - return bm; } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java index 6587780a..50965301 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java @@ -1,7 +1,6 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -19,39 +18,32 @@ public class CaveRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { boolean solid, intoSurface; int id, meta, cavyness, layers, offset; Block block; int x, y, z, subChunk, color, i, j, tX, tY, r, g, b; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData floorData = chunk.getTerrain((byte) 0); if (floorData == null || !floorData.load2DData()) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + throw new RuntimeException(); TerrainChunkData data; @@ -64,8 +56,8 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in cavyness = 0; layers = 0; y = floorData.getHeightMapValue(x, z); - offset = y % cVersion.subChunkHeight; - subChunk = y / cVersion.subChunkHeight; + offset = y % version.subChunkHeight; + subChunk = y / version.subChunkHeight; /* while (cavefloor > 0) { @@ -88,7 +80,7 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in data = chunk.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) { //start at the top of the next chunk! (current offset might differ) - offset = cVersion.subChunkHeight - 1; + offset = version.subChunkHeight - 1; continue; } @@ -173,8 +165,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - - return bm; } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java index 188e9d53..e7c39028 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java @@ -1,10 +1,10 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; import com.mithrilmania.blocktopograph.map.Dimension; @@ -22,26 +22,26 @@ public class ChessPatternRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, z, tX, tY; int color; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); - for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { color = ((x + z) & 1) == 1 ? darkShade : lightShade; @@ -50,9 +50,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in //This would get hardware acceleration. } } - - - return bm; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java index 30f9604e..41831629 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java @@ -1,7 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; import com.mithrilmania.blocktopograph.map.Dimension; @@ -12,19 +14,22 @@ public class DebugRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, z, i, j, tX, tY; @@ -35,14 +40,12 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in for (x = 0, tX = pX; x < 16; x++, tX += pW) { for (i = 0; i < pL; i++) { for (j = 0; j < pW; j++) { - bm.setPixel(tX + j, tY + i, 0xff000000 | ((offsetX + x) ^ (offsetZ + z))); + paint.setColor(0xff000000 | ((offsetX + x) ^ (offsetZ + z))); + canvas.drawPoint(tX + j, tY + i, paint); } } } } - - - return bm; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java index d7d6138c..0cddffe5 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java @@ -1,6 +1,5 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -17,37 +16,29 @@ public class GrassRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get grass data. TerrainChunkData data = chunk.getTerrain((byte) 0); if (data == null || !data.load2DData()) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + throw new RuntimeException(); - - int x, z, color, i, j, tX, tY; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); + int x, z, color, tX, tY; for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -60,8 +51,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - - return bm; } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java index 30609cc8..eac7c6d1 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java @@ -1,6 +1,5 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -17,43 +16,35 @@ public class HeightmapRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData data = chunk.getTerrain((byte) 0); if (data == null || !data.load2DData()) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + throw new RuntimeException(); - - TerrainChunkData dataW = cm.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); - TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); + TerrainChunkData dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); + TerrainChunkData dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); boolean west = dataW != null && dataW.load2DData(), north = dataN != null && dataN.load2DData(); int x, y, z, color, i, j, tX, tY; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); int yW, yN; int r, g, b; float yNorm, yNorm2, heightShading; @@ -94,8 +85,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - - return bm; } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java index e1e537bb..4ed0c276 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java @@ -1,8 +1,9 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; -import com.mithrilmania.blocktopograph.chunk.ChunkData; +import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; import com.mithrilmania.blocktopograph.map.Dimension; @@ -21,8 +22,8 @@ will just make things worse on some (most?) phones. /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to * @param dimension Mapped dimension * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) @@ -30,9 +31,12 @@ will just make things worse on some (most?) phones. * @param pY texture Y pixel coord to start rendering to * @param pW width (X) of one block in pixels * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException; + void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java index 77a2ff3e..89564b2e 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java @@ -1,7 +1,6 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -21,36 +20,30 @@ public class NetherRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //bottom chunk must be present TerrainChunkData floorData = chunk.getTerrain((byte) 0); if (floorData == null || !floorData.load2DData()) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + throw new RuntimeException(); - - Chunk chunkW = cm.getChunk(chunkX - 1, chunkZ, dimension); - Chunk chunkN = cm.getChunk(chunkX, chunkZ - 1, dimension); + Chunk chunkW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension); + Chunk chunkN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension); TerrainChunkData data; @@ -68,8 +61,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int stop; int subChunk; int stopSubChunk; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -95,11 +86,11 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in heightShading = SatelliteRenderer.getHeightShading(cavefloor, cavefloorW, cavefloorN); y = cavefloor + 1; - data = chunk.getTerrain((byte) (y / cVersion.subChunkHeight)); + data = chunk.getTerrain((byte) (y / version.subChunkHeight)); //light sources lightValue = (data != null && data.loadTerrain()) - ? data.getBlockLightValue(x, y % cVersion.subChunkHeight, z) + ? data.getBlockLightValue(x, y % version.subChunkHeight, z) : 0; //check if it is supported, default to full brightness to not lose details. @@ -121,20 +112,20 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in a = 1f; - offset = caveceil % cVersion.subChunkHeight; + offset = caveceil % version.subChunkHeight; stop = 0; - subChunk = caveceil / cVersion.subChunkHeight; - stopSubChunk = caveceil / cVersion.subChunkHeight; + subChunk = caveceil / version.subChunkHeight; + stopSubChunk = caveceil / version.subChunkHeight; subChunkLoop: for (; subChunk >= stopSubChunk; subChunk--) { - if (subChunk == stopSubChunk) stop = cavefloor % cVersion.subChunkHeight; + if (subChunk == stopSubChunk) stop = cavefloor % version.subChunkHeight; data = chunk.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) { //start at the top of the next chunk! (current offset might differ) - offset = cVersion.subChunkHeight - 1; + offset = version.subChunkHeight - 1; continue; } @@ -178,7 +169,7 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } //start at the top of the next chunk! (current offset might differ) - offset = cVersion.subChunkHeight - 1; + offset = version.subChunkHeight - 1; } @@ -199,11 +190,11 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in subChunkLoop: - for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { + for (subChunk = 0; subChunk < version.subChunks; subChunk++) { data = chunk.getTerrain((byte) subChunk); if (data == null || data.loadTerrain()) break; - for (y = 0; y < cVersion.subChunkHeight; y++) { + for (y = 0; y < version.subChunkHeight; y++) { //some x-ray for important stuff like portals switch (data.getBlockTypeId(x, y, z)) { @@ -243,8 +234,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); } } - - return bm; } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java index e679a281..25855249 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java @@ -1,6 +1,5 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -20,43 +19,36 @@ public class SatelliteRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData data = chunk.getTerrain((byte) 0); if (data == null || !data.load2DData()) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + throw new RuntimeException(); - - TerrainChunkData dataW = cm.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); - TerrainChunkData dataN = cm.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); + TerrainChunkData dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); + TerrainChunkData dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); boolean west = dataW != null && dataW.load2DData(), north = dataN != null && dataN.load2DData(); int x, y, z, color, i, j, tX, tY; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); + for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -75,11 +67,10 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - return bm; } //calculate color of one column - private static int getColumnColour(Chunk chunk, TerrainChunkData floorData, int x, int y, int z, int heightW, int heightN) throws Version.VersionException { + public static int getColumnColour(Chunk chunk, TerrainChunkData floorData, int x, int y, int z, int heightW, int heightN) throws Version.VersionException { float a = 1f; float r = 0f; float g = 0f; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java index b416add0..ccc683f3 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java @@ -1,12 +1,13 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; +import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; import com.mithrilmania.blocktopograph.util.MTwister; @@ -16,33 +17,52 @@ public class SlimeChunkRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, z, i, j, tX, tY; + //the bottom sub-chunk is sufficient to get heightmap data. + TerrainChunkData data = chunk.getTerrain((byte) 0); + if (data == null || !data.load2DData()) + throw new RuntimeException(); - MapType.OVERWORLD_SATELLITE.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + TerrainChunkData dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); + TerrainChunkData dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); + + boolean west = dataW != null && dataW.load2DData(), + north = dataN != null && dataN.load2DData(); + + //MapType.OVERWORLD_SATELLITE.renderer.renderToBitmap(chunk, canvas, dimension, chunkX, chunkZ, pX, pY, pW, pL, paint, version, chunkManager); boolean isSlimeChunk = isSlimeChunk(chunkX, chunkZ); int color, r, g, b, avg; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); //make slimeChunks much more green for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { - color = bm.getPixel(tX, tY); + + int y = data.getHeightMapValue(x, z); + + color = SatelliteRenderer.getColumnColour(chunk, data, x, y, z, + (x == 0) ? (west ? dataW.getHeightMapValue(dimension.chunkW - 1, z) : y)//chunk edge + : data.getHeightMapValue(x - 1, z),//within chunk + (z == 0) ? (north ? dataN.getHeightMapValue(x, dimension.chunkL - 1) : y)//chunk edge + : data.getHeightMapValue(x, z - 1)//within chunk + ); r = (color >> 16) & 0xff; g = (color >> 8) & 0xff; b = color & 0xff; @@ -61,7 +81,6 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in } } - return bm; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java index f6cd846f..ecc6d9e9 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java @@ -1,6 +1,5 @@ package com.mithrilmania.blocktopograph.map.renderer; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -22,32 +21,26 @@ public class XRayRenderer implements MapRenderer { /** * Render a single chunk to provided bitmap (bm) * - * @param cm ChunkManager, provides chunks, which provide chunk-data - * @param bm Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels + * @param chunk ChunkManager, provides chunks, which provide chunk-data + * @param canvas Bitmap to render to + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint + * @param version + * @param chunkManager * @return bm is returned back * @throws Version.VersionException when the version of the chunk is unsupported. */ - public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL) throws Version.VersionException { - - Chunk chunk = cm.getChunk(chunkX, chunkZ, dimension); - Version cVersion = chunk.getVersion(); - - if (cVersion == Version.ERROR) - return MapType.ERROR.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); - if (cVersion == Version.NULL) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get heightmap data. TerrainChunkData data; - int x, y, z, color, i, j, tX, tY; //render width in blocks @@ -62,21 +55,19 @@ public Bitmap renderToBitmap(ChunkManager cm, Bitmap bm, Dimension dimension, in int average; int r, g, b; - Canvas canvas = new Canvas(bm); - Paint paint = new Paint(); int subChunk; - for (subChunk = 0; subChunk < cVersion.subChunks; subChunk++) { + for (subChunk = 0; subChunk < version.subChunks; subChunk++) { data = chunk.getTerrain((byte) subChunk); if (data == null || !data.loadTerrain()) break; for (z = 0; z < 16; z++) { for (x = 0; x < 16; x++) { - for (y = 0; y < cVersion.subChunkHeight; y++) { + for (y = 0; y < version.subChunkHeight; y++) { block = Block.getBlock(data.getBlockTypeId(x, y, z) & 0xff, 0); - index2D = ((z - 0) * rW) + (x - 0); + index2D = (z * rW) + x; if (block == null || block.id <= 1) continue; else if (block == Block.B_56_0_DIAMOND_ORE) { @@ -101,8 +92,10 @@ else if (block == Block.B_56_0_DIAMOND_ORE) { } } - if (subChunk == 0) - return MapType.CHESS.renderer.renderToBitmap(cm, bm, dimension, chunkX, chunkZ, pX, pY, pW, pL); + if (subChunk == 0) { + MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, chunkX, chunkZ, pX, pY, pW, pL, paint, version, chunkManager); + return; + } for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -136,8 +129,6 @@ else if (block == Block.B_56_0_DIAMOND_ORE) { } } - - return bm; } } diff --git a/app/src/main/res/layout/map_fragment.xml b/app/src/main/res/layout/map_fragment.xml index 687253d0..f540fd37 100644 --- a/app/src/main/res/layout/map_fragment.xml +++ b/app/src/main/res/layout/map_fragment.xml @@ -9,94 +9,93 @@ - - + android:layout_height="match_parent"> + android:shadowRadius="2.0" + android:text="@string/map_water_mark" + android:textColor="@color/waterMark" + android:textStyle="normal" /> + + android:orientation="vertical" + tools:ignore="RtlHardcoded"> - + + + - - - - - - - - - - - - - + android:src="@drawable/ic_gps_fixed" + fab:fab_label="@string/go_to_coordinate" /> + + + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..ddb823cb --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,258 @@ + + + 由@protolambda制作 + 选择世界 + 世界 + 世界 + 打开侧边栏 + 关闭侧边栏 + 设置 + 关于 + 帮助 + 更新日志 + "不能打开世界:没有存储读/写权限。 + 尝试重启应用,或者在应用设置/手机管家开启本应用的存储权限。" + 没有自定义标记。在地图上长按创建。 + 还没有自定义标记。 + 查看本应用的文档(长期未更新): + + blocktopograph.protolambda.com/help + . + 种子: + + 本应用由@protolambda原创, + 由@MithrilMania、 + @flagmaggot、 + @MeowCat参与更新和维护。 + 在等待前任作者回归(或确认失踪)前,暂时从Github发布更新,您可以 + 打开发布页面 + 下载最新的apk安装包,需要从浏览器勾选“使用电脑版网页”或用电脑打开,手机默认看不到下载。 + 有账号的可以点一下Star~ 以后可能恢复咕狗商店上架,谁知道呢。 + 欢迎在Github查看源代码,并参与更新维护(包括增强翻译):\n + github.com/oO0oO0oO0o0o00/blocktopograph + \n使用前请备份你的重要存档,以免发生意外。一点小问题都可能导致Minecraft无法加载整个存档→_→ + \n本应用支持MCPE 0.14至1.9(当前)的存档格式,更旧或者更新的版本【可能】不完全兼容,但不妨一试。 + \n原作者网站:blocktopograph.protolambda.com + 这会是第二任作者的网站吗?或者只是重名mithrilmania.fc2web.com/ + Disclaimer\n + This app is not affiliated with Minecraft, Mojang AB or Microsoft.\n + This is not an official but a fan made app.\n + Minecraft is a trademark of Mojang AB.\n + Please note:\n + • NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG.\n + • THE APP IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND.\n + \nUsed libraries\n + • FloatingActionButton \n + • + Tileview, fork by mithrilmania \n + • leveldb-mcpe \n + with + android-leveldb, fork by mithrilmania \n + \n + + + 版本: %1$s +
+ 2018之前的更新记录可以到: + + blocktopograph.protolambda.com/changelog + 查看。]]> +
+ + 标记图标:\n + • default_marker, blue_marker, green_marker, red_marker \n + • Entity data-names (case sensitive) \n + • Block data-names (case sensitive) \n + + 已刷新世界列表。 + 找不到任何世界。 + 没法读/写世界。 + 输入文件路径... + 从外部文件打开世界 + 打开 + + 您使用的路径:%1$s\n + \n + 已经从%2$s + 搜索包含Minecraft世界的文件夹。 + + 没找到您输入的文件或者文件夹 + + 文件路径(可以以“/level.dat”结尾)不是文件夹。 + + 没找到level.dat文件 + 打开世界时发生错误。 + 关闭世界? + 输入用于levelDB的关键码... + 从数据库打开NBT数据 + 无效关键码。 + 根据该名称,没有找到数据库项。 + + 不能从该世界的数据库文件读取%1$s\"。 + + 没找到联机玩家的数据。 + 选择玩家 + 打开NBT数据 + 无法从数据库打开玩家的数据项。关键码: + + 当前Minecraft世界无法打开。 + 可以尝试从Minecraft退出该世界,然后重启应用。 + + 关闭世界? + 关闭NBT编辑窗口? + 传送单机玩家 + 创建自定义标记 + 移除标记 + 打开该区块的实体(entity)的NBT数据 + 打开该区块的方块实体(Tile entity)的NBT数据 + 该区块的实体数据 + 该区块的方块实体数据 + 该区块的NBT数据 + + %1$s + ~ %2$s + (%3$d; + %4$d; + %5$d) + [%6$s]. + + + 已传送玩家到(%1$.2f; + %2$.2f; + %3$.2f) + [%4$s]. + + 传送玩家失败。 + 找不到或者不能编辑本地玩家数据。 + 我的自定义标记 + 无效的标记名字 + 无效的图标名称 + X坐标无效 + Y坐标无效 + Z坐标无效 + 错误:创建标记失败。 + 1 + NBT编辑器 + 1 + 正在创建该区块的NBT数据... + 已完成~ + 创建或保存失败 + 错误:无法打开世界详情。 + 错误:无法打开世界。 + 没有有效的关卡数据(可能指level.dat?)。 + 名字: + 大小: + 模式: + 上次游玩: + 种子: + 文件路径: + 加载世界... + + “%1$s”无效 + + 取消 + 复制 + 粘贴(覆盖该标签) + 粘贴(作为该标签的子标签) + 删除 + 重命名 + 添加子标签 + 添加新NBT标签 + 作为子标签粘贴 + 移除所有NBT标签 + 不能修改NBT根标签。 + NBT根标签选项 + 输入标签名字... + 创建NBT标签 + 创建 + 剪贴板没有东西... + 真要删除所有NBT标签? + " 删 ↑ 除 ↓ " + 错误:修改NBT数据失败。 + NBT标签选项 + 错误:剪贴板中的标签与当前标签的子标签冲突。 + 错误:不能覆盖标签:上层标签类型未知。 + 错误:不能在空标签里覆盖。 + 错误:不能作为子标签粘贴:上层标签类型未知。 + 错误:不能从空列表删除标签。 + 重命名NBT标签 + 重命名 + 错误:上级标签已经包含相同的子标签。 + 错误:该组合标签已包含相同的关键码。 + 错误:只能在组合标签或列表标签添加子标签。 + 没有需要保存的。 + 保存? + 前往标记位置 + 找不到玩家 + 找不到出生点 + 玩家位置 + 出生点 + + %1$s在: + (%2$.2f; + %3$.2f; + %4$.2f) + [%5$s]. + + %1$s在: + (%2$d; + %3$d; + %4$d) + [%5$s]. + + 没有有效的地图。 + 寻找玩家... + 提取玩家数据失败。 + 前往玩家位置 + 前往单机玩家位置 + 前往其他玩家位置 + 走! + 前往坐标 + 已传送玩家到: + 这个标记不该被移除。 + 子区块 + 选择一个子区块(0至15): + 筛选标记 + 显示地图 + 选择世界 + 单机玩家的NBT数据 + 联机玩家的NBT数据 + 世界NBT数据 + 主世界 + 俯瞰图 + 洞穴 + 史莱姆区块 + 高度图 + 生态群系图 + 植被颜色 + 透视 + 光照强度 + 地狱 + 地图 + 末地 + 地图选项 + 开/关网格显示 + 开/关标记显示 + 高级选项 + 生物群系NBT数据 + 主世界NBT数据 + 村庄NBT数据 + 传送门NBT数据 + 维度0NBT数据 + 维度1NBT数据 + 维度2NBT数据 + 自主实体NBT数据 + 按名称打开NBT数据 + 生存 + 创造 + 探险 + + 位置:(%1$.2f; + %2$.2f) + 区块:(%3$d; + %4$d) + [%5$s]. + + 传送! + 前往出生点 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d648f9c0..872206de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - Blocktopograph + Blocktopograph By @protolambda @@ -27,7 +27,19 @@ And then check if you can find it here! - This app is created by @protolambda. \n + This app is created by @protolambda, + contributed by + @MithrilMania, + @flagmaggot, + @MeowCat and others as well.\n + Releases would be published to + this page, + you need to turn \"request desktop version\" on your browser, or use a PC, otherwise + apk downloads won\'t be shown. We may release on Google Play in the future. + Current active github repository: + github.com/oO0oO0oO0o0o00/blocktopograph + Feel free to star or fork, help with the project or simply add your own language support. + All non-destructive pull requests will be accepted. More information (including the roadmap, changelog and help-information) can be found at: blocktopograph.protolambda.com. \n \n @@ -60,16 +72,16 @@ - The app documentation can be found at: + The app documentation can be found at (maybe out of date): blocktopograph.protolambda.com/help . - Version: %1$s + Version: %1$s
- Full changelog can be found at: + Full changelog can be found at (out of date): blocktopograph.protolambda.com/changelog ]]> @@ -78,7 +90,7 @@ World seed: - Blocktopograph + Blocktopograph Marker icons:\n @@ -95,11 +107,9 @@ Open world with custom path Open - Your path: %1$s\n + Your path: %1$s\n \n - Already searched for world folders in %2$s + Already searched for world folders in %2$s No file/folder found at path! That path (optionally suffixed with /level.dat) is not a directory! @@ -111,13 +121,13 @@ Invalid key name. Cannot find db entry with that name! - Failed to read \"%1$s\" + Failed to read \"%1$s\" from world database. No multiplayer data found. Select player Open NBT Failed to open player entry in DB. key: - %1$s + %1$s
The current Minecraft world could not be opened. Try restarting this app, and close the world in MCPE if it was open. @@ -132,17 +142,17 @@ Tile-entity chunk data NBT chunk data - %1$s - ~ %2$s - (%3$d; - %4$d; - %5$d) - [%6$s]. + %1$s + ~ %2$s + (%3$d; + %4$d; + %5$d) + [%6$s].
Teleported the player to: - (%1$.2f; - %2$.2f; - %3$.2f) - [%4$s]. + (%1$.2f; + %2$.2f; + %3$.2f) + [%4$s]. Failed to teleport player. Failed to find/edit local player data. @@ -154,7 +164,7 @@ Invalid marker Z coordinate! Error: failed to create marker. - Failed to load %1$s. + Failed to load %1$s. NBT editor @@ -166,7 +176,7 @@ Created and saved new NBT chunk data! Error: failed to create/save the new NBT chunk data. - dd/MM/yyyy hh:mm a + dd/MM/yyyy hh:mm a Error: could not open world details; lost track of world. Error: could not open world. @@ -181,7 +191,7 @@ Loading world… - \"%1$s\" is invalid! + \"%1$s\" is invalid! Cancel Copy Paste (overwrite) @@ -227,20 +237,22 @@ Failed to find player. Failed to find spawn. Go to a marker: + Go to local player + Go to other player Player Spawn - %1$s at: - (%2$.2f; - %3$.2f; - %4$.2f) - [%5$s]. + %1$s at: + (%2$.2f; + %3$.2f; + %4$.2f) + [%5$s]. - %1$s at: - (%2$d; - %3$d; - %4$d) - [%5$s]. + %1$s at: + (%2$d; + %3$d; + %4$d) + [%5$s]. No map available. Searching for players… Failed to retrieve player data. @@ -250,7 +262,7 @@ Teleported the player to: This marker is not meant to be removed! Sub-chunk - Choose a sub–chunk (0…255): + Choose a sub–chunk (0…): Filter markers Show map Select world @@ -290,11 +302,12 @@ Creative Adventure - Pos: (%1$.2f; - %2$.2f) - Chunk: (%3$d; - %4$d) - [%5$s]. + Pos: (%1$.2f; + %2$.2f) + Chunk: (%3$d; + %4$d) + [%5$s]. Teleport! + Go to spawn
From 81c954b46621596937ff897b94079097dd35b320 Mon Sep 17 00:00:00 2001 From: MiemieMethod <40489495+MiemieMethod@users.noreply.github.com> Date: Tue, 22 Jan 2019 16:21:19 +0800 Subject: [PATCH 16/83] Add files via upload Update Chinese Translation --- app/src/main/res/values-zh-rCN/strings.xml | 235 +++++++++++---------- 1 file changed, 122 insertions(+), 113 deletions(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ddb823cb..83afc2d0 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1,24 +1,28 @@ 由@protolambda制作 - 选择世界 + 请选择一个世界 世界 世界 - 打开侧边栏 - 关闭侧边栏 + 打开导航栏 + 关闭导航栏 设置 关于 帮助 更新日志 - "不能打开世界:没有存储读/写权限。 - 尝试重启应用,或者在应用设置/手机管家开启本应用的存储权限。" - 没有自定义标记。在地图上长按创建。 - 还没有自定义标记。 + "无法打开世界,没有存储的读/写权限! + 请尝试重启应用并接受对读/写权限的请求,或者在应用设置/手机管家中开启本应用的存储权限。" + 没有自定义标记! + 还没有自定义标记!\n + 你可以在地图上你想要的地方长按来创建一个标记, + 然后点击“创建自定义标记”。 + 然后选择一个标记图标和一个Y坐标。 + 最后在地图上检查一下你有没有成功创建它! 查看本应用的文档(长期未更新): blocktopograph.protolambda.com/help . - 种子: + 世界种子: 本应用由@protolambda原创, 由@MithrilMania、 @@ -34,20 +38,20 @@ \n本应用支持MCPE 0.14至1.9(当前)的存档格式,更旧或者更新的版本【可能】不完全兼容,但不妨一试。 \n原作者网站:blocktopograph.protolambda.com 这会是第二任作者的网站吗?或者只是重名mithrilmania.fc2web.com/ - Disclaimer\n - This app is not affiliated with Minecraft, Mojang AB or Microsoft.\n - This is not an official but a fan made app.\n - Minecraft is a trademark of Mojang AB.\n - Please note:\n - • NOT AN OFFICIAL MINECRAFT PRODUCT. NOT APPROVED BY OR ASSOCIATED WITH MOJANG.\n - • THE APP IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND.\n - \nUsed libraries\n + 免责声明\n + 此应用程序与Minecraft、Mojang AB或Microsoft无关。\n + 这不是一个官方的而是一个饭制的APP。\n + Minecraft是Mojang的一个商标。\n + 请注意:\n + • 【这不是一个官方的MINECRAFT产品。并未经MOJANG批准并且与其无关。】\n + • 【该APP“按原样”提供,不提供任何形式的保证。】\n + \n使用的库\n • FloatingActionButton \n • - Tileview, fork by mithrilmania \n + Tileview,mithrilmania的版本库分支 \n • leveldb-mcpe \n with - android-leveldb, fork by mithrilmania \n + android-leveldb,mithrilmania的版本库分支 \n \n @@ -60,54 +64,55 @@ 标记图标:\n - • default_marker, blue_marker, green_marker, red_marker \n - • Entity data-names (case sensitive) \n - • Block data-names (case sensitive) \n + • default_marker(默认标记)、blue_marker(蓝标记)、green_marker(绿标记)、red_marker(红标记)\n + • 实体的数据名(大小写敏感)\n + • 方块的数据名(大小写敏感)\n - 已刷新世界列表。 - 找不到任何世界。 - 没法读/写世界。 - 输入文件路径... - 从外部文件打开世界 + 已刷新世界列表 + 找不到任何世界! + 无法读/写世界 + 输入文件路径…… + 从自定义路径打开世界 打开 您使用的路径:%1$s\n \n - 已经从%2$s - 搜索包含Minecraft世界的文件夹。 + 已在%2$s + 路径中搜索包含Minecraft世界的文件夹。 - 没找到您输入的文件或者文件夹 + 在您输入的路径中没有找到文件或者文件夹! - 文件路径(可以以“/level.dat”结尾)不是文件夹。 + 文件路径(可以以“/level.dat”结尾)不是文件夹! 没找到level.dat文件 - 打开世界时发生错误。 - 关闭世界? - 输入用于levelDB的关键码... - 从数据库打开NBT数据 - 无效关键码。 - 根据该名称,没有找到数据库项。 + 打开世界时发生错误 + 您想关闭该世界吗? + 输入levelDB中的键…… + 从数据库中打开NBT + 无效的键名 + 无法在数据库中找到具有该名字的记录! - 不能从该世界的数据库文件读取%1$s\"。 + 不能从该世界的数据库中读取%1$s\"。 - 没找到联机玩家的数据。 + 无法找到联机玩家的数据 选择玩家 - 打开NBT数据 - 无法从数据库打开玩家的数据项。关键码: + 打开NBT + 无法从数据库中打开玩家的记录。键: + %1$s - 当前Minecraft世界无法打开。 - 可以尝试从Minecraft退出该世界,然后重启应用。 + 当前的Minecraft世界无法打开。 + 如果你已在Minecraft打开该世界,可以尝试从Minecraft退出,然后重启本应用。 - 关闭世界? - 关闭NBT编辑窗口? + 您真要关闭该世界吗? + 您真要关闭该NBT编辑器吗? 传送单机玩家 创建自定义标记 移除标记 - 打开该区块的实体(entity)的NBT数据 - 打开该区块的方块实体(Tile entity)的NBT数据 + 打开该区块中实体的NBT + 打开该区块中方块实体的NBT 该区块的实体数据 该区块的方块实体数据 - 该区块的NBT数据 + 该区块的NBT %1$s ~ %2$s @@ -120,33 +125,37 @@ 已传送玩家到(%1$.2f; %2$.2f; %3$.2f) - [%4$s]. + [%4$s] - 传送玩家失败。 - 找不到或者不能编辑本地玩家数据。 + 传送玩家失败 + 找不到或者无法编辑本地玩家数据 我的自定义标记 - 无效的标记名字 - 无效的图标名称 - X坐标无效 - Y坐标无效 - Z坐标无效 - 错误:创建标记失败。 - 1 + 无效的标记名! + 无效的图标名! + 无效的X坐标! + 无效的Y坐标! + 无效的Z坐标! + 错误:创建标记失败 + + 加载%1$s失败 + NBT编辑器 - 1 - 正在创建该区块的NBT数据... - 已完成~ - 创建或保存失败 - 错误:无法打开世界详情。 - 错误:无法打开世界。 - 没有有效的关卡数据(可能指level.dat?)。 - 名字: - 大小: - 模式: + + 该区块尚未具有该类型的数据。 + 它首先需要存在,您要创建它吗? + 正在创建和保存该区块的新NBT数据…… + 已创建和保存该区块的新NBT数据! + 错误:创建或保存该区块的新NBT数据失败 + 错误:无法打开世界详情;世界信息丢失 + 错误:无法打开世界 + 没有有效的存档数据 + 世界名字: + 世界大小: + 游戏模式: 上次游玩: - 种子: + 世界种子: 文件路径: - 加载世界... + 加载世界中…… %1$s”无效 @@ -160,28 +169,28 @@ 添加新NBT标签 作为子标签粘贴 移除所有NBT标签 - 不能修改NBT根标签。 - NBT根标签选项 - 输入标签名字... + 不能修改根NBT标签。 + 根NBT标签选项 + 输入标签名字…… 创建NBT标签 创建 - 剪贴板没有东西... - 真要删除所有NBT标签? - " 删 ↑ 除 ↓ " - 错误:修改NBT数据失败。 + 剪贴板为空…… + 真的要删除所有的NBT标签吗? + " 删!除!" + 错误:修改NBT失败 NBT标签选项 - 错误:剪贴板中的标签与当前标签的子标签冲突。 - 错误:不能覆盖标签:上层标签类型未知。 - 错误:不能在空标签里覆盖。 - 错误:不能作为子标签粘贴:上层标签类型未知。 - 错误:不能从空列表删除标签。 + 错误:剪贴板中的键已经在该Compound中存在! + 错误:无法覆盖标签:未知的父标签类型 + 错误:无法在空的父标签里覆盖NBT + 错误:无法作为子标签粘贴:未知的父标签类型 + 错误:不能从空列表移除NBT 重命名NBT标签 重命名 - 错误:上级标签已经包含相同的子标签。 - 错误:该组合标签已包含相同的关键码。 - 错误:只能在组合标签或列表标签添加子标签。 - 没有需要保存的。 - 保存? + 错误:父标签已经包含该键 + 错误:该Compound中已存在该键 + 错误:只能在Compound或List中添加子标签 + 没有数据更改,无需保存 + 您真的要保存您的更改吗? 前往标记位置 找不到玩家 找不到出生点 @@ -200,52 +209,52 @@ %4$d) [%5$s]. - 没有有效的地图。 - 寻找玩家... - 提取玩家数据失败。 + 没有有效的地图 + 寻找玩家中…… + 检索玩家数据失败 前往玩家位置 前往单机玩家位置 前往其他玩家位置 - 走! + 前往! 前往坐标 已传送玩家到: - 这个标记不该被移除。 - 子区块 - 选择一个子区块(0至15): + 这个标记不可被移除! + 区段 + 选择一个区段(0至15): 筛选标记 显示地图 选择世界 - 单机玩家的NBT数据 - 联机玩家的NBT数据 - 世界NBT数据 + 单机玩家NBT + 联机玩家NBT + 世界NBT 主世界 - 俯瞰图 + 俯瞰 洞穴 史莱姆区块 高度图 - 生态群系图 + 生态群系 植被颜色 透视 光照强度 - 地狱 + 下界 地图 - 末地 + 末路之地 地图选项 - 开/关网格显示 - 开/关标记显示 + + 标记开关 高级选项 - 生物群系NBT数据 - 主世界NBT数据 - 村庄NBT数据 - 传送门NBT数据 - 维度0NBT数据 - 维度1NBT数据 - 维度2NBT数据 - 自主实体NBT数据 - 按名称打开NBT数据 + BiomeData NBT + Overworld NBT + Villages NBT + Portals NBT + dimension0 NBT + dimension1 NBT + dimension2 NBT + AutonomousEntities NBT + 按名称打开NBT 生存 创造 - 探险 + 冒险 位置:(%1$.2f; %2$.2f) @@ -255,4 +264,4 @@ 传送! 前往出生点 - \ No newline at end of file + From 99fc622c5ea17132dd597f9b48f324da061e07eb Mon Sep 17 00:00:00 2001 From: MiemieMethod <40489495+MiemieMethod@users.noreply.github.com> Date: Tue, 22 Jan 2019 16:39:30 +0800 Subject: [PATCH 17/83] Update strings.xml Add Supporting Versions Text --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 872206de..c894708e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,7 +46,7 @@ Please make a backup of all your loved MCPE worlds in case of data-corruption!\n NOTE: Minecraft might not load a whole world because of a single corrupt chunk of NBT!\n \n - This app is built and tested for MCPE 1.1.0 (beta), 1.0.x, 0.16.x, 0.15.x, and 0.14.x\n + This app is built and tested for MCPE 1.9.x(beta), 1.8.x, 1.7.x, 1.6.x, 1.5.x, 1.4.x, 1.2.x, 1.1.x, 1.0.x, 0.16.x, 0.15.x, and 0.14.x\n Older MCPE versions are not supported.\n \n \n From 7b3d76e709c26c94cbb9909d41135323507d2a74 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Tue, 22 Jan 2019 17:34:29 +0800 Subject: [PATCH 18/83] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85429758..f743e78d 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Until next change in Minecraft level format. Pull requests will still be processed in time during the pause unless Meow Cat decays. ## Download -Google play download not available now. -Download apk from [the release page](https://github.com/oO0oO0oO0o0o00/blocktopograph/releases). +[>>> Download on Google Play <<<](https://play.google.com/store/apps/details?id=rbq2012.blocktopograph) +And release page of the repository provides debug version of the app. screenshot ## Build From 2bf202e74d70f27bd9ca0d84f9e2e5ec8b315433 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Thu, 24 Jan 2019 13:44:18 +0800 Subject: [PATCH 19/83] * Comments fixed. * A bug preventing 1.5 chunks loading fixed. + Adaptive vector icon. --- app/build.gradle | 7 +--- app/src/main/AndroidManifest.xml | 3 +- app/src/main/ic_launcher-web.png | Bin 0 -> 44621 bytes .../terrain/V1_2_Plus_TerrainChunkData.java | 35 +++++++++++++++- .../map/renderer/BiomeRenderer.java | 18 -------- .../map/renderer/BlockLightRenderer.java | 18 -------- .../map/renderer/CaveRenderer.java | 20 +-------- .../map/renderer/ChessPatternRenderer.java | 18 -------- .../map/renderer/DebugRenderer.java | 18 -------- .../map/renderer/GrassRenderer.java | 18 -------- .../map/renderer/HeightmapRenderer.java | 18 -------- .../map/renderer/MapRenderer.java | 31 +++++++------- .../map/renderer/NetherRenderer.java | 18 -------- .../map/renderer/SatelliteRenderer.java | 18 -------- .../map/renderer/SlimeChunkRenderer.java | 18 -------- .../map/renderer/XRayRenderer.java | 18 -------- .../res/drawable/ic_launcher_background.xml | 11 +++++ .../res/drawable/ic_launcher_foreground.xml | 39 ++++++++++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 +++ .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 +++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 4236 -> 4858 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4858 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2671 -> 2934 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2934 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 5839 -> 6674 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6674 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 8899 -> 10276 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10276 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 12223 -> 14505 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 14505 bytes app/src/main/res/values-v21/styles.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values/colors.xml | 2 +- build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 4 +- leveldb/build.gradle | 3 +- tileview/build.gradle | 2 +- 37 files changed, 124 insertions(+), 231 deletions(-) create mode 100644 app/src/main/ic_launcher-web.png create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/app/build.gradle b/app/build.gradle index 497490e5..67e3ccf0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,15 +2,13 @@ apply plugin: 'com.android.application' android { compileSdkVersion 28 - defaultConfig { applicationId 'rbq2012.blocktopograph' minSdkVersion 16 targetSdkVersion 28 - versionCode 10 + versionCode 11 versionName "1.8" } - buildTypes { debug { @@ -23,7 +21,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - } dependencies { @@ -40,4 +37,4 @@ dependencies { implementation 'com.google.firebase:firebase-core:16.0.6' } -//apply plugin: 'com.google.gms.google-services' +apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c6a313c1..fe3b4aca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,7 +14,8 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java index 14eff67e..d183739b 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java @@ -40,6 +40,7 @@ public class V1_2_Plus_TerrainChunkData extends TerrainChunkData { public static final int POS_BIOME_DATA = POS_HEIGHTMAP + area + area; public static final int DATA2D_LENGTH = POS_BIOME_DATA + area; + //Masks used to extract BlockState bits of a certain block out of a int32, and vice-versa. private static final int[] msk = {0b1, 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, 0b11111111, 0b111111111, 0b1111111111, 0b11111111111, @@ -59,23 +60,33 @@ public void write() throws WorldData.WorldDBException { @Override public boolean loadTerrain() { + //Don't repeat the work. if (mainStorage == null) { try { + //Retrieve raw data from database. Chunk chunk = this.chunk.get(); byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); if (rawData == null) return false; ByteBuffer raw = ByteBuffer.wrap(rawData); raw.order(ByteOrder.LITTLE_ENDIAN); + + //The first byte indicates version. switch (rawData[0]) { - case 0: + //1: Only one BlockStorage starting from the next byte. + case 1: raw.position(1); break; + //8: One or more BlockStorage's, next byte is the count. We only read the first. case 8: raw.position(2); break; + //0,2,3,4,5,6,7: Should use a V1_1 terrain, why reaching here? + //Else: wtf? default: return false; } + + //Load the BlockStorage. loadBlockStorage(raw); return true; } catch (Exception e) { @@ -104,17 +115,27 @@ private void loadBlockStorage(ByteBuffer raw) throws IOException { //Palette items count. int psize = raw.getInt(); - //Construct the palette. Each is a piece of nbt data. + //Construct the palette. Each item is a piece of nbt data. palette = new ArrayList<>(16); + + //NBT reader requires a stream. ByteArrayInputStream bais = new ByteArrayInputStream(raw.array()); bais.skip(raw.position()); + + //Wrap it. NBTInputStream nis = new NBTInputStream(bais, false); for (int i = 0; i < psize; i++) { + + //Read a piece of nbt data, represented by a root CompoundTag. CompoundTag tag = (CompoundTag) nis.readTag(); + + //Read `name` and `val` then resolve the `name` into numeric id. String name = ((StringTag) tag.getChildTagByKey("name")).getValue(); int data = ((ShortTag) tag.getChildTagByKey("val")).getValue(); palette.add(BlockNameResolver.resolve(name) << 8 | data); } + + //If one day we need to read more BlockStorage's, this line helps. raw.position(raw.position() + nis.getReadCount()); } @@ -165,10 +186,20 @@ public void createEmpty() { } private int getBlockState(int x, int y, int z) { + + //The codeOffset'th BlockState is wanted. int codeOffset = getOffset(x, y, z); + + //How much BlockStates can one int32 hold? int intCapa = 32 / blockCodeLenth; + + //The int32 that holds the wanted BlockState. int stick = mainStorage.get(codeOffset / intCapa); + + //Get the BlockState. It's also the index in palette array. int ind = (stick >> (codeOffset % intCapa * blockCodeLenth)) & msk[blockCodeLenth - 1]; + + //Transform the local BlockState into global id<<8|data structure. return palette.get(ind); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java index 7c279c26..e4b2483c 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java @@ -14,24 +14,6 @@ public class BiomeRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, z, biomeID, color, i, j, tX, tY; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java index 085d4f76..8f690a72 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java @@ -13,24 +13,6 @@ public class BlockLightRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, y, z, subChunk, color, i, j, tX, tY; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java index 50965301..0fd20a49 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java @@ -14,25 +14,7 @@ public class CaveRenderer implements MapRenderer { - - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { boolean solid, intoSurface; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java index e7c39028..5169aa23 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java @@ -19,24 +19,6 @@ public class ChessPatternRenderer implements MapRenderer { this.lightShade = lightShade; } - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, z, tX, tY; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java index 41831629..b80e86f2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java @@ -11,24 +11,6 @@ public class DebugRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, z, i, j, tX, tY; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java index 0cddffe5..0c99a60e 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java @@ -13,24 +13,6 @@ public class GrassRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get grass data. diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java index eac7c6d1..c58d03dc 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java @@ -13,24 +13,6 @@ public class HeightmapRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get heightmap data. diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java index 4ed0c276..d895d377 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java @@ -17,25 +17,28 @@ will just make things worse on some (most?) phones. So I guess we have to do some weird patterns to make this efficient... Currently trying to keep tile sizes constant, for bitmap recycling. Makes zooming look sharp too. + + The cat solved this :) */ + /** * Render a single chunk to provided bitmap (bm) * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. + * @param chunk The chunk. + * @param canvas Canvas for the corresponding bitmap to render to, hw acceleration on + * @param dimension Mapped dimension + * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) + * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) + * @param pX texture X pixel coord to start rendering to + * @param pY texture Y pixel coord to start rendering to + * @param pW width (X) of one block in pixels + * @param pL length (Z) of one block in pixels + * @param paint Paint instance used to draw on canvas + * @param version Ahh, why do we need this + * @param chunkManager ChunkManager, some renderer needs info about its neighbor + * @throws RuntimeException when the version of the chunk is unsupported. + * TODO: reduce complicity, e.g. remove chunkManager from parameters. */ void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java index 89564b2e..5d6031f8 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java @@ -17,24 +17,6 @@ public class NetherRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //bottom chunk must be present diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java index 25855249..9a9d2ad4 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java @@ -16,24 +16,6 @@ public class SatelliteRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get heightmap data. diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java index ccc683f3..14d74edc 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java @@ -14,24 +14,6 @@ public class SlimeChunkRenderer implements MapRenderer { - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { int x, z, i, j, tX, tY; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java index ecc6d9e9..5f145042 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java @@ -18,24 +18,6 @@ public class XRayRenderer implements MapRenderer { TODO make the X-ray viewable blocks configurable, without affecting performance too much... */ - /** - * Render a single chunk to provided bitmap (bm) - * - * @param chunk ChunkManager, provides chunks, which provide chunk-data - * @param canvas Bitmap to render to - * @param dimension Mapped dimension - * @param chunkX X chunk coordinate (x-block coord / Chunk.WIDTH) - * @param chunkZ Z chunk coordinate (z-block coord / Chunk.LENGTH) - * @param pX texture X pixel coord to start rendering to - * @param pY texture Y pixel coord to start rendering to - * @param pW width (X) of one block in pixels - * @param pL length (Z) of one block in pixels - * @param paint - * @param version - * @param chunkManager - * @return bm is returned back - * @throws Version.VersionException when the version of the chunk is unsupported. - */ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { //the bottom sub-chunk is sufficient to get heightmap data. diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d60f2b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..90570271 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..bbd3e021 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..bbd3e021 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index ad36cc90ff3c65d7285da8d20c28183387071585..16913c26855f71a30812033ea17fd1e9b6c23a76 100644 GIT binary patch literal 4858 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 literal 4236 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index fcadc71fff660ddc7128f1dc652aca7586f18fc7..a9945ec022af0ad54e5c2e295dd131725baf81a0 100644 GIT binary patch delta 2928 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 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..a9945ec022af0ad54e5c2e295dd131725baf81a0 GIT binary patch literal 2934 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 0bef54ce60027633b5d28cc6b979efbbb7c84d83..0ff0ac3907d29dfe51ef6e37dd5816f3991b1d16 100644 GIT binary patch literal 6674 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 literal 5839 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 2456d583db5a1618a317f761816dc49491b8c146..b7e5b0881ec076aa6700f28b656371c7701bcc54 100644 GIT binary patch literal 10276 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 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b7e5b0881ec076aa6700f28b656371c7701bcc54 GIT binary patch literal 10276 zcmV+vidq#AoUN;Mx3$A+)h?~I z|Jqh--4jI_q9EY;-}AV^mk@*uLXf=gJ0Br=^WNV(>n=4l)u!51n`%>Ss!g@2Hr1xu zRGVs(Y6Gz*e}gs=%7zhLt}m z$F+UJR<3PQ4!>9O7O&K?IbJ)GbNRm}=Xl*9@`T8X9=H?u(tQRiMig^$vM2CDLGzQq~gyJDJ1fW1qToa zq!;#ia=nU^a(NfX_qpVnF$gMZG*wMBAu}{?D_4(bmHx@O-kAiN$KpZZRg%BuC`ntq zDyoE6N##`?LiejW?)7)WVVWrg? zhE!xyPE{qdAR!Qt?;At}fW5vZ3nsj$GBlS&OC^rp;%A_$xklWGbK5#4hwDcwWhXUD zOhqMOMdell(}1u@0u==c@9i~E&k0C-Q?`1AM(%NJqpDHGfzvKIi<^{~!@DbFnsTem zNaF)jCr|?O$u@O)$yr{hXns{S+&~-p56RhHi-nxvYE7f;(9|jw)i9CgAwJu)*<7*=xwdBfI-6p#v< zTKuDGZIr{y#>U-d4bRvtz6~jiODc0Ep;;5WY{WN8+{AZko_U>YemHG*P-z`mv=Nrw zM(#*bq|Ky_4b)mB=J2P{x9C-}%GZ%8Qp1oRC4LikvD$pKmTxsXf%5}fCFSyVH3gG3 zF4qs1A4z~^N8*{!O~ItaXL67B?rrn#+nE?##n&L3=ZGLdri_&_HrE&C?u&$7XO_d2 z`xl`2c>yDr?_GeMr2?k-Y$67J8F}M{6QJvNzUQJglz$g zwmJFN=WsXw8kCopLq$b}kl=Ut+xKK2+!v3*W0eu2_|3eF{ioRKzZLMrtZ>#MA?H7| z)%>OMfaA?*l>I+vz|{vA#X?h?;J$ba9*gI|b154{37fgMn><8lwuqB_N?G8+ik`Zw zH>^881%ABp1w1Oc2QSOZ>J5&l;IViPJlFcKr@*vNdMgP`RS=EY==n$BD2w(@)?p^l z98?x~X6_Dy&wg1?{F&dN>}9Fc(9|Y)PV|*N|8)brw|jtcKt%qIS;zT-A8Fde5{@LC zBoDE@aeW>>qY<)B%z<PG4FL@nYoc>|b!EDFvpPu>y2yz%G$`xc5{6gC<(waK#=MT}P3 z^8I{B-5IJ_h)7)|WO83cFLg;$>WuZ64W3ql<_Se43Gt0yc`OCKyto~n6z3__Ov&Oa zJ$?2Fj$hmftG*aZo*`H<$wpEqaT`6$`%QP?Dyh?Kp-yi0kB065hKanHyZb{9d6WOV z{Ua1Sd#v=()FuTb`Ecp>&yag^A>-taZP<)A(?KnEt;bDncqgUnyx$@z+iPt@HjIwn z>d$DK@BZ8aj|%TKS!imL$3^$y#HD>iD~%)18qi>-EHR4*BUZZaBX#zU(!eu%Gq)ep z9wTRXunlVdXG58$g=-K0Bz{C8lvk9$b@05Fa^@MY=UswN&#r_8pGC{P9S-X#exqmQ z;JHpp;SaTloZ-<<(34Oeeh)SeOxzX-D~=|@nafAu{?i*0Wr^Pw!58Jh(_3)%%2B2) zuuSCBbW-IHjb6v83><1jl^AOt_i%j|v(o z?X@^)t%xaID_90AErcj7lCT!#;OFP(a!RN!^PX2!?5agB) zcnsT4Eryg_AITl+OI~A3_VZ(bC>{f9lri!7vl$w7kPUy&oOs@B<%k&qx{Cbe=A>C&i(0!x}cn&mWgcELB78qxp7q^l34E;zf(~{ES>f@vVNZ<>NOm{Ei_UUBX zP+F*E+y-tus=Guf8cRh15~S|&;s1N^KPHO6oKVCZrxMQrCgoUI+;B4p9^nX+KZu3X z=ZM2ynE&=W#>EBj%f-_$b9oYkj&ufIxOp`^DBWXSVCvGiSLgpaT`sO^M>>G28o)6k z8#v2hNkc?rEo^$*yh|=}Roc9dcz}evIbgPR!@h-;p(CAM@gjr^E?`(!n6QAT>a)k7 z=vm=gm8r&Oc~(*kXU=~IX-h_vm!_N0vvUbVLBkzj_R4Ya>mO&BeQP_H)Bw@E(1?%R zADejS=*wFRwTxZu<}XWEjk*A0%k6vDVe_Y}AS{v3Xd!lCqQp$?1D_trhDZ4inmjyD z3Le8}U+sYS_rhyDD|et7^ojF?jBShI>WzQubv~&9%I2XnT`_t+2YO7jjip*hE&$<4 zS|x1e?x@l}4G&ONzv7pS^SE{YdKfw>6!?QIg$ma@(h8y`1VPTe4EXQnWoG}Tq;YQC zxe7ZEZH8f~J-|20M(El2gDuH3hd}n;wQ%j$zfe|I|BiCfYoIFSfZbn*eBk^IsdIIC z0n{>V5)IwANZ;ZvDM0MPzMppowjbCCgT@8a&_<}<2PApJhMmjd_Wc`556^?VyO6bK zHAIZ{2T?PPf-l9+gPY;r!#h%$X_){LIMZu9DmdY0-Q=N#@ZfhwXLyc~wWnbU1JDuK z+J9a=hc8bafU$FjfL~OX8insS#1`hQp2S3M&&j?@({5Gz0xtf24i;~i4gt~jHSXJ= z-1qqT!{D3KhoMx))Wy&LekD^MA~a)h8$BUxX4i!@q?Z|>)(M$ag9BxmHB24=RrlU| zk_V@L`35E~904IQPBnaoo}=7g`mzN0>B31UD0IC3(4b}Kb-rbWOTY$vGjejNF5V1Hgt*WMKuF8ZCL?o*>iGNV`hqe89Oq`6Cn1R zjgTzNNCTfp>l$yiPn0#Jeh>>^ojM@%n=MhtdE&EtbLJ>a{V;iBme9IIH`=+zxji+-fjz&~G_#d>0cXqLVMGaRwy&Fp0sd#OW_9YnTxv z2oPSIJJ`Hn#0vK!sWcIqv5!5W&m_BP$bc*WwTaur&6XwlQUpNkHy=SlhZ;e`Xht-0 z!-(#a!&wYS@~FhJvN8hBZPXH5GUUU)@}a~{=M;cgMq^a$cMtqAKJNK z*mAc*i6O#hEQ<$`3!T3v3}{FTP)nQMx^0Q+%9ADfQY1iZ$;*GhfHFL_XN_zwO8*2e z$k?`o36rIXcJP?%x34l_gpnw)W^`xvlHMO&BLc!Oy4PrTShQ{` zT)6ZLlsqq~^PITP?|=OQ9}pj=*JzI#p*Z?HV`hfIk?;005G%SJPZS`|K+{(UQS?#? zAgW3#R>rRN0RAwGo-`V3DRJ}_lkOJ7w7knE$&sYk3U z%w0VZ&i(H!lP(aKLcrf}AKX`1+V;SdvsqZ~hVi1`kG z6Wu=`+74zcPlA&_f2|blbF~3f^=zGrhc0t1s1HPm)DMby-{~OrgCx>GEe#FTv=TG9 zzb0>yE&rp805RQ3N{ZpqmEU1OMjG^u=ho0j2*yYfO<_cZpG7O9_ZSY$UNsIb5OY>k ztUQxBya^!2v5qt?9`b=}Au&^x^=Vukvc%=Gwvo1hBmgn~k9W9rM{<&<@^GM)5g>Nt zhW&1qZ+>5FmbgaNc&HtG`0-38vs|i#9p35!h=C}|v^aW+>(e)CAnHhuSmpMzi@$MK zN&Fxt|I>GB*KWyl4q|x>sZ~~h*n(|t*bAU4m-N~>#wad4#Sb=pvXTkemH8R!0}z#x z=*2EiYt=+mj`S;Zq(4(L>O#hdt*P8M z_Ezv7Tz`O=q~{>B7g39xpCo1pTN@@4%OsK5T*og76Pon}2%RO(_)P9e#({2TCQKj; z)}~b#@SOCCZ#6)SCWHuY$=15jQ6KL)Uwn`kKb|iNu&Dj82_&^oZ(u z+?)ZLlRn`!Kr2$34?v7hWL_3Nt6N@TwpRrLG;F!+llB(s7PLvDPJr52cGEIU%9gDU zbW;cD{Z-?u3-rBa22fRRf=(6vr#svy=B#4)3b$vb4sC7f4NzA@gbZ*Ok~AU8KRY|1dMPm8<&Alu@*XsI9&fwMC+z)-h7i9`WF?W2~sPFFgB(o_H`(Enio z;`907=;*i;nV@Xc*&2`S9qsx|vmYeyF0C>Hlqv&2ey_!k3iH2I=ks~9`1!oPUTw#+ z_Yo%&JddwT&r&-;=tO&Yd4avX{R(745`fzBhII0h=hBt(0Z1|@Iv~IwcJAB>hYlTr zLx&H-p(9mtmU1y zX^+I#^#KUud@VIiHIulHJ%5&0B-RuF5>&#`FAqca9-%S=^yqOOOq!koPoBs&J@LsO zzJt&nA|pJJriOv;(!_$W`uJlDEif zXtQhpVZ~0KJXvOdp1&x8Jx6mG!TEyzclkGFiWX5#6rVJvPJnRYgugFef{TA%fLnKN z3dwK3{|YBgeG9RP(eOP1;PTbKg?@jScmIvUgqUe#?%!ed-G6WoF=18ab?mrg$bVA% zY-XoUpCIsu$SneMa&kIDb3mH)oyEQ-ouN~I;B3c4`HhX24InJ+Ns}hY3=mFo=@rqr zN(Nal!5JTD`1JfC`9d>fSR1f`Wq}BqT&gf`Wn|FeniG{Qa1}=)H2?CSGF`=EF4UeKdQ59r>#JA{S`&h^?i+qz>*jTejE4dhwLGe}I1mrDdj9q28{ zf}x?ImPF&iFz)9>nES}fk(L!8d?P1LnkX|sIH!+jzn4+d`qj8zdd7-60lIVdj?|Sj zba)ipx-C4b?4JFfFpmG5p6A}ZyF$mD|KE>r?ba2c@3M0@%guqdu&|g&b3huBj7()h z`F4itR*6}>`|@ZLlNF%S(o&c>ae~YM{dM(^8j~pOI+Ru8dMV=*>jVh@4+!=LzW`tH z@&;dI$goJbDFV=^`*yt%pqaC$1A76Qf}*F8wsF|zZD8*1?ga)0 z27WYzulKys)sYyHP9xGiw#vhimKC5EFJ8ce3FBo3D8Jw_9Q|$|>^-&>4t={Du3Z01 zXg(nHYtV`A(X&UL05Nk|lk?xde^0E;TQs|NfT9P(&0E)n0ouE>R)B(n1L5cwhlKV& z_s3ZXj_C#y77T}i!q>CW7C$S7*>m2L$$_T+&##05a~i@+G)IuYkv8g+YByRL0Fsvm zs&k&@b3ej>@G!ZA@k>AYpw@efiXH?vL;(6^&kpdf1)!**kxW#_-hac6WsDXL8sP|6 zuKgwS+h+&&5*PzyN+8^96t)$C9RXft1IqlsNvt{pKFBczZ;=~3A2qUnM zH)jy1H>g1YiX2J+x_M1#|6RMc*9y>zRZD9`lTj0bt4%S^Dk3!h5z#_@`t_0}|5IUV zYC0SlAZAQknknwu(Do0D*mayAWYt2-5{03v|Nf`r@XhHXaOj)e@Y97;%(N~-^B|H) zX8xkNbpnKDHFtY9-mguGOYZk!r}emr3Ox{)g0k7s|l9(oC{h?FaRnYB#a50g_i9=&ye-)|iGD z=Qg}`PuQGX{NnlHV`t}f?g^Q^B3aCsvSgys zX%;tgJWh_u-tS@F1L1s%0syMb2imeNQ@V*o3!Xx9N}|N(%frLtrHP5j5UhJ?{GS^6 zAT(pcmb>kfMGGklK-X{mD+VANW>QV|enrJaFnz|fh65-H0s2qqe7nVj@%aD!2hz_o zy(xQxMBzJ)zHl}UYdWFAyJ}LdU|8KUkE+afgjIARowcg*0FvE=p{Wv`5Pa11esD)* zsi=pK>efI-MMcoPcMt@H2Go$MT#!$#?NZ_Z+WXn3H6o{^v2ik$+4UROnR2LS@~{$< zhc-4gOR>&%baeEjnc`Qh8Eex<&3@E+&NXS1iprvaN}fN1Q@?y$E!Y+&ZkE+^=YN6V z(4ac=KnRgILG5(!{yj)Y9#cC&H-rJgHm`ud*OiT*j~o-$jF@~}^x=F+n=whOgt+D{2m_GIcrWJ=~wHaDy##M&V5qt-bXDY)uOvw_>gftm09~- z+S%LXp@HhP4CT{&m^wXG<}kj9Kr~_USh4puNDM#+4(}HNC~8P#jeW4M(VMc>gCfO% zd0?;5d24I?;_-OsJnzQ3*4EbUD2w^3G2s*u6MZX9&X^6Jg|f7d&Dt{%j$hmX1ts|n z-lbJifC`HW;P|&+z}WH0;OFNjGeDRTo;o9~-T)ms_BjLwy{_o@=e<|soFm5!f8#Uz z`TN3|A5ROfua^G^1`O<9r}OOQ=JwLq*f;{~nl@jwQj^27m+#PqG+fEoD*XL!8>Q7k z0vVg@3-k65fm8n+geS!h8%zUXNWAQ$C9rmVx{&PMw;T5Euac~s&9G?6Jcv&k1p~vw zY9rR{^Bhkr%Yz56;ADqp7LM?mQ;Hb>mr08|HeZ8oW{KIEC_@ zy#r;^_F!25%~ZH_`)4RAEmi@fAnikZpXWsT45BUXdXh{kE~ys6UTJ6_ZF-HjU4oNP z%TEivmed`gkac1X+|9qH0;qwt4@O?Lwze2~*=T5Jm`c+3S(cFAytP(qmtia1{>FKP z1nT2l`r36%PuZ14~Wm>L$!~-JVN)nLV6_B2vgl| zeT-wqtmoiVunhuG)e6FVnDJ?U`0ST;kUVL$s`BNheJVK|4p>@RF2=fSXze4;7s3nL z518e!4^3HewpV#WK_rlVqkL2V$OoQsbetU>9e*Kph)X${v4$?URBOPE9g*Q|4OY4) z8s0Ih+>01fCa1ZCYLfv;-YN*>H0KX-oL}hb>Z0Rp)liYwo3xOikrlO&op-e5gi&if zDw7)+ph0Qg5D@AM0U<v*QAz|TiW*uVKpag7@1 z_xGje_7*Sx-r(aWBKVy@zYeK+oZy^3zI>EQ4~{!Fn5JXhsjD*+h_|iJ9MJOCLh7`u zM`t1q{nG4r;v%gL5tUW19+T+>aqIXndNm(nSMVX`Lq0@I^DOcU?^cpFCk3{DlFM{V z-?k%1V%fEO2h*KGAYm~9!0Til?*sdJLSr=LUB`TKMMUSFFU z!tG~OQ~FyHu>fw^8wB?eIgZ!Q8n}4ge%2Kp-L1gRtrh=$+DDv2jbXiY zYt7akQ6D&8PRQgoC`2`&%H~xLS>loxIKuwK)kAf3FeI?YbK~fnrb3kac-vw$GI+3ra`lps16;x2X~$lv)Y}mwJN_Xb$w*; z0;fFBL1rb)I_VLeVch&^ID7tt&|tj!3igv0Q~-XKcpMwK=Gos)Fw13niCHdOf!gO4 zf#;P|cO&TR)P5Dqbm_4gOk1cm(#AGr*iL3eq+4)Om$k8LJQQlm1mBX917ypXR`gFQ5hU&-AzS8#Dx1Z93J8_j_kthIm!*vw=95rB13p; z6ZEG9$KvPuuKJw5Wg&B|#sr5Ak85vZu;J;SCUxRM>&ArERil|Maau^w%+a3ORYfU7Oz+qh>XI}=~eemYxyC*G-sXhijziD4w z3Or~3I_BJs4Nf&Y13Q(w^)-PmUIy4f-h<`$v}ZL}v`|x$jqMK!LVJ5n&DNe_GaSz2 z($R{BC^4H`Ibc@TN4SimXaVyFTf&I6o^bG+PvJ?yV<@kvxk^qNae4*&;f-}c>qJ)>{GPh}Ax+vSXNhTY9bFCY@Y#<4RBIxI znls)jq6Ht3LM;`)sG2!UTrh$esVdq}d+LXA)%)RR(TSz-7(5owp$y=N!ej5#@uqfr z^&r|*VMXgiQ(63;x_U2a(WVZB$N?cD6?wife4@lG&&sfw4v#$snwHg8@Hj3EXSl$q z2}f-pBr}&MRsW8k1tU5zs>6Np7-a#6?en;bL?Bej}>B|zTzsJw`9sY*< zC=)oWP`$#XmoaqV>0cmb%ZI`vh`6*B*;FAPe@FGt34BKL;p@gDk3bXp-=ib5A3%=IWjzWsD2MInpvW zWX-PHvMFdE(a1~?-W$;eav%@r8ZgRoee`nIS2((Z(MAe_C~*_7yzi8*`JMwz%9RZU zX-yWBVGcC}OAo!hq-@=&<`Fbgu*3Bx15a%b8B&$nl{}~mx4-F(s720Y@f*2{^@}$1 z%Da!Veas!$bRlB5ZFLLkSYhVaekGPE%V9Qb>hOqzNSiuy7BsAMHh0q<+<%6{74(a+ zSEHg#T0*90S3#~AE<4el6p)`_F=*s~mFZDmRn%F`#(qT!@D zePAaIjaK~Nc&pE%m%3EqP$5Ny`f(dMWj!X?=6ght3TY}@hlJ?(*C~|vI~@J*)OFNx z0!$}bmV!RbT20aCsjnt#O>+@?tc-Ox^o!hjna&(EGQ{bY2ov7>$iYoqv0o~0oP zHu=n3t;FDXP)8Xv%MJmC&&;~DTY+Up+qUd!S?bZUee2*6mu%FaqdTx&#)dlAE_$7` zLb@m092>gS@b8 zDOlSGd>lsOpr6w%#HiHDqr>Mq`kMX3XdNS3RvK@+zayzOdY9%d^x4`7!+k77eRG}u z0i&&Up+<^XMNAt;Y}o5j&gP9c@g;u4%^TDw5;0Si_&@gBaSSpnvGLS9+|Eclih|=z z%b*J_6Mb4n?>1MhQ(tY=j@ox~s=HP-6s6=ktzz)Z*6!qx7OT2WQ4O*=vvQy;*V z1(GWf4`SZ>@dlZ#=$&f+>)-`W&oFvJ%~UnE{xwjH-^7Nh4j*ddYWHWnDDx+^nKO>C zyy6V(ROulCj4(7tFYJ5_ez$P#utC>IdobQR&F46?9Apdnth)5s-(`j8DtP1ol0YMM zp}7!rwi)w=Ezx=wIs-iVc3RkjXr_qwogPMi=n5m#Js6;vX^L~a1X87?s8l)SRH`C9 zMhJ|B{AdCZ0m6eN)PE63H7tx%jKK6U2In3|I1cNfjhDe`bLaNyx`x_A$US<}dv&4C zu>*Y;fyNPlqpku+{vfH-!tFr&xtP+%LwouLJ5jBK738k1tsX#pix_T%`LdvR>jV9# zIs8d9R6#WHozUcA)*Hb}*vzfUH*N7^116AH!#{E><`3;$$c6-yd+z-^F;Nq07lfr- zuwh|WKZC2*JiV_wJGIZ~XrY~?uB9GAHH{n9Hcs?j?diRn(r4+Q(m09(sSQorx22JZ zF?G1DMPPDa14YEET}Q3%2$fS0lbOE5E!T%6*&OaY(e}r%84i~Q%yhUO@qV{E#HY$5 zpyfv{a(){1fy>j#h0c$APO!U1e6Ihx`ggo$@27vMi?_j#)*St#mL7UrO&vRYXk?|G z(pF!yZ>v_V`1D$C^x7^W&{)%ZHKzC8jy_8p6*!6o=^Yx82((f=7EETeW!jeJPaJ7f z<17X$cd8*gvGUc`TLoZuP_1_AAzFIsp^T8v_#OGpp8~=~bICfM4U03qmLt8kExi{r z5oiQvsx^HU6*!FmlBiZFVlTRr^FKD@q~3D3bVUK9L(FFj7KaxEdI`HFcmyZzhR%s zH8u%od@r_`1U0Pi@~O{R$*2AHBHeaJLOFKpL)Djs;Kr^}u3X1@Ya=nyzmcoT1989o z=o#$oHht)xC499N?QpUc&02SJ`6y8OyJ2UV9I7k!rz;x_+-=1Nbjti|tIfUJ`?~>& z@dwB*RRY&Tv(uH6c$vivbJ~!s3nxRbWr*I6oqzct=G$?WgLV>}=Y~o482&icOY5Jq z$`zAAo^R*EPPWFM70BogwE1z;$Dk;Ug*{x>-cy`R$F%Q=g$_5Ge9L;olwghm^8Pb^ z%+A3yJspf5y!}+&pqsaL`b{(+?WATLj=Ez7V;!dC75f53~?3ID5ffJ6dWow009xvZUzZ@uq@Pfd&$ogi<_dOn>jq@P_b%x_AJH*ncxt zeu0+b=4l4`f+ha1m-e7-Np~(F5m7(lWjfNW)ty2$XWHASkSmj?@sv|~<@iw8@JY}) zJI~oT<_sB3l_?`1TG7<&ggadf&fO)s2C16FDFx-r1{sYlK9oP7G9CX;5wTMj?%h2? zvkqqR9Z)#1^W|{Y|L%&e#t){K0bVvzQ2%>`8=qO?Z}{aqsQtbEdO01^KdNF7s+aB% z?e?7eaGtBmVlS!G&f^2^Dx4x2I%FK-=TN=bHZ@o{Ehr6>X#(r`1f<= zeY2KdCV22MN6R{iqIGA|HOAb;EN#WJ&g{{^-qp2D2UzkIPsMHGg(wO7I^y2RVeij# zrSS1E`?p~6{<3|n8+Qqf)hK?$r7X$lv^kI8c!!w~}-E=80@2r40IiD;O23Bu)+mvzkQ(NYzy>*DX*K@kI_b2== zuT+Gqt?uIBd`F=k_|BQCP@V0EvezZLUee|g{O?>cUVJX`-QN=2;MV8VaW>mHc0<-+ zKIV;lFzkgOW;Xaivqt7o!psVJy$Pu(NoW1N*3a|@*-bN{2 z*0exJG`dR0;F=jJVoEOXe`3SGcoxIzPr&zDt6^F3@aHq(1;l6d#Vn) z`~Izw2Q!;8@Yn+R3?yk|&*T1{L1|(%0zs#`gvc!E;(>`G@PmUh9H*o4eDM1ff$@x|3Wr`bCZ(`lf!bT@-+z6fYv_3JwT~+LSx(sv zp?p$~H$g~vYCCt+AsoN7wZ5#h4*BG~I)AL31!=_YMgu;8lt~7pB1e>P(ssh3;f}sw zc?iZ{qBh?JA^$dz^Z@E>^ z1l$)dac4zR*U_(Bjt0GT0V^&z6#7mf%@SZdp|Kt$yk(F122=)=B%>3F@iindO&+X!+`r>$SdxJ+iq490dup5h zN$DpcSrIc;yp8A|-fm&Aen@;X-`SY^(u{Bhe&vd|#K9x!+XYOIDOESKWs7>LA(<|9 zK42*PNH1*3Z|eR|`9nTD#xq`2b4v{J?K?jxm8xI!37+_H<2S{15sB$8yUwg5we|o| zXWM!y13YA9;+ z6zeGapD}ejDTx2x$CaC<(@JNP-><){h?7{Gj;F!7v;(sDtxZhp0bGlvjgW45_(6G&n zEx@ugAY6Omp;$;H8~xW&D)wOqw(`524tAj6_EO`pvVr7RW8@=JkndjFTJtwI8IULyKK+jZTeZSa;E(@*Wdis zs1o=DIs8E}Eo#HY5RGGxn|9KU>A%cG}mI&LD=!OFEQrc)vGtkK>$*F#_uda}-}f!!Cud>O($ zY7cjx!U;XaL3!@Nv0Lqg$TJrT9uO6jV~B}*;F)bSnN`vw_186Wy0Qf__4EE7;?aI| zzjDrP5R|#R@!0LzZ8~xE`1r?ml#B(j5~+O0CIyaRy{?H;37r2JV74H7P7(*yZ1{S? zwdCrGzJ(IIefCch<;V1*{^~1?uXC6rZ~K_lARg2B6>zm3eCN0%tlY&13U(Ly<0^T5 z?g*2Dli=lZd789*kXkwx50E!B&P527Z(S9sd#Wdg6K)RpauQjgJ7&pcwlhDX0`=kb!+7OdQ z3AdwB1J}tecK>#UowYKR;v22Sh+dP}!K$Ulmx%8uP>j#s9Oep{TxP*xt{e!+WKUVr8S-|LKugka)vdvhtelUC} zC1U%Qn|;lM)l`V{A!zI`{RvXNVO?)W?!-^f%tpc^6;R+_Z?Vb62kxx!fKvs9A{w29 z|J)dLFaY%t@1ZZ#jh*(ic4faKRRV23kHe5>im)K?B1%f(`X)l8KKolExCVFN1PbMZ z{<>T&wN;qZJ%YKs?l>A1CE~%Yg46zpLPXmL`7_bL+A&4de6=GGMkMhvk;l1G$S3he z&64W?5Aa4hR-)F4;X#HaL3xNa@Z1J7`?y2i&;n(VDzF@to*DMxqboO9AGJuY=0^speBkN2~|;^HkD|1^k*p`SnVHAXRrO`f%MDM z^K=xRm`R=En`zd^m}g$Yre=r?V^5Dyv3wLQr6|@1_5G5SVrrRfy(qSy`SKb2*e|iV zd*{rvOehVM!WC#iE3cN!xq(p#|BmG(jfArf5oE!E&u7&A+pV=K1Ul{%yv(}~0Re*z z3p%J=f80BedX0Dt3L_O4L3NGNg6^g@651pS_Dv_W-l?+7mfH8u^2wx8 zH7_(}$c{JU08jNcmw%AZVcVqZF2zKKN!ToA!&cnhf?@~0q1#OPC1^z3v}Y~>W__rz z+Re_yZ|-Le8&@Y080!e(RUYFhQc~?1_mH4$6TiB=AKlnrE1_~*HmQs;{e)>!+BHQY zfx6?DKefy;-)Qp6cKm%U-Acr-f+Bbs;EF522k+0*PSW6-N9QO+V!BNt;0hY?M z`QuB%5j1xtv)LWZ-QtP)tSXZn#rdG7J_fK?9%2RUr@9` zlDPge>uYtMtAf2;lX#Y0Xfr{3ysmh40_Ji56sgw=he zrgAWP-yzKo#l@Db2rBwqXz-hvotc*H3_+h8-4L}#y{eHX9T3=(B|E}T2B%WZb{4{l!KyGjgL_TvI0^kYm4fs_&S8|4k1eqq z6ybuY*Lbqz0Hnc(#p&cQv)GlP@Q%&V48MN$NUp@FXR(rTu=@>C039Dh`Ll>TA%{DQ zPPntX8{H(Q&>?aug|x;L3jZPH{7(Kff#{5dR*hNAW5dfC^X}5-89eKtxdOmOQS#Ih zMgND9J9-JI0xz^$O|(qc0(Gh-F$_<9n#5GCnHuLwZ|kAA0YkX!(CXGRY6$tURI$E_ zP( zDG-uPS4v2q$joqvsMNoSJd!~cn4Pa_edqg`X=cbdP{`s`FP6m8RE%QU%4_gjVs~~- z;z0njc`S?$Es&nu=wYtDo9fo$HB;suV@?W}WeG*qGnKONQm{?I)(s~1x;Ws=L!$9f zVIrHK*SPRBd`(pR2I6J52wN)%DE>+z3CrjiTytG&#_ccsZnOF+rqwy69Qs~60o673_SD!oo0SY9d2rktnu3Qd((~7HNY5t6hUC+!2p(fnjotGW--2V&^zX_rw52J2^E<+4kb4*1dpo!s%rTKzBW!!VGHQN!G+ULlfD^t2 z?*442qAVYiR!&Ae6Waa=(^!6BiX{Q7Xk2`b2+-(oNB#U9axIW3fx`10J(&F(_wCTixRT zv^byqupc@5*woW=OSKohoxDI=vLJtlee#G6c=6z9R`h)WGmbRp&`NOtf4TLY>S+B< z)hDx9pqzUkV0k8u9Du!1I6aH8Ge{1)%)n{y6Y1a?3KgxVBnPwKa>`p+rNYhmj@hIy zM!N)_!9ysoyIK~LLHNJ^)5-&+;=;YmHm1u5qeISd4!_8^tVQ6~z(e4(Y`MnlMlr!2 zgA&PiqyTv1&y{6zXaGMzPVE#%!QLKrP0F@o&%3J(rLZk@nRK{fv!D#iMs&YRh^$m# zWK*mHdQLF~Knaium&Lc?4YN|ccQ&@Bn>3wV zNt7=c&spCKKJ=!)cn1d44ObY=nFpwVgQ^|GL7&(rTU}7 zvD?}Ciuo?zk`YzBOIn_W-sD8Ms177Y^TWatm7*lF5?~d6223Xxh?oahj6=R(^id$( zrh${GakS=olIzqLv*ld>Y9RrvL$Py2&8PNFN}RwDPIJjxdEMZOTXD$w(kYHw_0Hwh zVDZB)8f@?x_Q0%=!$DXn3ebfd zg$9AnVRHFA4}T*^c6R!peVpy@tC!c-Zs&>x!laT3yLJ+r1EpR4D!!!O_jr!6cVnl>OI(;d|Jcva>y%04udg(I+P$ARrVDAkoSK zaQW*>y0OJZW9$r76z?2ur+x{r2jyX4jFn9Q`-o$%zYRqEmj}!3@`iJKdNlcemCVmy zSG2UWNTtFR6t2tjG3j_oEy96aN65ezr-LEI$Y_0ogHY!e7gx4cbNs@Kpt&w@f$9~c zVB7x`14AMnLlT&k0y`uDe0-hRr>Cd6|A_{MPl;r)o*B&i#|z=l-G{;rd{Kd zKlh>!iKx5VWmwNXV6GG>uzdx??3f2|j{?-x)EHYxl!@Cofs}h2fXWFOmfscYQ}!&B z0ynoW0L;u^QelAoZSq285H>b;Mda?p3<9=B2)gyUYmP*@=z7su5P+{|RT8u_4pnlA zTCg%a3_Ue9bx0PD2kEPR0>>)8z+Zf~DJKbl<{U|ef@rzgn!qUd63iHf#38`P_m9T? zQI$!DRuC7I(jlSW0InF&rI-i-bhfBx1CGT#uQnAZyMrOge1&OKA?9~k7(7_+2yi7o zdug7t0}7bkuB$dUJKRnXKQ=qlIusKM0LK_h#iD=sn)n1p$|`Z5yAAqAXJcfIYm;4} zA1@d?j!Xg#Nz?BwT*Jd~tzZZ@%?>wmIXYbhk!5AE7*^(V-Oiawk50cL` zsfY3L@u0t!ilU}B_!<1}FAh><#d>zu;+grWu z2yOEgma6yMj|ClruWPFN>czt+({!Ah;#ouP3v7W0(?7dWTgy{pBKZn-q( z&}zQkwDpaswzf9aN1~NM5;k^{Uq*>ummF>gcUqzEpU7ebnwpxuXy>P_xDNUUJ;LHm zFSD)rzsv6-_C*4#`M4IjA;gJ_91Um!PpGvZ8T6WQ|8P-_L%&Jy?+(f(0B#Rw$+pvl zbYP@{l8S{?KoBgD=GNBj1oe}#U;8Tz5SNkqum&B^b|4A3chy%HQC>TK|1`~6++FHqMPROHQ|0pbJ z3eDYT2eg7P)lyKXcGut?OqC7Sd9mh={1HCBik1co(PH=6v8I^80a@_gYc+lm$)JpP8<@wf>LQa(1R7D`FF)6x8XWv24|OtvPSYKo;y`Yhz2@^z~A)Pd}Ot+A*f7lMN}8O{Mn>w-4up6lzg$FF*D9kSb?AM?9c&|p?4Hk1GE}_kSrm>%&sh`1ZXi2roS89pVL%yT}_S}x_N*Bk`wR(cRyP>)cweEZv4OJ3Xkj<7?+ zp|zp`IG@}oUyhsPS7zf6%@{!0%p1HvN!{!+O?8spUhakwHGVikP)L2-s4;mVMOK_**w~wvU44%N4yjzOkp{tZh4R>r{I$ZR--2SS zAW3X8Yaa^T>4Ea8%*8Ms%#VF;1hP(K2q{#WeWvR$MUBH+Ri1FP;`JDkw*JuhPZPn7 z`PS$wE>p58+q*(`Of8yc4oq(?`-8SK)vkuQ|0n)+`)SsSp*QyZ^94)4(;F^6pAT^q zw?9mnNuLj@^=k812A==hKAQO58GR)-?ds6mNf@sEYQt|{OM^NaG+};{x%fzv-gd62 zqUMZKUI=1xh_~P9#_vm1=vOC8LIGm=iFsdW9K|BoIWLG|d-_en)g1ku>~I!s12GV=jjA^#1_`X3nIZxSHIZTP4Zs zVk!{{aU_&*oeQL?{Frg_2DW*AGBK*vL{4RWZ=L|Qc|G&rooNGQ+a-!|Jgyl zJ0FnWA)o4SJ&+K=p^(VXIrtz`NDiw7Ke?aD5?NWZIqYdiE&JX*nPdhEOH7eaKjbg> z_`UW-%9wswjjz2WVI!Ri4^etW-{bSbm$dc#E>yuoHGa8t;&%3{m6xd#gRxgJPX!G+ z?Tk!Y@6Z?O)K-Cro;uwmB4ir*KWLRB2-M3B7ps&mY;A7>lAZ?KoB6^A6B!wSk zzu}u{qyx0SyI#!F*Lkf%Rv4U`O9aLiM|^3tU9J_C)>f|YtFJC8SrJP1Udq8swPB>F z4!-N_C!yQuNnZRwMn;zC=;+8N#g2OZE224G8%;UPGF>G$RJEWW;XElRbnQ6 zU$OH7pL2D6tKvoTmnwj-^QFc>f|1s-!p%Nz^Jtf}0DKi=L08DmGQnQ{MEG*pP zjpu^3jj*bbM13a5sD=5go_N#Ay}4%azpB6KG2}`?9dSoF9m5x7Jhhd$A4KM~2{`IHk2I;IpF`zX_= zX+(s0ush2Bx0KhDZ?~Qj1N2ElgyB3mc+(uh<7zqikBHx`*)@s$C4w52eCnk|=*`U= zVIP}6CBu~YFVRnwZK95ryLWTDPg4Pb?x;wCqW61ThKcEC##&MJkdvOpZ{{8M@n7*) zvWl=IS|Q5#l;cZ3vsR1P)+Aw%z)zz25BdEgj^eLI7u))IGglF8gB+ALYMUcYa% zpVWsr=CLGVxqc43`3>$YY{Tw&i=>G|`rd9T<^h_GS1G73KTFL<6BTnSCN~flmGDwo-XI&>9h!MiZBrjG0j%~uB_m`zGr-Q*jUNv*GG^D6{O!oh$I)? z_plrMu|}Mr!<=XxC9Yv{eptietiTb+cN?fGGy2Gg@zAwYu0lamdT12!_Y2-;0fam; zjVVM=&uNYKtClE!R{W*6f&Vr~mPS2~unB_j60RsZ3F-Ea{HIr5Ss623GnLI@Z91g$ zRHLF`9boMZkrQhiX)^CnJe97dRt`gG>WtSAi`thCn+iYj7BhFp`sE~}GEAel*64XJ zjpiej*?;WuBP*5e6LikuwOB^fx~fjU*6rsiYJ3P+gzqmx*aji7fdb{wNye}`_a{Msa(+x+G*Y}SwD9p>M z!Q0M{{S(UL%a3VZesA$hs4`tWMis9A92whd5+egCn8=+UC7sh41ZfphF}@w$fIW8g zwE&l1_RQ(z-<5Fc>RbIc3(X?DhR~gT74YopIfY~k->EdBqX&vIs&9#ZaIpVuH)F+v zh~Behp`%Rxg)TZ&(*LyK~O+s)$iW{QG&py{Yepkq) zQs%Hcv5ibyg06Hy+LtRTX%zinH!8WhngyOYRf&mqFz?1$#~l}~$BCJ;`M*sxM7ssG zjM!SZkP5Dwnx|jC#bTXx*~we{1Y6(G-XYfHE5qL*WYAM+eGiDtp5#$WjWF4HaKB0$ zFnLD@W4<<}wCsLP7;WKrk$pk=*L>avC3%ZuyY?0sW7#{LwKJ^^WK&ObHwuc z3$y6wE3CMG)Tl>W$&sDuOZBs|M8AS}ffs)hSJxF?oUkem9W1<#k>7pam$*lH#LLDS zd+`h|kHJxXiK(~Zhk+Znf3cm;Y-xX9KjHPJ+`BipHtdXLhM)>nGkZySsOs*x_Fv73 z5ln4xk~ICXBOEjLMZ#lfR{he)ACX}TZ_PfKZb9Q%?f;Im(i>B!HE~1i?oNWA+FYcOAdErSm#OLxg z$a{glfY-w6Bj)cY`(+)Rf8F_C4*l)5pLP;g_Et(X1W9uMt4#pG=Y#Qi(KUU4xIhRR zh2lVE6q}?Uof$l*8TEY3ThNbbe4fY5m6N+K0*CAwW-+4SNzNJ1m@+wBDA`X`mIX9b z&j9F0lFe#Ns{q~}t)^&rc>j?rI@kEugpUvomkVJR6ffrgVEl2o_Wo82WPlcGIqX*# z45lu%Fyy?xf)YxNSeJJQdd`cq+z@IVL%c54b{Q6@Of!dc+7Lo(W;6kQH25fN=T)3c!JPt;JH-6X$ro$x6fP!_rDI!v@vEo`6!IRBSWQp4O7jUMm zuo%k}SXR%|dqp1vOgJDTX&-|(I7L6bB@EpjO%&_C(ll$NTE6vm`r^Ua83VIV1{I1f z`?&nrv-1Xan~WOYeWo@zNGi{oqxS;01OMH*{CmGYTTG5S!)%mcQ#s)vf}2|S2~?=> zA?V&f+fz*hPT+kpI8PHP5SM)n=hH8R#;`R@XM;LX9Ae~)ziRG)FCs&c-H$8za?PPHcl~!vmtnzj6rnYN$k^HZ)y&>g40}PQ9{k9q*c{K zzsq-g+^Ej?caVSxw2gos?i4$%(Ex6I_I(vK`)DRk1t7*O-!8XbKj-|Mj6Awov0w$` zZu!EFF-@9A)ytnz%x>4&&`88!py)`hD;&HQBE3mlY>0F{PFP$1W{yc9qh>SEYA(gH zCJUpMX6xYz7u>EnHZf1>-s9CDP4lRby1p0ypR?)s+R)j%GWKu&LYf7QkjOWUrbRvB zMuqVS)m-ePV-nlX$UD(W1eYohz;*hWw)B3cdjvL%-PpBj6GiD;u^Gk6_IjH zZg2cTrB~k|MT2=faETeq&vf(MJuZCQ05MD#h9|nsI`LP^Q@a;^Ax6A#+M^jrNxJ`_ zIB@=xy3aKBMtBcyGdFP#Zb(%t1I1AMSQo?yJ;7Sg0kb~{x_cOKUb?f`bB06ox_3Y3 zZfHlO!v^rgvl5UmTB`n&hV10RXY7hH0;>%X>UlY$f19;JO($o7&os%UKwR|G!D$v; zx-sKvOlo`?*UXyk_{aB8-Q(0%G}o>v;;SD@;}naZb{D9;%KL@9sEnPYZvWlKB-^N( zB(cT8w0XF!$e-Djk>-APmpH=!5z!4BJKbw znpDKBl%a)jln476y4?Bhd_5dCo{f({a2@;T?$7hcgfaGTDQFv7sllHdwB)ZRxA!6e z@*g!^<7iQ8`k-R$POE=H=6>kG8(p=Z?CKohf2vf3bOs$s- z`n(^lLvS6&(I~noB!KG22RsEvvW3vX-Fz$eJOtfcoDb77I}a%gvbl^*TDjtvjL}rNd02|9xjo43Ebk@# z>g_DGxshysc=%KoDX69IEI%6JxvF$RW9TwKoDpPbqNTds_$=Fb;fl`C*{X+0kYg;q z-M9Zc+2t+RT|wZO`H@iuCazL8>?VQv{*py)Uk_8o8tB_t9liX@`)5>3q<9!B&;Qux zVf}tsT~bN{IpPMoVF(;A*!$V&O=}o;IOMr}bwzB&yglZ}&Bx&7Jn8Zk~)j z*|9TQM=JCDLoO$KTrQ`-o#UDdq&E!l0U*Emc3pO_4~>_V zc{ScS?Hk^j&G*OF>*c&-fdzA;+Us0mQoK25;*KwVu)M4*<2Z*LKG}cL@ON64D7D*PV1SJ=W-8w97U5t7oac-^qH4VqrSuZhvap#~I`ifJ_zgfVk zB>SZaBRZp93ARqO7sxgBvpeMV>2xR;@O3X={)U*f@|M@ud9}jIXt&+sa8cbZGn@aR zIhp*_odSH#i#urK@*jl<f6^njj`@%*|jXl5Dgv;`H5qwSM zksscs_I`jdEBP5FTFL+6X$!(6%YPCnvzWC# zLsalN%vi*u=JDxCs?&4%n;WJCI2<$J*NEBcw6=@vmu$kb6mMx=9P_lX2RI#vgtw!L zFLc<;1a290*=NIH{TXQ>&lnna)h^2m=q1AyO~*OY5RnO|Pd9#XClA@dlNuA+NxgxV zdtm6hVF@m7V!prg82?&by)9$is6qEj@Fg2DbHy{CFj*PwFAb)BFEcU7`dhlG+G_B=l4 zwE;&?H!8$XOMbm^SAMDeAjk+d5}fo)rI69=(_<$VaU|9(Qiha14HaDy#Pm(u*;JU$ zYNg5K2%9nMsmWn^!>HP?ky!kvjaBoxLJ7_nss%eT>VChrU#T;(EM1w(G;8IPh2X2) z)#=k?eYBa_;Hy00W9hA~#c1hU7tcwgEV@`equcgw@4A`$e(h-wE{qd;NxQ#_rP<>pnW)(94wG@a7Y$)wC z+A1sdf%o`Lr5GN~6rOZm*GsOcW?i;rhmm=3GGI0&Z`)iH##vOFTml0oR)L2?5&>C8 z*I5!(X5gcn>Zt9IF!8%Z4H?|||C7a(gm*Tr+qW7b8y_WXkVd~0+%O}Mh2TZ;@K@s% tm8Ot_`evR4JcT(3%z^DfZ2g4#&hMh`z-L|d^$Q|EMnX}%M$|a?{{Uop;dTH3 literal 12223 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 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..716c0dfdf6176f29fb8c59783e8b269e3817dae2 GIT binary patch literal 14505 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index 251fb9fb..05c42771 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -4,6 +4,6 @@ false true true - @android:color/transparent + @color/colorPrimaryDark diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 83afc2d0..9d1a6ed2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -240,7 +240,7 @@ 地图 末路之地 地图选项 - + 网格开关 标记开关 高级选项 BiomeData NBT diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 6ed6bfae..2819ebc3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,7 +1,7 @@ #689f38 - #212121 + #5C8D31 #1e88e5 #1a76c7 diff --git a/build.gradle b/build.gradle index 739d2749..9fa3a781 100644 --- a/build.gradle +++ b/build.gradle @@ -10,9 +10,9 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.0' - //classpath 'com.google.gms:google-services:4.2.0' + classpath 'com.google.gms:google-services:4.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d0a9b409..cc8f4525 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Dec 27 09:49:08 EST 2018 +#Mon Jan 21 19:59:31 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/leveldb/build.gradle b/leveldb/build.gradle index 70571592..a39859e1 100644 --- a/leveldb/build.gradle +++ b/leveldb/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 26 + compileSdkVersion 28 defaultConfig { minSdkVersion 16 @@ -14,7 +14,6 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk { abiFilters 'armeabi-v7a','arm64-v8a' - //abiFilters 'x86' } } release { diff --git a/tileview/build.gradle b/tileview/build.gradle index 830ac7dc..6211df78 100644 --- a/tileview/build.gradle +++ b/tileview/build.gradle @@ -20,7 +20,7 @@ android { } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(include: ['*.jar'], dir: 'libs') testImplementation 'junit:junit:4.12' implementation 'com.android.support:appcompat-v7:28.0.0' } From b4ef1a99e8dfec4b4d22e2d67c2833e9ad8eafcf Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Sat, 26 Jan 2019 15:53:03 +0800 Subject: [PATCH 20/83] * Refactored `Chunk` & `SubChunk`. --- app/build.gradle | 5 +- .../com/mithrilmania/blocktopograph/Log.java | 65 ++++- .../blocktopograph/WorldActivity.java | 60 +---- .../WorldActivityInterface.java | 5 +- .../blocktopograph/chunk/BedrockChunk.java | 187 ++++++++++++++ .../blocktopograph/chunk/Chunk.java | 183 ++++++-------- .../blocktopograph/chunk/ChunkManager.java | 4 +- .../blocktopograph/chunk/ChunkTag.java | 6 +- .../blocktopograph/chunk/NBTChunkData.java | 4 +- .../blocktopograph/chunk/PocketChunk.java | 184 ++++++++++++++ .../blocktopograph/chunk/TempChunk.java | 186 ++++++++++++++ .../blocktopograph/chunk/Version.java | 42 ++-- .../blocktopograph/chunk/VoidChunk.java | 65 +++++ .../chunk/terrain/AquaTerrainSubChunk.java | 138 ++++++++++ .../chunk/terrain/PreAquaTerrainSubChunk.java | 56 +++++ .../chunk/terrain/TerrainChunkData.java | 19 +- .../chunk/terrain/TerrainSubChunk.java | 60 +++++ .../chunk/terrain/V0_9_TerrainChunkData.java | 9 +- .../chunk/terrain/V1_0_TerrainChunkData.java | 13 +- .../chunk/terrain/V1_1_TerrainChunkData.java | 12 +- .../terrain/V1_2_Plus_TerrainChunkData.java | 17 +- .../blocktopograph/map/Block.java | 70 +++--- .../blocktopograph/map/MCTileProvider.java | 24 +- .../blocktopograph/map/MapFragment.java | 28 +-- .../blocktopograph/map/MarkerAsyncTask.java | 9 +- .../map/renderer/BiomeRenderer.java | 16 +- .../map/renderer/BlockLightRenderer.java | 19 +- .../map/renderer/CaveRenderer.java | 139 +++++----- .../map/renderer/ChessPatternRenderer.java | 2 +- .../map/renderer/DebugRenderer.java | 2 +- .../map/renderer/GrassRenderer.java | 10 +- .../map/renderer/HeightmapRenderer.java | 27 +- .../map/renderer/MapRenderer.java | 3 +- .../map/renderer/NetherRenderer.java | 178 +++++-------- .../map/renderer/SatelliteRenderer.java | 137 ++++------ .../map/renderer/SlimeChunkRenderer.java | 26 +- .../map/renderer/XRayRenderer.java | 72 +++--- .../blocktopograph/nbt/EditorFragment.java | 237 +++++++++--------- leveldb/src/main/jni/com_litl_leveldb_DB.cc | 17 +- 39 files changed, 1546 insertions(+), 790 deletions(-) create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/BedrockChunk.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/PocketChunk.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/TempChunk.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/VoidChunk.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/AquaTerrainSubChunk.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/PreAquaTerrainSubChunk.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainSubChunk.java diff --git a/app/build.gradle b/app/build.gradle index 67e3ccf0..e9212635 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,8 +6,8 @@ android { applicationId 'rbq2012.blocktopograph' minSdkVersion 16 targetSdkVersion 28 - versionCode 11 - versionName "1.8" + versionCode 12 + versionName "1.8.2" } buildTypes { @@ -35,6 +35,7 @@ dependencies { implementation 'com.github.bmelnychuk:atv:1.2.8' //core is the new recommended alias for analytics implementation 'com.google.firebase:firebase-core:16.0.6' + implementation 'org.jetbrains:annotations-java5:15.0' } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/Log.java b/app/src/main/java/com/mithrilmania/blocktopograph/Log.java index 0ef7dd46..dcc4cb17 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/Log.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/Log.java @@ -1,26 +1,81 @@ package com.mithrilmania.blocktopograph; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; + +import com.google.firebase.analytics.FirebaseAnalytics; + public class Log { //TODO This is kind of lazy, but repeating the Log.d(*msg*) everywhere is obnoxious //TODO log only if debug mode is on? - public static final String LOG_TAG = "Blocktopograph"; + private static final String LOG_TAG = "Blocktopo"; - public static void i(String msg){ + private static FirebaseAnalytics mFirebaseAnalytics; + + public static void i(@NonNull String msg) { android.util.Log.i(LOG_TAG, msg); } - public static void d(String msg){ + public static void d(@NonNull String msg) { android.util.Log.d(LOG_TAG, msg); } - public static void w(String msg){ + public static void w(@NonNull String msg) { android.util.Log.w(LOG_TAG, msg); } - public static void e(String msg){ + public static void e(@NonNull String msg) { android.util.Log.e(LOG_TAG, msg); } + synchronized static public FirebaseAnalytics getFirebaseAnalytics(@NonNull Context context) { + if (mFirebaseAnalytics == null) { + mFirebaseAnalytics = FirebaseAnalytics.getInstance(context); + + //don't measure the test devices in analytics + mFirebaseAnalytics.setAnalyticsCollectionEnabled(!BuildConfig.DEBUG); + } + return mFirebaseAnalytics; + } + + public static void logFirebaseEvent(@NonNull Context context, @NonNull CustomFirebaseEvent firebaseEvent) { + getFirebaseAnalytics(context).logEvent(firebaseEvent.eventID, new Bundle()); + } + + public static void logFirebaseEvent(@NonNull Context context, @NonNull CustomFirebaseEvent firebaseEvent, @NonNull Bundle eventContent) { + getFirebaseAnalytics(context).logEvent(firebaseEvent.eventID, eventContent); + } + + // Firebase events, these are meant to be as anonymous as possible, + // pure counters for usage analytics. + // Do not remove! Removing analytics in a fork skews the results to the original userbase! + // Forks should stay in touch, all new features are welcome. + //Wonder why you put these things in a certain Activity. + //That should be global. + public enum CustomFirebaseEvent { + + //max 32 chars: "0123456789abcdef0123456789abcdef" + MAPFRAGMENT_OPEN("map_fragment_open"), + MAPFRAGMENT_RESUME("map_fragment_resume"), + MAPFRAGMENT_RESET("map_fragment_reset"), + NBT_EDITOR_OPEN("nbt_editor_open"), + NBT_EDITOR_SAVE("nbt_editor_save"), + WORLD_OPEN("world_open"), + WORLD_RESUME("world_resume"), + GPS_PLAYER("gps_player"), + GPS_MULTIPLAYER("gps_multiplayer"), + GPS_SPAWN("gps_spawn"), + GPS_MARKER("gps_marker"), + GPS_COORD("gps_coord"); + + public final String eventID; + + CustomFirebaseEvent(String eventID) { + this.eventID = eventID; + } + } + } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java index 4a5a2611..834839d2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java @@ -26,6 +26,7 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; +import android.widget.Toast; import com.google.firebase.analytics.FirebaseAnalytics; import com.mithrilmania.blocktopograph.map.Dimension; @@ -52,55 +53,6 @@ public class WorldActivity extends AppCompatActivity private MapFragment mapFragment; - private FirebaseAnalytics mFirebaseAnalytics; - - synchronized public FirebaseAnalytics getFirebaseAnalytics() { - if (mFirebaseAnalytics == null) { - mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); - - //don't measure the test devices in analytics - mFirebaseAnalytics.setAnalyticsCollectionEnabled(!BuildConfig.DEBUG); - } - return mFirebaseAnalytics; - } - - // Firebase events, these are meant to be as anonymous as possible, - // pure counters for usage analytics. - // Do not remove! Removing analytics in a fork skews the results to the original userbase! - // Forks should stay in touch, all new features are welcome. - public enum CustomFirebaseEvent { - - //max 32 chars: "0123456789abcdef0123456789abcdef" - MAPFRAGMENT_OPEN("map_fragment_open"), - MAPFRAGMENT_RESUME("map_fragment_resume"), - MAPFRAGMENT_RESET("map_fragment_reset"), - NBT_EDITOR_OPEN("nbt_editor_open"), - NBT_EDITOR_SAVE("nbt_editor_save"), - WORLD_OPEN("world_open"), - WORLD_RESUME("world_resume"), - GPS_PLAYER("gps_player"), - GPS_MULTIPLAYER("gps_multiplayer"), - GPS_SPAWN("gps_spawn"), - GPS_MARKER("gps_marker"), - GPS_COORD("gps_coord"); - - public final String eventID; - - CustomFirebaseEvent(String eventID) { - this.eventID = eventID; - } - } - - @Override - public void logFirebaseEvent(CustomFirebaseEvent firebaseEvent) { - getFirebaseAnalytics().logEvent(firebaseEvent.eventID, new Bundle()); - } - - @Override - public void logFirebaseEvent(CustomFirebaseEvent firebaseEvent, Bundle eventContent) { - getFirebaseAnalytics().logEvent(firebaseEvent.eventID, eventContent); - } - @Override public void showActionBar() { ActionBar bar = getSupportActionBar(); @@ -144,8 +96,12 @@ protected void onCreate(Bundle savedInstanceState) { ? getIntent().getSerializableExtra(World.ARG_WORLD_SERIALIZED) : savedInstanceState.getSerializable(World.ARG_WORLD_SERIALIZED)); if (world == null) { + Toast.makeText(this, "cannot open: world == null", Toast.LENGTH_SHORT).show(); //WTF, try going back to the previous screen by finishing this hopeless activity... finish(); + //Finish does not guarantee codes below won't be executed! + //Shit + return; } @@ -197,6 +153,8 @@ Send anonymous world data to the Firebase (Google analytics for Android) server. TODO BigQuery is not configured yet, @mithrilmania (author of Blocktopograph) is working on it! + Ahhh good idea anyway... Then why didn't you continue it. + *link to results will be included here for reference when @mithrilmania is done* */ String worldSeed = String.valueOf(this.world.getWorldSeed()); @@ -208,7 +166,7 @@ Send anonymous world data to the Firebase (Google analytics for Android) server. // anonymous global counter of opened worlds - logFirebaseEvent(CustomFirebaseEvent.WORLD_OPEN, bundle); + Log.logFirebaseEvent(this, Log.CustomFirebaseEvent.WORLD_OPEN, bundle); // Open the world-map as default content @@ -239,7 +197,7 @@ public void onResume() { super.onResume(); // anonymous global counter of resumed world-activities - logFirebaseEvent(CustomFirebaseEvent.WORLD_RESUME); + Log.logFirebaseEvent(this, Log.CustomFirebaseEvent.WORLD_RESUME); try { this.world.resume(); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivityInterface.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivityInterface.java index 1f1af8c3..c701f354 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivityInterface.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivityInterface.java @@ -3,6 +3,7 @@ import android.os.Bundle; import android.view.ViewGroup; + import com.mithrilmania.blocktopograph.chunk.ChunkData; import com.mithrilmania.blocktopograph.chunk.NBTChunkData; import com.mithrilmania.blocktopograph.map.Dimension; @@ -27,10 +28,6 @@ public interface WorldActivityInterface { void addMarker(AbstractMarker marker); - void logFirebaseEvent(WorldActivity.CustomFirebaseEvent firebaseEvent); - - void logFirebaseEvent(WorldActivity.CustomFirebaseEvent firebaseEvent, Bundle eventContent); - void showActionBar(); void hideActionBar(); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/BedrockChunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/BedrockChunk.java new file mode 100644 index 00000000..70675db6 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/BedrockChunk.java @@ -0,0 +1,187 @@ +package com.mithrilmania.blocktopograph.chunk; + +import android.graphics.Color; +import android.support.annotation.Nullable; + +import com.mithrilmania.blocktopograph.WorldData; +import com.mithrilmania.blocktopograph.chunk.terrain.TerrainSubChunk; +import com.mithrilmania.blocktopograph.map.Biome; +import com.mithrilmania.blocktopograph.map.Dimension; +import com.mithrilmania.blocktopograph.util.Noise; + +import java.nio.ByteBuffer; + +public final class BedrockChunk extends Chunk { + + private static final int POS_HEIGHTMAP = 0; + private static final int POS_BIOME_DATA = 0x200; + public static final int DATA2D_LENGTH = 0x300; + + private boolean mHasBlockLight; + private final boolean[] mVoidList; + private final boolean[] mErrorList; + private final TerrainSubChunk[] mTerrainSubChunks; + private volatile ByteBuffer data2D; + + BedrockChunk(WorldData worldData, Version version, int chunkX, int chunkZ, Dimension dimension) { + super(worldData, version, chunkX, chunkZ, dimension); + mVoidList = new boolean[16]; + mErrorList = new boolean[16]; + mTerrainSubChunks = new TerrainSubChunk[16]; + load2dData(); + mHasBlockLight = true; + } + + private void load2dData() { + if (data2D == null) { + try { + byte[] rawData = mWorldData.get().getChunkData(mChunkX, mChunkZ, ChunkTag.DATA_2D, mDimension, (byte) 0, false); + if (rawData == null) { + mIsError = true; + mIsVoid = true; + return; + } + this.data2D = ByteBuffer.wrap(rawData); + } catch (Exception e) { + mIsError = true; + mIsVoid = true; + } + } + } + + @Nullable + private TerrainSubChunk getSubChunk(int which) { + if (mIsError || mVoidList[which]) return null; + TerrainSubChunk ret = mTerrainSubChunks[which]; + if (ret == null) { + byte[] raw; + try { + raw = mWorldData.get().getChunkData(mChunkX, mChunkZ, + ChunkTag.TERRAIN, mDimension, (byte) which, true); + if (raw == null) { + mVoidList[which] = true; + return null; + } + } catch (Exception e) { + e.printStackTrace(); + mErrorList[which] = true; + mVoidList[which] = true; + return null; + } + ret = TerrainSubChunk.create(raw); + if (ret == null || ret.isError()) { + mVoidList[which] = true; + mErrorList[which] = true; + ret = null; + } else if (!ret.hasBlockLight()) mHasBlockLight = false; + mTerrainSubChunks[which] = ret; + } + return ret; + } + + private int get2dOffset(int x, int z) { + return (z << 4) | x; + } + + @Override + public boolean supportsBlockLightValues() { + return mHasBlockLight; + } + + @Override + public int getHeightLimit() { + return 256; + } + + @Override + public int getHeightMapValue(int x, int z) { + short h = data2D.getShort(POS_HEIGHTMAP + (get2dOffset(x, z) << 1)); + return ((h & 0xff) << 8) | ((h >> 8) & 0xff); + } + + @Override + public int getBiome(int x, int z) { + return data2D.get(POS_BIOME_DATA + get2dOffset(x, z)); + } + + private int getNoise(int base, int x, int z) { + // noise values are between -1 and 1 + // 0.0001 is added to the coordinates because integer values result in 0 + double oct1 = Noise.noise( + ((double) (mChunkX * 16 + x) / 100.0) + 0.0001, + ((double) (mChunkZ * 16 + z) / 100.0) + 0.0001); + double oct2 = Noise.noise( + ((double) (mChunkX * 16 + x) / 20.0) + 0.0001, + ((double) (mChunkZ * 16 + z) / 20.0) + 0.0001); + double oct3 = Noise.noise( + ((double) (mChunkX * 16 + x) / 3.0) + 0.0001, + ((double) (mChunkZ * 16 + z) / 3.0) + 0.0001); + return (int) (base + 60 + (40 * oct1) + (14 * oct2) + (6 * oct3)); + } + + @Override + public int getGrassColor(int x, int z) { + Biome biome = Biome.getBiome(getBiome(x, z) & 0xff); + int r = getNoise(30 + (biome.color.red / 5), x, z); + int g = getNoise(120 + (biome.color.green / 5), x, z); + int b = getNoise(30 + (biome.color.blue / 5), x, z); + return Color.rgb(r, g, b); + } + + @Override + public int getBlockRuntimeId(int x, int y, int z) { + if (x >= 16 || y >= 256 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return 0; + TerrainSubChunk subChunk = getSubChunk(y >> 4); + if (subChunk == null) return 0; + return subChunk.getBlockRuntimeId(x, y & 0xf, z); + } + + @Override + public int getBlockLightValue(int x, int y, int z) { + if (!mHasBlockLight || x >= 16 || y >= 256 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return 0; + TerrainSubChunk subChunk = getSubChunk(y >> 4); + if (subChunk == null) return 0; + return subChunk.getBlockLightValue(x, y & 0xf, z); + } + + @Override + public int getSkyLightValue(int x, int y, int z) { + if (x >= 16 || y >= 256 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return 0; + TerrainSubChunk subChunk = getSubChunk(y >> 4); + if (subChunk == null) return 0; + return subChunk.getSkyLightValue(x, y & 0xf, z); + } + + @Override + public int getHighestBlockYUnderAt(int x, int z, int y) { + if (x >= 16 || y >= 256 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return -1; + TerrainSubChunk subChunk; + for (int which = y >> 4; which >= 0; which--) { + subChunk = getSubChunk(which); + if (subChunk == null) continue; + for (int innerY = (which == (y >> 4)) ? y & 0xf : 15; innerY >= 0; innerY--) { + if (subChunk.getBlockRuntimeId(x, innerY, z) != 0) return (which << 4) | innerY; + } + } + return -1; + } + + @Override + public int getCaveYUnderAt(int x, int z, int y) { + if (x >= 16 || y >= 256 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return -1; + TerrainSubChunk subChunk; + for (int which = y >> 4; which >= 0; which--) { + subChunk = getSubChunk(which); + if (subChunk == null) continue; + for (int innerY = (which == (y >> 4)) ? y & 0xf : 15; innerY >= 0; innerY--) { + if (subChunk.getBlockRuntimeId(x, innerY, z) == 0) return (which << 4) | innerY; + } + } + return -1; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java index 1dce4d73..737fd7bf 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Chunk.java @@ -1,139 +1,106 @@ package com.mithrilmania.blocktopograph.chunk; -import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.WorldData; -import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; import java.lang.ref.WeakReference; -import java.util.concurrent.atomic.AtomicReferenceArray; - -public class Chunk { - - public final WeakReference worldData; - - public final int x, z; - public final Dimension dimension; - - private Version version; - - private AtomicReferenceArray terrain; - - private volatile NBTChunkData entity, blockEntity; - - public Chunk(WorldData worldData, int x, int z, Dimension dimension) { - this.worldData = new WeakReference<>(worldData); - this.x = x; - this.z = z; - this.dimension = dimension; - terrain = new AtomicReferenceArray<>(16); - Log.e("new Chunk " + x + "," + z); +public abstract class Chunk { + + protected final WeakReference mWorldData; + protected final Version mVersion; + public final int mChunkX; + public final int mChunkZ; + public final Dimension mDimension; + protected NBTChunkData mEntity; + protected NBTChunkData mTileEntity; + boolean mIsVoid; + boolean mIsError; + + Chunk(WorldData worldData, Version version, int chunkX, int chunkZ, Dimension dimension) { + mWorldData = new WeakReference<>(worldData); + mVersion = version; + mChunkX = chunkX; + mChunkZ = chunkZ; + mDimension = dimension; + mIsVoid = false; + mIsError = false; + try { + mEntity = version.createEntityChunkData(this); + mTileEntity = version.createBlockEntityChunkData(this); + } catch (Version.VersionException e) { + e.printStackTrace(); + } } - public TerrainChunkData getTerrain(byte subChunk) throws Version.VersionException { - TerrainChunkData data = terrain.get(subChunk & 0xff); - if (data == null) { - data = this.getVersion().createTerrainChunkData(this, subChunk); - terrain.set(subChunk & 0xff, data); + public static Chunk create(WorldData worldData, int chunkX, int chunkZ, Dimension dimension) { + Version version; + try { + byte[] data = worldData.getChunkData(chunkX, chunkZ, ChunkTag.VERSION, dimension, (byte) 0, false); + version = Version.getVersion(data); + } catch (WorldData.WorldDBLoadException | WorldData.WorldDBException e) { + e.printStackTrace(); + version = Version.ERROR; + } + Chunk chunk; + switch (version) { + case ERROR: + case OLD_LIMITED: + chunk = new VoidChunk(worldData, version, chunkX, chunkZ, dimension); + chunk.mIsError = true; + break; + case v0_9: + chunk = new PocketChunk(worldData, version, chunkX, chunkZ, dimension); + break; + case V1_0: + case V1_1: + case V1_2_PLUS: + chunk = new BedrockChunk(worldData, version, chunkX, chunkZ, dimension); + break; + case NULL: + default: + chunk = new VoidChunk(worldData, version, chunkX, chunkZ, dimension); } - return data; + return chunk; } - public NBTChunkData getEntity() throws Version.VersionException { - if (entity == null) entity = this.getVersion().createEntityChunkData(this); - return entity; + public final WorldData getWorldData() { + return mWorldData.get(); } - - public NBTChunkData getBlockEntity() throws Version.VersionException { - if (blockEntity == null) blockEntity = this.getVersion().createBlockEntityChunkData(this); - return blockEntity; + public final boolean isVoid() { + return mIsVoid; } - public Version getVersion() { - ///Meow - ///if (worldData.isMeow()) return Version.V1_1; + public final boolean isError() { + return mIsError; + } - if (this.version == null) try { - WorldData worldData = this.worldData.get(); - if (worldData == null) return Version.ERROR; - byte[] data = worldData.getChunkData(x, z, ChunkTag.VERSION, dimension, (byte) 0, false); - this.version = Version.getVersion(data); - } catch (WorldData.WorldDBLoadException | WorldData.WorldDBException e) { - e.printStackTrace(); - this.version = Version.ERROR; - } + abstract public boolean supportsBlockLightValues(); - return this.version; - } + abstract public int getHeightLimit(); + abstract public int getHeightMapValue(int x, int z); - //TODO: should we use the heightmap??? - public int getHighestBlockYAt(int x, int z) throws Version.VersionException { - ///Meow - ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, 255, z); + abstract public int getBiome(int x, int z); - Version cVersion = getVersion(); - TerrainChunkData data; - for (int subChunk = cVersion.subChunks - 1; subChunk >= 0; subChunk--) { - data = this.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) continue; + abstract public int getGrassColor(int x, int z); - for (int y = cVersion.subChunkHeight; y >= 0; y--) { - if (data.getBlockTypeId(x & 15, y, z & 15) != 0) - return (subChunk * cVersion.subChunkHeight) + y; - } - } - return -1; - } + abstract public int getBlockRuntimeId(int x, int y, int z); - public int getHighestBlockYUnderAt(int x, int z, int y) throws Version.VersionException { - ///Meow - ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); + abstract public int getBlockLightValue(int x, int y, int z); - Version cVersion = getVersion(); - int offset = y % cVersion.subChunkHeight; - int subChunk = y / cVersion.subChunkHeight; - TerrainChunkData data; + abstract public int getSkyLightValue(int x, int y, int z); - for (; subChunk >= 0; subChunk--) { - data = this.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) continue; + abstract public int getHighestBlockYUnderAt(int x, int z, int y); - for (y = offset; y >= 0; y--) { - if (data.getBlockTypeId(x & 15, y, z & 15) != 0) - return (subChunk * cVersion.subChunkHeight) + y; - } + abstract public int getCaveYUnderAt(int x, int z, int y); - //start at the top of the next chunk! (current offset might differ) - offset = cVersion.subChunkHeight - 1; - } - return -1; + public final NBTChunkData getEntity() { + return mEntity; } - public int getCaveYUnderAt(int x, int z, int y) throws Version.VersionException { - ///Meow - ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); - - Version cVersion = getVersion(); - int offset = y % cVersion.subChunkHeight; - int subChunk = y / cVersion.subChunkHeight; - TerrainChunkData data; - - for (; subChunk >= 0; subChunk--) { - data = this.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) continue; - for (y = offset; y >= 0; y--) { - if (data.getBlockTypeId(x & 15, y, z & 15) == 0) - return (subChunk * cVersion.subChunkHeight) + y; - } - - //start at the top of the next chunk! (current offset might differ) - offset = cVersion.subChunkHeight - 1; - } - return -1; + public final NBTChunkData getBlockEntity() { + return mTileEntity; } - - } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java index b6e84941..31a5bb69 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkManager.java @@ -13,7 +13,7 @@ public class ChunkManager { private LruCache chunks = new LruCache(256) { @Override protected Chunk create(Key key) { - return new Chunk(worldData.get(), key.x, key.z, key.dim); + return Chunk.create(worldData.get(), key.x, key.z, key.dim); } }; @@ -37,7 +37,7 @@ static class Key { public int x, z; Dimension dim; - public Key(int x, int z, Dimension dim) { + Key(int x, int z, Dimension dim) { this.x = x; this.z = z; this.dim = dim; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkTag.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkTag.java index 50344d49..5ea022fc 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkTag.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/ChunkTag.java @@ -2,7 +2,7 @@ /** * Reference from Tommaso Checchi (/u/mojang_tommo), MCPE developer: - * https://www.reddit.com/r/MCPE/comments/5cw2tm/level_format_changes_in_mcpe_0171_100/d9zv9s8/ + * https://www.reddit.com/r/MCPE/comments/5cw2tm/level_format_changes_in_mcpe_0171_100/d9zv9s8/ */ public enum ChunkTag { @@ -15,13 +15,13 @@ public enum ChunkTag { PENDING_TICKS((byte) 0x33),//TODO untested BLOCK_EXTRA_DATA((byte) 0x34),//TODO untested, 32768 bytes, used for top-snow. BIOME_STATE((byte) 0x35),//TODO untested + GENERATOR_STAGE((byte) 0x36), VERSION((byte) 0x76); - public final byte dataID; - ChunkTag(byte dataID){ + ChunkTag(byte dataID) { this.dataID = dataID; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java index c40bf6c4..5d78fbf6 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/NBTChunkData.java @@ -23,7 +23,7 @@ public NBTChunkData(Chunk chunk, ChunkTag dataType) { public void load() throws WorldData.WorldDBLoadException, WorldData.WorldDBException, IOException { Chunk chunk = this.chunk.get(); - loadFromByteArray(chunk.worldData.get().getChunkData(chunk.x, chunk.z, dataType, chunk.dimension, (byte) 0, false)); + loadFromByteArray(chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, dataType, chunk.mDimension, (byte) 0, false)); } public void loadFromByteArray(byte[] data) throws IOException { @@ -34,7 +34,7 @@ public void write() throws WorldData.WorldDBException, IOException { if (this.tags == null) this.tags = new ArrayList<>(); byte[] data = DataConverter.write(this.tags); Chunk chunk = this.chunk.get(); - chunk.worldData.get().writeChunkData(chunk.x, chunk.z, this.dataType, chunk.dimension, (byte) 0, false, data); + chunk.getWorldData().writeChunkData(chunk.mChunkX, chunk.mChunkZ, this.dataType, chunk.mDimension, (byte) 0, false, data); } @Override diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/PocketChunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/PocketChunk.java new file mode 100644 index 00000000..94f4e9b5 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/PocketChunk.java @@ -0,0 +1,184 @@ +package com.mithrilmania.blocktopograph.chunk; + +import android.graphics.Color; + +import com.mithrilmania.blocktopograph.WorldData; +import com.mithrilmania.blocktopograph.map.Dimension; + +import java.nio.ByteBuffer; + +public final class PocketChunk extends Chunk { + + + private static final int POS_BLOCK_IDS = 0; + private static final int POS_META_DATA = 0x8000; + private static final int POS_SKY_LIGHT = 0xc000; + private static final int POS_BLOCK_LIGHT = 0x10000; + //Isn't this "dirty table"? + //Seems Proto got it wrong... + private static final int POS_HEIGHTMAP = 0x14000; + private static final int POS_BIOME_DATA = 0x14100; + private static final int LENGTH = 0x14500; + + private volatile ByteBuffer mData; + + PocketChunk(WorldData worldData, Version version, int chunkX, int chunkZ, Dimension dimension) { + super(worldData, version, chunkX, chunkZ, dimension); + tryLoad(); + } + + private void tryLoad() { + if (mData == null) { + try { + byte[] rawData = mWorldData.get().getChunkData(mChunkX, mChunkZ, ChunkTag.V0_9_LEGACY_TERRAIN, mDimension, (byte) 0, false); + if (rawData == null) { + mIsVoid = true; + return; + } + mData = ByteBuffer.allocate(rawData.length); + mData.put(rawData, 0, rawData.length); + mIsVoid = false; + } catch (Exception e) { + mIsError = true; + mIsVoid = true; + } + } + } + + public void createEmpty() { + byte[] chunk = new byte[LENGTH]; + int x, y, z, i = 0; + byte bedrock = (byte) 7; + byte sandstone = (byte) 24; + + //generate super basic terrain (one layer of bedrock, 31 layers of sandstone) + //Emmm but why + for (x = 0; x < 16; x++) { + for (z = 0; z < 16; z++) { + for (y = 0; y < 128; y++, i++) { + chunk[i] = (y == 0 ? bedrock : (y < 32 ? sandstone : 0)); + } + } + } + + //fill meta-data with 0 + for (; i < POS_SKY_LIGHT; i++) { + chunk[i] = 0; + } + + //fill blocklight with 0xff + for (; i < POS_BLOCK_LIGHT; i++) { + chunk[i] = (byte) 0xff; + } + + //fill block-light with 0xff + for (; i < POS_HEIGHTMAP; i++) { + chunk[i] = (byte) 0xff; + } + + //fill heightmap + for (; i < POS_BIOME_DATA; i++) { + chunk[i] = 32; + } + + //fill biome data + for (; i < LENGTH; ) { + chunk[i++] = 1;//biome: plains + chunk[i++] = (byte) 42;//r + chunk[i++] = (byte) 42;//g + chunk[i++] = (byte) 42;//b + } + + this.mData = ByteBuffer.wrap(chunk); + } + + private int getOffset(int x, int y, int z) { + // I prefer shifts than multiplies, prefer "bit or" than plus. + // I know compiler and Android runtime can optimize + // but this is cool, right?! + // No, not at all. I saw you filled the shift wrong and debugged for an hour one day. + return (((x << 4) | z) << 7) | y; + } + + private int get2dOffset(int x, int z) { + return (z << 4) | x; + } + + @Override + public boolean supportsBlockLightValues() { + return false; + } + + @Override + public int getHeightLimit() { + return 128; + } + + @Override + public int getHeightMapValue(int x, int z) { + if (x >= 16 || z >= 16 || x < 0 || z < 0 || mIsVoid) + return 0; + //There's no height map saved here! + //Do it the hard way! + for (int offset = POS_BLOCK_IDS + getOffset(x, 127, z), y = 127; y >= 0; y--, offset--) { + if (mData.get(offset) != 0) return y + 1; + } + return 0; + } + + @Override + public int getBiome(int x, int z) { + return mData.get(POS_BIOME_DATA + (get2dOffset(x, z) << 2)); + } + + @Override + public int getGrassColor(int x, int z) { + int offset = POS_BIOME_DATA + (get2dOffset(x, z) << 2); + return Color.rgb(mData.get(offset + 1) & 0xff, mData.get(offset + 2) & 0xff, mData.get(offset + 3) & 0xff); + } + + @Override + public int getBlockRuntimeId(int x, int y, int z) { + if (x >= 16 || y >= 128 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return 0; + int offset = getOffset(x, y, z); + int id = mData.get(POS_BLOCK_IDS + offset) & 0xff; + int data = mData.get(POS_META_DATA + (offset >>> 1)); + data = (offset & 1) == 1 ? ((data >>> 4) & 0xf) : (data & 0xf); + return (id << 8) | data; + } + + @Override + public int getBlockLightValue(int x, int y, int z) { + if (x >= 16 || y >= 128 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return 0; + int offset = getOffset(x, y, z); + int dualData = mData.get(POS_BLOCK_LIGHT + (offset >>> 1)); + return (offset & 1) == 1 ? (dualData >>> 4) & 0xf : dualData & 0xf; + } + + @Override + public int getSkyLightValue(int x, int y, int z) { + if (x >= 16 || y >= 128 || z >= 16 || x < 0 || y < 0 || z < 0 || mIsVoid) + return 0; + int offset = getOffset(x, y, z); + int dualData = mData.get(POS_SKY_LIGHT + (offset >>> 1)); + return (offset & 1) == 1 ? (dualData >>> 4) & 0xf : dualData & 0xf; + } + + @Override + public int getHighestBlockYUnderAt(int x, int z, int y) { + for (int yy = y; yy >= 0; yy--) { + if (getBlockRuntimeId(x & 0xf, yy, z & 0xf) != 0) return yy; + } + return -1; + } + + @Override + public int getCaveYUnderAt(int x, int z, int y) { + for (int yy = y; yy >= 0; yy--) { + if (getBlockRuntimeId(x & 0xf, yy, z & 0xf) == 0) return yy; + } + return -1; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/TempChunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/TempChunk.java new file mode 100644 index 00000000..e759041e --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/TempChunk.java @@ -0,0 +1,186 @@ +package com.mithrilmania.blocktopograph.chunk; + +import com.mithrilmania.blocktopograph.Log; +import com.mithrilmania.blocktopograph.WorldData; +import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; +import com.mithrilmania.blocktopograph.map.Dimension; + +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicReferenceArray; + + +public class TempChunk { + +// public final WeakReference worldData; +// +// public final int x, z; +// public final Dimension dimension; +// +// private Version version; +// +// private AtomicReferenceArray terrain; +// +// private volatile NBTChunkData entity, blockEntity; +// +// public TempChunk(WorldData worldData, int x, int z, Dimension dimension) { +// this.worldData = new WeakReference<>(worldData); +// this.x = x; +// this.z = z; +// this.dimension = dimension; +// try { +// byte[] data = worldData.getChunkData(x, z, ChunkTag.VERSION, dimension, (byte) 0, false); +// this.version = Version.getVersion(data); +// } catch (WorldData.WorldDBLoadException | WorldData.WorldDBException e) { +// e.printStackTrace(); +// this.version = Version.ERROR; +// } +// terrain = new AtomicReferenceArray<>(16); +// Log.e("new Chunk " + x + "," + z); +// } +// +// public boolean load2dData() throws Version.VersionException { +// TerrainChunkData terr = getTerrain((byte) 0); +// if (null == terr) return false; +// else return terr.load2DData(); +// } +// +// public int getHeightMapValue(int x, int z) throws Version.VersionException { +// return getTerrain((byte) 0).getHeightMapValue(x, z); +// } +// +// public int getBiome(int x, int z) throws Version.VersionException { +// return getTerrain((byte) 0).getBiome(x, z); +// } +// +// public int getGrassR(int x, int z) throws Version.VersionException { +// return getTerrain((byte) 0).getGrassR(x, z); +// } +// +// public int getGrassG(int x, int z) throws Version.VersionException { +// return getTerrain((byte) 0).getGrassG(x, z); +// } +// +// public int getGrassB(int x, int z) throws Version.VersionException { +// return getTerrain((byte) 0).getGrassB(x, z); +// } +// +// public int getBlockLightValue(int x, int y, int z) throws Version.VersionException { +// return getTerrain((byte) (y / version.subChunkHeight)).getBlockLightValue(x, y % version.subChunkHeight, z); +// } +// +// public int getBlockTypeId(int x, int y, int z) throws Version.VersionException { +// return getTerrain((byte) (y / version.subChunkHeight)).getBlockTypeId(x, y % version.subChunkHeight, z); +// } +// +// public int getBlockData(int x, int y, int z) throws Version.VersionException { +// return getTerrain((byte) (y / version.subChunkHeight)).getBlockData(x, y % version.subChunkHeight, z); +// } +// +// public boolean supportsBlockLightValues() throws Version.VersionException { +// return getTerrain((byte) 0).supportsBlockLightValues(); +// } +// +// public int getHeightLimit() { +// switch (version) { +// //This one in lower case good trip leaving for next author. +// case v0_9: +// case OLD_LIMITED: +// return 128; +// case V1_0: +// case V1_1: +// case V1_2_PLUS: +// return 256; +// default: +// return 0; +// } +// } +// +// private TerrainChunkData getTerrain(byte subChunk) throws Version.VersionException { +// TerrainChunkData data = terrain.get(subChunk & 0xff); +// if (data == null) { +// data = this.getVersion().createTerrainChunkData(this, subChunk); +// terrain.set(subChunk & 0xff, data); +// } +// return data; +// } +// +// public NBTChunkData getEntity() throws Version.VersionException { +// if (entity == null) entity = this.getVersion().createEntityChunkData(this); +// return entity; +// } +// +// +// public NBTChunkData getBlockEntity() throws Version.VersionException { +// if (blockEntity == null) blockEntity = this.getVersion().createBlockEntityChunkData(this); +// return blockEntity; +// } +// +// public Version getVersion() { +// return version; +// } +// +// +// //TODO: should we use the heightmap??? +// public int getHighestBlockYAt(int x, int z) throws Version.VersionException { +// TerrainChunkData data; +// for (int subChunk = version.subChunks - 1; subChunk >= 0; subChunk--) { +// data = this.getTerrain((byte) subChunk); +// if (data == null || !data.loadTerrain()) continue; +// +// for (int y = version.subChunkHeight; y >= 0; y--) { +// if (data.getBlockTypeId(x & 15, y, z & 15) != 0) +// return (subChunk * version.subChunkHeight) + y; +// } +// } +// return -1; +// } +// +// public int getHighestBlockYUnderAt(int x, int z, int y) throws Version.VersionException { +// ///Meow +// ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); +// +// Version cVersion = getVersion(); +// int offset = y % cVersion.subChunkHeight; +// int subChunk = y / cVersion.subChunkHeight; +// TerrainChunkData data; +// +// for (; subChunk >= 0; subChunk--) { +// data = this.getTerrain((byte) subChunk); +// if (data == null || !data.loadTerrain()) continue; +// +// for (y = offset; y >= 0; y--) { +// if (data.getBlockTypeId(x & 15, y, z & 15) != 0) +// return (subChunk * cVersion.subChunkHeight) + y; +// } +// +// //start at the top of the next chunk! (current offset might differ) +// offset = cVersion.subChunkHeight - 1; +// } +// return -1; +// } +// +// public int getCaveYUnderAt(int x, int z, int y) throws Version.VersionException { +// ///Meow +// ///if (worldData.isMeow()) return meowTeChData.getHighestBlockYUnderAt(x, y, z); +// +// Version cVersion = getVersion(); +// int offset = y % cVersion.subChunkHeight; +// int subChunk = y / cVersion.subChunkHeight; +// TerrainChunkData data; +// +// for (; subChunk >= 0; subChunk--) { +// data = this.getTerrain((byte) subChunk); +// if (data == null || !data.loadTerrain()) continue; +// for (y = offset; y >= 0; y--) { +// if (data.getBlockTypeId(x & 15, y, z & 15) == 0) +// return (subChunk * cVersion.subChunkHeight) + y; +// } +// +// //start at the top of the next chunk! (current offset might differ) +// offset = cVersion.subChunkHeight - 1; +// } +// return -1; +// } + + +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java index b129b665..62aaa214 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/Version.java @@ -1,6 +1,8 @@ package com.mithrilmania.blocktopograph.chunk; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.SparseArray; import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; @@ -42,7 +44,8 @@ public enum Version { } } - public static Version getVersion(byte[] data) { + @NonNull + public static Version getVersion(@Nullable byte[] data) { //Log.d("Data version: "+ ConvertUtil.bytesToHexStr(data)); //`data` is supposed to be one byte, @@ -66,24 +69,25 @@ public static Version getVersion(byte[] data) { } public TerrainChunkData createTerrainChunkData(Chunk chunk, byte subChunk) throws VersionException { - switch (this) { - case ERROR: - case NULL: - return null; - case OLD_LIMITED: - throw new VersionException("Handling terrain chunk data is NOT supported for this version!", this); - case v0_9: - return new V0_9_TerrainChunkData(chunk, subChunk); - case V1_0: - return new V1_0_TerrainChunkData(chunk, subChunk); - case V1_1: - //This was used as default and the author said: - //use the latest version, like nothing will ever happen... - return new V1_1_TerrainChunkData(chunk, subChunk); - case V1_2_PLUS: - default: - return new V1_2_Plus_TerrainChunkData(chunk, subChunk); - } + return null; +// switch (this) { +// case ERROR: +// case NULL: +// return null; +// case OLD_LIMITED: +// throw new VersionException("Handling terrain chunk data is NOT supported for this version!", this); +// case v0_9: +// return new V0_9_TerrainChunkData(chunk, subChunk); +// case V1_0: +// return new V1_0_TerrainChunkData(chunk, subChunk); +// case V1_1: +// //This was used as default and the author said: +// //use the latest version, like nothing will ever happen... +// return new V1_1_TerrainChunkData(chunk, subChunk); +// case V1_2_PLUS: +// default: +// return new V1_2_Plus_TerrainChunkData(chunk, subChunk); +// } } public NBTChunkData createEntityChunkData(Chunk chunk) throws VersionException { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/VoidChunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/VoidChunk.java new file mode 100644 index 00000000..0210fbc3 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/VoidChunk.java @@ -0,0 +1,65 @@ +package com.mithrilmania.blocktopograph.chunk; + +import com.mithrilmania.blocktopograph.WorldData; +import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.NBTChunkData; +import com.mithrilmania.blocktopograph.chunk.Version; +import com.mithrilmania.blocktopograph.map.Dimension; + +public final class VoidChunk extends Chunk { + + VoidChunk(WorldData worldData, Version version, int chunkX, int chunkZ, Dimension dimension) { + super(worldData, version, chunkX, chunkZ, dimension); + mIsVoid = true; + } + + @Override + public boolean supportsBlockLightValues() { + return false; + } + + @Override + public int getHeightLimit() { + return 0; + } + + @Override + public int getHeightMapValue(int x, int z) { + return 0; + } + + @Override + public int getBiome(int x, int z) { + return 0; + } + + @Override + public int getGrassColor(int x, int z) { + return 0; + } + + @Override + public int getBlockRuntimeId(int x, int y, int z) { + return 0; + } + + @Override + public int getBlockLightValue(int x, int y, int z) { + return 0; + } + + @Override + public int getSkyLightValue(int x, int y, int z) { + return 0; + } + + @Override + public int getHighestBlockYUnderAt(int x, int z, int y) { + return -1; + } + + @Override + public int getCaveYUnderAt(int x, int z, int y) { + return -1; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/AquaTerrainSubChunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/AquaTerrainSubChunk.java new file mode 100644 index 00000000..033021bd --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/AquaTerrainSubChunk.java @@ -0,0 +1,138 @@ +package com.mithrilmania.blocktopograph.chunk.terrain; + +import com.mithrilmania.blocktopograph.map.BlockNameResolver; +import com.mithrilmania.blocktopograph.nbt.convert.NBTInputStream; +import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; +import com.mithrilmania.blocktopograph.nbt.tags.ShortTag; +import com.mithrilmania.blocktopograph.nbt.tags.StringTag; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; + +public final class AquaTerrainSubChunk extends TerrainSubChunk { + + //There could be multiple BlockStorage but we can just display the main. + private volatile IntBuffer mMainStorage; + private volatile List mPalette; + private volatile int blockTypes, mBlockCodeLenth; + + //Masks used to extract BlockState bits of a certain block out of a int32, and vice-versa. + private static final int[] msk = {0b1, 0b11, 0b111, 0b1111, 0b11111, 0b111111, 0b1111111, + 0b11111111, + 0b111111111, 0b1111111111, 0b11111111111, + 0b111111111111, + 0b1111111111111, 0b11111111111111, 0b11111111111111}; + + AquaTerrainSubChunk(ByteBuffer raw) { + + raw.order(ByteOrder.LITTLE_ENDIAN); + //The first byte indicates version. + switch (raw.get(0)) { + //1: Only one BlockStorage starting from the next byte. + case 1: + raw.position(1); + break; + //8: One or more BlockStorage's, next byte is the count. We only read the first. + case 8: + raw.position(2); + break; + default: + mIsError = true; + return; + } + mHasBlockLight = false; + mHasSkyLight = false; + + //Load the BlockStorage. + try { + loadBlockStorage(raw); + } catch (IOException e) { + e.printStackTrace(); + mIsError = true; + } + } + + private void loadBlockStorage(ByteBuffer raw) throws IOException { + + //Read BlockState length. + //this byte = (length << 2) | serializedType. + mBlockCodeLenth = (raw.get() & 0xff) >> 1; + + if (mBlockCodeLenth > 16) throw new IOException("mBlockLength > 16"); + + //We use this much of bytes to store BlockStates. + int bufsize = (4095 / (32 / mBlockCodeLenth) + 1) << 2; + ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + mMainStorage = byteBuffer.asIntBuffer(); + + //No convenient way copy these stuff. + byteBuffer.put(raw.array(), raw.position(), bufsize); + raw.position(raw.position() + bufsize); + + //Palette items count. + int psize = raw.getInt(); + +// if(psize>(1< most possible bound"); +// } + + //Construct the palette. Each item is a piece of nbt data. + mPalette = new ArrayList<>(16); + + //NBT reader requires a stream. + ByteArrayInputStream bais = new ByteArrayInputStream(raw.array()); + bais.skip(raw.position()); + + //Wrap it. + NBTInputStream nis = new NBTInputStream(bais, false); + for (int i = 0; i < psize; i++) { + + //Read a piece of nbt data, represented by a root CompoundTag. + CompoundTag tag = (CompoundTag) nis.readTag(); + + //Read `name` and `val` then resolve the `name` into numeric id. + String name = ((StringTag) tag.getChildTagByKey("name")).getValue(); + int data = ((ShortTag) tag.getChildTagByKey("val")).getValue(); + mPalette.add(BlockNameResolver.resolve(name) << 8 | data); + } + + //If one day we need to read more BlockStorage's, this line helps. + raw.position(raw.position() + nis.getReadCount()); + } + + @Override + public int getBlockRuntimeId(int x, int y, int z) { + if (mIsError) return 0; + + //The codeOffset'th BlockState is wanted. + int codeOffset = getOffset(x, y, z); + + //How much BlockStates can one int32 hold? + int intCapa = 32 / mBlockCodeLenth; + + //The int32 that holds the wanted BlockState. + int stick = mMainStorage.get(codeOffset / intCapa); + + //Get the BlockState. It's also the index in palette array. + int ind = (stick >> (codeOffset % intCapa * mBlockCodeLenth)) & msk[mBlockCodeLenth - 1]; + + //Transform the local BlockState into global id<<8|data structure. + return mPalette.get(ind); + } + + @Override + public int getBlockLightValue(int x, int y, int z) { + return 0; + } + + @Override + public int getSkyLightValue(int x, int y, int z) { + return 0; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/PreAquaTerrainSubChunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/PreAquaTerrainSubChunk.java new file mode 100644 index 00000000..21863f85 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/PreAquaTerrainSubChunk.java @@ -0,0 +1,56 @@ +package com.mithrilmania.blocktopograph.chunk.terrain; + +import com.mithrilmania.blocktopograph.chunk.BedrockChunk; +import com.mithrilmania.blocktopograph.chunk.Chunk; + +import java.nio.ByteBuffer; + +public final class PreAquaTerrainSubChunk extends TerrainSubChunk { + + private static final int POS_BLOCK_IDS = 1; + private static final int POS_META_DATA = 0x1001; + private static final int POS_SKY_LIGHT = 0x1801; + private static final int POS_BLOCK_LIGHT = 0x2001; + private static final int TERRAIN_MAX_LENGTH = 0x2801; + + private ByteBuffer mData; + + PreAquaTerrainSubChunk(ByteBuffer raw) { + int size = raw.capacity(); + if (size < POS_SKY_LIGHT || size > TERRAIN_MAX_LENGTH) { + mIsError = true; + return; + } + mIsError = false; + mData = ByteBuffer.allocate(size); + mData.put(raw); + mHasSkyLight = size >= POS_BLOCK_LIGHT; + mHasBlockLight = size == TERRAIN_MAX_LENGTH; + } + + @Override + public int getBlockRuntimeId(int x, int y, int z) { + if (mIsError) return 0; + int offset = getOffset(x, y, z); + int id = mData.get(POS_BLOCK_IDS + offset) & 0xff; + int data = mData.get(POS_META_DATA + (offset >>> 1)); + data = (offset & 1) == 1 ? ((data >>> 4) & 0xf) : (data & 0xf); + return (id << 8) | data; + } + + @Override + public int getBlockLightValue(int x, int y, int z) { + if (mIsError || !mHasBlockLight) return 0; + int offset = getOffset(x, y, z); + int dualData = mData.get(POS_BLOCK_LIGHT + (offset >>> 1)); + return (offset & 1) == 1 ? (dualData >>> 4) & 0xf : dualData & 0xf; + } + + @Override + public int getSkyLightValue(int x, int y, int z) { + if (mIsError || !mHasSkyLight) return 0; + int offset = getOffset(x, y, z); + int dualData = mData.get(POS_SKY_LIGHT + (offset >>> 1)); + return (offset & 1) == 1 ? (dualData >>> 4) & 0xf : dualData & 0xf; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java index 50e3d450..e91b0a4c 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainChunkData.java @@ -9,11 +9,18 @@ public abstract class TerrainChunkData extends ChunkData { public final byte subChunk; + protected boolean mNotFailed; + public TerrainChunkData(Chunk chunk, byte subChunk) { super(chunk); + this.mNotFailed = true; this.subChunk = subChunk; } + public final boolean hasNotFailed() { + return mNotFailed; + } + public abstract boolean loadTerrain(); public abstract boolean load2DData(); @@ -47,14 +54,14 @@ protected int getNoise(int base, int x, int z) { // 0.0001 is added to the coordinates because integer values result in 0 Chunk chunk = this.chunk.get(); double oct1 = Noise.noise( - ((double) (chunk.x * 16 + x) / 100.0) + 0.0001, - ((double) (chunk.z * 16 + z) / 100.0) + 0.0001); + ((double) (chunk.mChunkX * 16 + x) / 100.0) + 0.0001, + ((double) (chunk.mChunkZ * 16 + z) / 100.0) + 0.0001); double oct2 = Noise.noise( - ((double) (chunk.x * 16 + x) / 20.0) + 0.0001, - ((double) (chunk.z * 16 + z) / 20.0) + 0.0001); + ((double) (chunk.mChunkX * 16 + x) / 20.0) + 0.0001, + ((double) (chunk.mChunkZ * 16 + z) / 20.0) + 0.0001); double oct3 = Noise.noise( - ((double) (chunk.x * 16 + x) / 3.0) + 0.0001, - ((double) (chunk.z * 16 + z) / 3.0) + 0.0001); + ((double) (chunk.mChunkX * 16 + x) / 3.0) + 0.0001, + ((double) (chunk.mChunkZ * 16 + z) / 3.0) + 0.0001); return (int) (base + 60 + (40 * oct1) + (14 * oct2) + (6 * oct3)); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainSubChunk.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainSubChunk.java new file mode 100644 index 00000000..31b74705 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/TerrainSubChunk.java @@ -0,0 +1,60 @@ +package com.mithrilmania.blocktopograph.chunk.terrain; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.jetbrains.annotations.Contract; + +import java.nio.ByteBuffer; + +public abstract class TerrainSubChunk { + + boolean mHasSkyLight; + boolean mHasBlockLight; + boolean mIsError; + + @Nullable + public static TerrainSubChunk create(@NonNull byte[] rawData) { + TerrainSubChunk subChunk; + ByteBuffer byteBuffer = ByteBuffer.wrap(rawData); + switch (rawData[0]) { + case 0: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + subChunk = new PreAquaTerrainSubChunk(byteBuffer); + break; + case 1: + case 8: + subChunk = new AquaTerrainSubChunk(byteBuffer); + break; + default: + subChunk = null; + } + return subChunk; + } + + abstract public int getBlockRuntimeId(int x, int y, int z); + + abstract public int getBlockLightValue(int x, int y, int z); + + abstract public int getSkyLightValue(int x, int y, int z); + + @Contract(pure = true) + public final boolean hasBlockLight() { + return mHasBlockLight; + } + + @Contract(pure = true) + public final boolean isError() { + return mIsError; + } + + final int getOffset(int x, int y, int z) { + return (((x << 4) | z) << 4) | y; + } + +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java index 57888f7d..a16916e5 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V0_9_TerrainChunkData.java @@ -31,29 +31,30 @@ public class V0_9_TerrainChunkData extends TerrainChunkData { public V0_9_TerrainChunkData(Chunk chunk, byte subChunk) { super(chunk, subChunk); + mNotFailed = tryLoad(); } @Override public void write() throws IOException, WorldData.WorldDBException { Chunk chunk = this.chunk.get(); - chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.dimension, subChunk, false, toByteArray()); + chunk.getWorldData().writeChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.mDimension, subChunk, false, toByteArray()); } @Override public boolean loadTerrain() { - return tryLoad(); + return mNotFailed; } @Override public boolean load2DData() { - return tryLoad(); + return mNotFailed; } public boolean tryLoad() { if (buf == null) { try { Chunk chunk = this.chunk.get(); - byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.dimension, subChunk, false); + byte[] rawData = chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.V0_9_LEGACY_TERRAIN, chunk.mDimension, subChunk, false); if (rawData == null) return false; this.buf = ByteBuffer.wrap(rawData); return true; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java index fe73714d..4823e156 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_0_TerrainChunkData.java @@ -3,9 +3,7 @@ import com.mithrilmania.blocktopograph.WorldData; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkTag; -import com.mithrilmania.blocktopograph.chunk.Version; import com.mithrilmania.blocktopograph.map.Biome; -import com.mithrilmania.blocktopograph.util.Noise; import java.nio.ByteBuffer; @@ -33,13 +31,14 @@ public class V1_0_TerrainChunkData extends TerrainChunkData { public V1_0_TerrainChunkData(Chunk chunk, byte subChunk) { super(chunk, subChunk); + mNotFailed = loadTerrain(); } @Override public void write() throws WorldData.WorldDBException { Chunk chunk = this.chunk.get(); - chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); - chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); + chunk.getWorldData().writeChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.TERRAIN, chunk.mDimension, subChunk, true, terrainData.array()); + chunk.getWorldData().writeChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.DATA_2D, chunk.mDimension, subChunk, true, data2D.array()); } @Override @@ -47,7 +46,7 @@ public boolean loadTerrain() { if (terrainData == null) { try { Chunk chunk = this.chunk.get(); - byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); + byte[] rawData = chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.TERRAIN, chunk.mDimension, subChunk, true); if (rawData == null) return false; this.terrainData = ByteBuffer.wrap(rawData); return true; @@ -55,7 +54,7 @@ public boolean loadTerrain() { //data is not present return false; } - } else return true; + } else return mNotFailed; } @Override @@ -63,7 +62,7 @@ public boolean load2DData() { if (data2D == null) { try { Chunk chunk = this.chunk.get(); - byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); + byte[] rawData = chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.DATA_2D, chunk.mDimension, subChunk, false); if (rawData == null) return false; this.data2D = ByteBuffer.wrap(rawData); return true; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java index 09b44308..703a8754 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_1_TerrainChunkData.java @@ -4,7 +4,6 @@ import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkTag; import com.mithrilmania.blocktopograph.map.Biome; -import com.mithrilmania.blocktopograph.util.Noise; import java.nio.ByteBuffer; @@ -31,13 +30,14 @@ public class V1_1_TerrainChunkData extends TerrainChunkData { public V1_1_TerrainChunkData(Chunk chunk, byte subChunk) { super(chunk, subChunk); + mNotFailed = loadTerrain(); } @Override public void write() throws WorldData.WorldDBException { Chunk chunk = this.chunk.get(); - chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); - chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); + chunk.getWorldData().writeChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.TERRAIN, chunk.mDimension, subChunk, true, terrainData.array()); + chunk.getWorldData().writeChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.DATA_2D, chunk.mDimension, subChunk, true, data2D.array()); } @Override @@ -45,7 +45,7 @@ public boolean loadTerrain() { if (terrainData == null) { try { Chunk chunk = this.chunk.get(); - byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); + byte[] rawData = chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.TERRAIN, chunk.mDimension, subChunk, true); if (rawData == null) return false; this.terrainData = ByteBuffer.wrap(rawData); return true; @@ -53,7 +53,7 @@ public boolean loadTerrain() { //data is not present return false; } - } else return true; + } else return mNotFailed; } @Override @@ -61,7 +61,7 @@ public boolean load2DData() { if (data2D == null) { try { Chunk chunk = this.chunk.get(); - byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); + byte[] rawData = chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.DATA_2D, chunk.mDimension, subChunk, false); if (rawData == null) return false; this.data2D = ByteBuffer.wrap(rawData); return true; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java index d183739b..3e3e2647 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/chunk/terrain/V1_2_Plus_TerrainChunkData.java @@ -1,17 +1,14 @@ package com.mithrilmania.blocktopograph.chunk.terrain; -import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.WorldData; import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkTag; import com.mithrilmania.blocktopograph.map.Biome; -import com.mithrilmania.blocktopograph.map.Block; import com.mithrilmania.blocktopograph.map.BlockNameResolver; import com.mithrilmania.blocktopograph.nbt.convert.NBTInputStream; import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; import com.mithrilmania.blocktopograph.nbt.tags.ShortTag; import com.mithrilmania.blocktopograph.nbt.tags.StringTag; -import com.mithrilmania.blocktopograph.util.Noise; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -19,7 +16,6 @@ import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.ArrayList; -import java.util.BitSet; import java.util.List; public class V1_2_Plus_TerrainChunkData extends TerrainChunkData { @@ -49,13 +45,14 @@ public class V1_2_Plus_TerrainChunkData extends TerrainChunkData { public V1_2_Plus_TerrainChunkData(Chunk chunk, byte subChunk) { super(chunk, subChunk); + mNotFailed = loadTerrain(); } @Override public void write() throws WorldData.WorldDBException { Chunk chunk = this.chunk.get(); - //this.chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true, terrainData.array()); - chunk.worldData.get().writeChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, true, data2D.array()); + //this.chunk.worldData.get().writeChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.TERRAIN, chunk.mDimension, subChunk, true, terrainData.array()); + chunk.getWorldData().writeChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.DATA_2D, chunk.mDimension, subChunk, true, data2D.array()); } @Override @@ -65,7 +62,7 @@ public boolean loadTerrain() { try { //Retrieve raw data from database. Chunk chunk = this.chunk.get(); - byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.TERRAIN, chunk.dimension, subChunk, true); + byte[] rawData = chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.TERRAIN, chunk.mDimension, subChunk, true); if (rawData == null) return false; ByteBuffer raw = ByteBuffer.wrap(rawData); raw.order(ByteOrder.LITTLE_ENDIAN); @@ -93,7 +90,7 @@ public boolean loadTerrain() { //data is not present return false; } - } else return true; + } else return mNotFailed; } private void loadBlockStorage(ByteBuffer raw) throws IOException { @@ -144,7 +141,7 @@ public boolean load2DData() { if (data2D == null) { try { Chunk chunk = this.chunk.get(); - byte[] rawData = chunk.worldData.get().getChunkData(chunk.x, chunk.z, ChunkTag.DATA_2D, chunk.dimension, subChunk, false); + byte[] rawData = chunk.getWorldData().getChunkData(chunk.mChunkX, chunk.mChunkZ, ChunkTag.DATA_2D, chunk.mDimension, subChunk, false); if (rawData == null) return false; this.data2D = ByteBuffer.wrap(rawData); return true; @@ -187,6 +184,8 @@ public void createEmpty() { private int getBlockState(int x, int y, int z) { + if (!mNotFailed) return 0; + //The codeOffset'th BlockState is wanted. int codeOffset = getOffset(x, y, z); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java index 1f17fd3d..51a17883 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java @@ -17,22 +17,22 @@ /** * Created by mithrilmania - * + *

* ========================== * POCKET EDITION BLOCKS ONLY * ========================== - * + *

* uvs are up to date with MCPE 0.14.0 - * - --- Please attribute @mithrilmania for generating+updating this enum + *

+ * --- Please attribute @mithrilmania for generating+updating this enum */ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { - /* - * ============================== - * Blocks - * ============================== - */ + /* + * ============================== + * Blocks + * ============================== + */ B_0_0_AIR("air", null, 0, 0, null, 0x00000000, false), B_1_0_STONE("stone", "stone", 1, 0, "blocks/stone.png", 0xff464646, false), @@ -448,7 +448,6 @@ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { B_251_0_OBSERVER("observer", null, 251, 0, "blocks/observer.png", 0xff3d6e86, false), - B_236_0_CONCRETE_WHITE("concrete", "orange", 236, 0, "blocks/observer.png", 0xffffffff, false), B_236_1_CONCRETE_ORANGE("concrete", "orange", 236, 1, "blocks/observer.png", 0xffffd030, false), B_236_2_CONCRETE_MAGENTA("concrete", "magenta", 236, 2, "blocks/observer.png", 0xffef007f, false), @@ -576,11 +575,11 @@ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { - /* - * ============================== - * Items - * ============================== - */ + /* + * ============================== + * Items + * ============================== + */ I_256_0_IRON_SHOVEL("iron_shovel", null, 256, 0, "items/iron_shovel.png"), I_257_0_IRON_PICKAXE("iron_pickaxe", null, 257, 0, "items/iron_pickaxe.png"), @@ -811,7 +810,7 @@ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { public Bitmap bitmap; - Block(String name, String subName, int id, int subId, String texPath, int color, boolean hasBiomeShading){ + Block(String name, String subName, int id, int subId, String texPath, int color, boolean hasBiomeShading) { this.id = id; this.subId = subId; this.name = name; @@ -823,7 +822,7 @@ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { this.identifier = "minecraft:" + subName; } - Block(String name, String subName, int id, int subId, String texPath){ + Block(String name, String subName, int id, int subId, String texPath) { this.id = id; this.subId = subId; this.name = name; @@ -836,69 +835,76 @@ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { } @Override - public Bitmap getBitmap(){ + public Bitmap getBitmap() { return this.bitmap; } @NonNull @Override - public NamedBitmapProvider getNamedBitmapProvider(){ + public NamedBitmapProvider getNamedBitmapProvider() { return this; } @NonNull @Override - public String getBitmapDisplayName(){ + public String getBitmapDisplayName() { return this.displayName; } @NonNull @Override - public String getBitmapDataName(){ + public String getBitmapDataName() { return name + "@" + subName; } private static final Map byDataName = new HashMap<>(); private static final SparseArray> blockMap; + static { blockMap = new SparseArray<>(); SparseArray subMap; - for(Block b : Block.values()){ + for (Block b : Block.values()) { subMap = blockMap.get(b.id); - if(subMap == null){ + if (subMap == null) { subMap = new SparseArray<>(); blockMap.put(b.id, subMap); } subMap.put(b.subId, b); - if(b.subId == 0) byDataName.put(b.name, b); + if (b.subId == 0) byDataName.put(b.name, b); byDataName.put(b.name + "@" + b.subName, b); } } - public static Block getByDataName(String dataName){ + public static Block getByDataName(String dataName) { return byDataName.get(dataName); } public static void loadBitmaps(AssetManager assetManager) throws IOException { - for(Block b : Block.values()){ - if(b.bitmap == null && b.texPath != null){ + for (Block b : Block.values()) { + if (b.bitmap == null && b.texPath != null) { try { b.bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeStream(assetManager.open(b.texPath)), 32, 32, false); - } catch(FileNotFoundException e){ + } catch (FileNotFoundException e) { //TODO file-paths were generated from block names; some do not actually exist... //Log.w("File not found! "+b.texPath); - } catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } } } - public static Block getBlock(int id, int meta){ - if(id < 0) return null; + public static Block getBlock(int id, int meta) { + if (id < 0) return null; SparseArray subMap = blockMap.get(id); - if(subMap == null) return null; + if (subMap == null) return null; else return subMap.get(meta); } + public static Block getBlock(int runtimeId) { + int id = runtimeId >>> 8; + int data = runtimeId & 0xf; + return getBlock(id, data); + } + } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java index 77a2d502..cc5d19dd 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MCTileProvider.java @@ -131,24 +131,22 @@ public Bitmap getBitmap(Tile tile, Context context) { for (x = minChunkX, pX = 0; x < maxChunkX; x++, pX += pixelsPerChunkW) { Chunk chunk = chunkManager.getChunk(x, z, dimension); + if (chunk.isError()) { + MapType.ERROR.renderer.renderToBitmap(chunk, canvas, dimension, + x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, chunkManager); + continue; + } + MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, + x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, chunkManager); + if (chunk.isVoid()) continue; try { - - Version cVersion = chunk.getVersion(); - if (cVersion == Version.ERROR) { - MapType.ERROR.renderer.renderToBitmap(chunk, canvas, dimension, - x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, cVersion, chunkManager); - continue; - } - MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, - x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, cVersion, chunkManager); - mapType.renderer.renderToBitmap(chunk, canvas, dimension, x, z, - pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, cVersion, chunkManager); + pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, chunkManager); } catch (Exception e) { - MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, - x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, null, chunkManager); + MapType.ERROR.renderer.renderToBitmap(chunk, canvas, dimension, + x, z, pX, pY, pixelsPerBlockW, pixelsPerBlockL, paint, chunkManager); e.printStackTrace(); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java index 421a17d7..56d01b05 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java @@ -31,14 +31,13 @@ import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.R; import com.mithrilmania.blocktopograph.World; -import com.mithrilmania.blocktopograph.WorldActivity; import com.mithrilmania.blocktopograph.WorldActivityInterface; import com.mithrilmania.blocktopograph.WorldData; import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.TempChunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.ChunkTag; import com.mithrilmania.blocktopograph.chunk.NBTChunkData; -import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.marker.AbstractMarker; import com.mithrilmania.blocktopograph.map.marker.CustomNamedBitmapProvider; import com.mithrilmania.blocktopograph.map.marker.MarkerImageView; @@ -127,7 +126,7 @@ public void onStart() { WorldActivityInterface worldProvider = this.worldProvider.get(); getActivity().setTitle(worldProvider.getWorld().getWorldDisplayName()); - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_OPEN); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.MAPFRAGMENT_OPEN); } @Override @@ -137,7 +136,7 @@ public void onResume() { //resume drawing the map this.tileView.resume(); - worldProvider.get().logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_RESUME); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.MAPFRAGMENT_RESUME); } public void closeChunks() { @@ -231,10 +230,9 @@ public DimensionVector3 getSpawnPos() throws Exception { int spawnY = ((IntTag) level.getChildTagByKey("SpawnY")).getValue(); int spawnZ = ((IntTag) level.getChildTagByKey("SpawnZ")).getValue(); if (spawnY == 256) { - TerrainChunkData data = mChunkManager.getChunk(spawnX >> 4, spawnZ >> 4, Dimension.OVERWORLD) - .getTerrain((byte) 0); - if (data.load2DData()) - spawnY = data.getHeightMapValue(spawnX % 16, spawnZ % 16) + 1; + Chunk chunk = mChunkManager.getChunk(spawnX >> 4, spawnZ >> 4, Dimension.OVERWORLD); + if (!chunk.isError()) + spawnY = chunk.getHeightMapValue(spawnX % 16, spawnZ % 16) + 1; } return new DimensionVector3<>(spawnX, spawnY, spawnZ, Dimension.OVERWORLD); } catch (Exception e) { @@ -289,7 +287,7 @@ public void onClick(View view) { worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); } - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_PLAYER); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.GPS_PLAYER); frameTo((double) playerPos.x, (double) playerPos.z); @@ -323,7 +321,7 @@ public void onClick(View view) { worldProvider.changeMapType(spawnPos.dimension.defaultMapType, spawnPos.dimension); } - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_SPAWN); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.GPS_SPAWN); frameTo((double) spawnPos.x, (double) spawnPos.z); @@ -415,7 +413,7 @@ public void onClick(DialogInterface dialog, int which) { frameTo((double) m.x, (double) m.z); - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_MARKER); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.GPS_MARKER); } }); markerDialogBuilder.show(); @@ -494,7 +492,7 @@ public void onClick(DialogInterface dialog, int whichButton) { return; } - worldProvider.get().logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_COORD); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.GPS_COORD); frameTo((double) inX, (double) inZ); } @@ -1186,7 +1184,7 @@ private boolean chunkDataNBT(Chunk chunk, boolean entity) { } //just open the editor if the data is there for us to edit it - this.worldProvider.get().openChunkNBTEditor(chunk.x, chunk.z, chunkData, this.tileView); + this.worldProvider.get().openChunkNBTEditor(chunk.mChunkX, chunk.mChunkZ, chunkData, this.tileView); return true; } @@ -1276,7 +1274,7 @@ public void filterMarker(AbstractMarker marker) { public void resetTileView() { if (this.tileView != null) { WorldActivityInterface worldProvider = this.worldProvider.get(); - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.MAPFRAGMENT_RESET); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.MAPFRAGMENT_RESET); updateMarkerFilter(); @@ -1514,7 +1512,7 @@ public void onClick(DialogInterface dialog, int whichButton) { .setAction("Action", null).show(); WorldActivityInterface worldProvider = owner.get().worldProvider.get(); - worldProvider.logFirebaseEvent(WorldActivity.CustomFirebaseEvent.GPS_MULTIPLAYER); + Log.logFirebaseEvent(activity.get(), Log.CustomFirebaseEvent.GPS_MULTIPLAYER); if (playerPos.dimension != worldProvider.getDimension()) { worldProvider.changeMapType(playerPos.dimension.defaultMapType, playerPos.dimension); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java index 9029a058..44ce41bb 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java @@ -3,9 +3,10 @@ import android.os.AsyncTask; import com.mithrilmania.blocktopograph.Log; - import com.mithrilmania.blocktopograph.WorldActivityInterface; -import com.mithrilmania.blocktopograph.chunk.*; +import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.ChunkManager; +import com.mithrilmania.blocktopograph.chunk.NBTChunkData; import com.mithrilmania.blocktopograph.map.marker.AbstractMarker; import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; import com.mithrilmania.blocktopograph.nbt.tags.FloatTag; @@ -90,7 +91,9 @@ private void loadEntityMarkers(int chunkX, int chunkZ) { } } catch (Exception e) { - Log.w(e.getMessage()); + //TODO: e.getMessage can be null + //String msg=e.getMessage(); + //String log="MarkerAsyncTask.loadEntityMarkers)Log.w(); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java index e4b2483c..6d7bf190 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BiomeRenderer.java @@ -7,37 +7,25 @@ import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; -import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Biome; import com.mithrilmania.blocktopograph.map.Dimension; public class BiomeRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { int x, z, biomeID, color, i, j, tX, tY; Biome biome; - //the bottom sub-chunk is sufficient to get biome data. - TerrainChunkData data = chunk.getTerrain((byte) 0); - if (data == null || !data.load2DData()) - throw new RuntimeException(); - for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { - - biomeID = data.getBiome(x, z) & 0xff; + biomeID = chunk.getBiome(x, z) & 0xff; biome = Biome.getBiome(biomeID); color = biome == null ? 0xff000000 : (biome.color.red << 16) | (biome.color.green << 8) | (biome.color.blue) | 0xff000000; -// for (i = 0; i < pL; i++) { -// for (j = 0; j < pW; j++) { -// bm.setPixel(tX + j, tY + i, color); -// } -// } paint.setColor(color); canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java index 8f690a72..0a0c4d08 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/BlockLightRenderer.java @@ -7,29 +7,25 @@ import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; -import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; public class BlockLightRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { - int x, y, z, subChunk, color, i, j, tX, tY; + int x, y, z, subChunk, color, yLim, tX, tY; //render width in blocks int rW = 16; int[] light = new int[rW * 16]; - for (subChunk = 0; subChunk < version.subChunks; subChunk++) { - TerrainChunkData data = chunk.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) break; + yLim = chunk.getHeightLimit(); - for (z = 0; z < 16; z++) { - for (x = 0; x < 16; x++) { - for (y = 0; y < version.subChunkHeight; y++) { - light[(z * rW) + x] += data.getBlockLightValue(x, y, z) & 0xff; - } + for (z = 0; z < 16; z++) { + for (x = 0; x < 16; x++) { + for (y = 0; y < yLim; y++) { + light[(z * rW) + x] += chunk.getBlockLightValue(x, y, z) & 0xff; } } } @@ -43,7 +39,6 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int color = (l << 16) | (l << 8) | (l) | 0xff000000; - paint.setColor(color); canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java index 0fd20a49..d664aaef 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/CaveRenderer.java @@ -14,21 +14,14 @@ public class CaveRenderer implements MapRenderer { - - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { boolean solid, intoSurface; int id, meta, cavyness, layers, offset; Block block; int x, y, z, subChunk, color, i, j, tX, tY, r, g, b; - //the bottom sub-chunk is sufficient to get heightmap data. - TerrainChunkData floorData = chunk.getTerrain((byte) 0); - if (floorData == null || !floorData.load2DData()) - throw new RuntimeException(); - - TerrainChunkData data; - for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -37,9 +30,8 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int intoSurface = false; cavyness = 0; layers = 0; - y = floorData.getHeightMapValue(x, z); - offset = y % version.subChunkHeight; - subChunk = y / version.subChunkHeight; + //offset = y % version.subChunkHeight; + //subChunk = y / version.subChunkHeight; /* while (cavefloor > 0) { @@ -57,75 +49,62 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int r = g = b = 0; subChunkLoop: - for (; subChunk >= 0; subChunk--) { - - data = chunk.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) { - //start at the top of the next chunk! (current offset might differ) - offset = version.subChunkHeight - 1; - continue; - } - - - for (y = offset; y >= 0; y--) { - - id = data.getBlockTypeId(x, y, z) & 0xff; - - meta = data.getBlockData(x, y, z) & 0xff; - block = Block.getBlock(id, meta); - - //try the default meta value: 0 - if (block == null) block = Block.getBlock(id, 0); - - switch (id) { - case 0: - //count the number of times it goes from solid to air - if (solid) layers++; - - //count the air blocks underground, - // but avoid trees by skipping the first layer - if (intoSurface) cavyness++; - break; - case 66://rail - if (b < 150) { - b = 150; - r = g = 50; - } - break; - case 5://wooden plank - if (b < 100) { - b = 100; - r = g = 100; - } - break; - case 52://monster spawner - r = g = b = 255; - break subChunkLoop; - case 54://chest - if (b < 170) { - b = 170; - r = 240; - g = 40; - } - break; - case 98://stone bricks - if (b < 145) { - b = 145; - r = g = 120; - } - break; - case 48://moss cobblestone - case 4://cobblestone - if (b < 140) { - b = 140; - r = g = 100; - } - break; - } - r += data.getBlockLightValue(x, y, z); - solid = block != null && block.color.alpha == 0xff; - intoSurface |= solid && (y < 60 || layers > 0); + for (y = chunk.getHeightMapValue(x, z); y >= 0; y--) { + + id = chunk.getBlockRuntimeId(x, y, z); + block = Block.getBlock(id); + + //try the default meta value: 0 + if (block == null) block = Block.getBlock(id & 0x7fffff00); + + switch (id) { + case 0: + //count the number of times it goes from solid to air + if (solid) layers++; + + //count the air blocks underground, + // but avoid trees by skipping the first layer + if (intoSurface) cavyness++; + break; + case 66://rail + if (b < 150) { + b = 150; + r = g = 50; + } + break; + case 5://wooden plank + if (b < 100) { + b = 100; + r = g = 100; + } + break; + case 52://monster spawner + r = g = b = 255; + break subChunkLoop; + case 54://chest + if (b < 170) { + b = 170; + r = 240; + g = 40; + } + break; + case 98://stone bricks + if (b < 145) { + b = 145; + r = g = 120; + } + break; + case 48://moss cobblestone + case 4://cobblestone + if (b < 140) { + b = 140; + r = g = 100; + } + break; } + r += chunk.getBlockLightValue(x, y, z); + solid = block != null && block.color.alpha == 0xff; + intoSurface |= solid && (y < 60 || layers > 0); } if (g == 0 && layers > 0) { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java index 5169aa23..88244078 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/ChessPatternRenderer.java @@ -19,7 +19,7 @@ public class ChessPatternRenderer implements MapRenderer { this.lightShade = lightShade; } - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { int x, z, tX, tY; int color; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java index b80e86f2..4b1b5c4e 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/DebugRenderer.java @@ -11,7 +11,7 @@ public class DebugRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { int x, z, i, j, tX, tY; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java index 0c99a60e..439ef7c2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/GrassRenderer.java @@ -7,25 +7,19 @@ import com.mithrilmania.blocktopograph.chunk.Chunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; -import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; public class GrassRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { - - //the bottom sub-chunk is sufficient to get grass data. - TerrainChunkData data = chunk.getTerrain((byte) 0); - if (data == null || !data.load2DData()) - throw new RuntimeException(); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { int x, z, color, tX, tY; for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { - color = ((data.getGrassR(x, z) & 0xff) << 16) | ((data.getGrassG(x, z) & 0xff) << 8) | (data.getGrassB(x, z) & 0xff) | 0xff000000; + color = chunk.getGrassColor(x, z); paint.setColor(color); canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java index c58d03dc..63123e7a 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/HeightmapRenderer.java @@ -5,28 +5,23 @@ import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.TempChunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; -import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; public class HeightmapRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { - //the bottom sub-chunk is sufficient to get heightmap data. - TerrainChunkData data = chunk.getTerrain((byte) 0); - if (data == null || !data.load2DData()) - throw new RuntimeException(); + Chunk dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension); + Chunk dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension); - TerrainChunkData dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); - TerrainChunkData dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); + boolean west = dataW != null && !dataW.isVoid(), + north = dataN != null && !dataN.isVoid(); - boolean west = dataW != null && dataW.load2DData(), - north = dataN != null && dataN.load2DData(); - - int x, y, z, color, i, j, tX, tY; + int x, y, z, color, tX, tY; int yW, yN; int r, g, b; float yNorm, yNorm2, heightShading; @@ -36,16 +31,18 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int //smooth step function: 6x^5 - 15x^4 + 10x^3 - y = data.getHeightMapValue(x, z); + y = chunk.getHeightMapValue(x, z); + + if (y < 0) continue; yNorm = (float) y / (float) dimension.chunkH; yNorm2 = yNorm * yNorm; yNorm = ((6f * yNorm2) - (15f * yNorm) + 10f) * yNorm2 * yNorm; yW = (x == 0) ? (west ? dataW.getHeightMapValue(dimension.chunkW - 1, z) : y)//chunk edge - : data.getHeightMapValue(x - 1, z);//within chunk + : chunk.getHeightMapValue(x - 1, z);//within chunk yN = (z == 0) ? (north ? dataN.getHeightMapValue(x, dimension.chunkL - 1) : y)//chunk edge - : data.getHeightMapValue(x, z - 1);//within chunk + : chunk.getHeightMapValue(x, z - 1);//within chunk heightShading = SatelliteRenderer.getHeightShading(y, yW, yN); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java index d895d377..e639ac1b 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/MapRenderer.java @@ -35,11 +35,10 @@ will just make things worse on some (most?) phones. * @param pW width (X) of one block in pixels * @param pL length (Z) of one block in pixels * @param paint Paint instance used to draw on canvas - * @param version Ahh, why do we need this * @param chunkManager ChunkManager, some renderer needs info about its neighbor * @throws RuntimeException when the version of the chunk is unsupported. * TODO: reduce complicity, e.g. remove chunkManager from parameters. */ - void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException; + void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java index 5d6031f8..5137fc98 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/NetherRenderer.java @@ -17,33 +17,23 @@ public class NetherRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { - - //bottom chunk must be present - TerrainChunkData floorData = chunk.getTerrain((byte) 0); - if (floorData == null || !floorData.load2DData()) - throw new RuntimeException(); + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { Chunk chunkW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension); Chunk chunkN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension); - TerrainChunkData data; + //Do you have to list all variables here in a 80s manner + // regardless of many are only used within nested loop... float shading, shadingSum, rf, gf, bf, af, a, blendR, blendG, blendB, sumRf, sumGf, sumBf; int layers; int caveceil, cavefloor, cavefloorW, cavefloorN; - int x, y, z, color, i, j, tX, tY, r, g, b; + int x, y, z, color, tX, tY, r, g, b; Block block; - int id, meta; + int id; int worth; - int lightValue; float heightShading, lightShading, sliceShading, avgShading; - int offset; - int stop; - int subChunk; - int stopSubChunk; - for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { @@ -51,9 +41,8 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int shadingSum = 0; sumRf = sumGf = sumBf = 0; layers = 1; - cavefloor = floorData.getHeightMapValue(x, z);//TODO test this + cavefloor = chunk.getHeightMapValue(x, z);//TODO test this - //See-through-multi-level-height-light-shading is the new black -- @mithrilmania while (cavefloor > 0) { caveceil = chunk.getCaveYUnderAt(x, z, cavefloor - 1); @@ -68,16 +57,10 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int heightShading = SatelliteRenderer.getHeightShading(cavefloor, cavefloorW, cavefloorN); y = cavefloor + 1; - data = chunk.getTerrain((byte) (y / version.subChunkHeight)); - - //light sources - lightValue = (data != null && data.loadTerrain()) - ? data.getBlockLightValue(x, y % version.subChunkHeight, z) - : 0; //check if it is supported, default to full brightness to not lose details. - if (data.supportsBlockLightValues()) { - lightShading = (float) lightValue / 15f + 1; + if (chunk.supportsBlockLightValues()) { + lightShading = (float) chunk.getBlockLightValue(x, y, z) / 15f + 1; } else { lightShading = 2f; } @@ -94,66 +77,50 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int a = 1f; - offset = caveceil % version.subChunkHeight; - stop = 0; - subChunk = caveceil / version.subChunkHeight; - stopSubChunk = caveceil / version.subChunkHeight; - - - subChunkLoop: - for (; subChunk >= stopSubChunk; subChunk--) { - if (subChunk == stopSubChunk) stop = cavefloor % version.subChunkHeight; + for (y = caveceil; y >= cavefloor; y--) { - data = chunk.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) { - //start at the top of the next chunk! (current offset might differ) - offset = version.subChunkHeight - 1; - continue; - } - - for (y = offset; y >= stop; y--) { + id = chunk.getBlockRuntimeId(x, y, z); - id = data.getBlockTypeId(x, y, z) & 0xff; + if (id == 0) continue;//skip air blocks - if (id == 0) continue;//skip air blocks + block = Block.getBlock(id); - meta = data.getBlockData(x, y, z) & 0xff; - block = Block.getBlock(id, meta); + if (block == null) block = Block.getBlock(id & 0x7fffff00); - //try the default meta value: 0 - if (block == null) block = Block.getBlock(id, 0); - - if (block == null) { - Log.w("UNKNOWN block: id: " + id + " meta: " + meta); - continue; - } + //try the default meta value: 0 + //if (block == null) block = Block.getBlock(id, 0); - // no need to process block if it is fully transparent - if (block.color.alpha == 0) continue; + if (block == null) { + //Log.w("UNKNOWN block: id: " + id + " meta: " + meta); + continue; + } - rf = block.color.red / 255f; - gf = block.color.green / 255f; - bf = block.color.blue / 255f; - af = block.color.alpha / 255f; + // no need to process block if it is fully transparent + if (block.color.alpha == 0) continue; - // alpha blend and multiply - blendR = a * af * rf * shading; - blendG = a * af * gf * shading; - blendB = a * af * bf * shading; + rf = block.color.red / 255f; + gf = block.color.green / 255f; + bf = block.color.blue / 255f; + af = block.color.alpha / 255f; - sumRf += blendR; - sumGf += blendG; - sumBf += blendB; - a *= 1f - af; + // alpha blend and multiply + blendR = a * af * rf * shading; + blendG = a * af * gf * shading; + blendB = a * af * bf * shading; - // break when an opaque block is encountered - if (block.color.alpha == 0xff) break subChunkLoop; - } + sumRf += blendR; + sumGf += blendG; + sumBf += blendB; + a *= 1f - af; - //start at the top of the next chunk! (current offset might differ) - offset = version.subChunkHeight - 1; + // break when an opaque block is encountered + if (block.color.alpha == 0xff) break; } + //start at the top of the next chunk! (current offset might differ) + //offset = 15;//cVersion.subChunkHeight - 1; + //} + layers++; } @@ -170,43 +137,36 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int g = g < 0 ? 0 : g > 255 ? 255 : g; b = b < 0 ? 0 : b > 255 ? 255 : b; - - subChunkLoop: - for (subChunk = 0; subChunk < version.subChunks; subChunk++) { - data = chunk.getTerrain((byte) subChunk); - if (data == null || data.loadTerrain()) break; - - for (y = 0; y < version.subChunkHeight; y++) { - - //some x-ray for important stuff like portals - switch (data.getBlockTypeId(x, y, z)) { - case 52://monster spawner - r = g = b = 255; - break subChunkLoop;//max already? just stop - case 54://chest - if (worth < 90) { - worth = 90; - b = 170; - r = 240; - g = 40; - } - break; - case 115://nether wart - if (worth < 80) { - worth = 80; - r = b = 120; - g = 170; - } - break; - case 90://nether portal - if (worth < 95) { - worth = 95; - r = 60; - g = 0; - b = 170; - } - break; - } + for (y = 0; y < chunk.getHeightLimit(); y++) { + + //some x-ray for important stuff like portals + switch (chunk.getBlockRuntimeId(x, y, z) >>> 8) { + case 52://monster spawner + r = g = b = 255; + break; + case 54://chest + if (worth < 90) { + worth = 90; + b = 170; + r = 240; + g = 40; + } + break; + case 115://nether wart + if (worth < 80) { + worth = 80; + r = b = 120; + g = 170; + } + break; + case 90://nether portal + if (worth < 95) { + worth = 95; + r = 60; + g = 0; + b = 170; + } + break; } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java index 9a9d2ad4..4d5fb213 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java @@ -1,12 +1,14 @@ package com.mithrilmania.blocktopograph.map.renderer; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.TempChunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; @@ -16,31 +18,26 @@ public class SatelliteRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { - //the bottom sub-chunk is sufficient to get heightmap data. - TerrainChunkData data = chunk.getTerrain((byte) 0); - if (data == null || !data.load2DData()) - throw new RuntimeException(); + Chunk dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension); + Chunk dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension); - TerrainChunkData dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); - TerrainChunkData dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); - - boolean west = dataW != null && dataW.load2DData(), - north = dataN != null && dataN.load2DData(); + boolean west = dataW != null && !dataW.isVoid(), + north = dataN != null && !dataN.isVoid(); int x, y, z, color, i, j, tX, tY; for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { - y = data.getHeightMapValue(x, z); + y = chunk.getHeightMapValue(x, z); - color = getColumnColour(chunk, data, x, y, z, + color = getColumnColour(chunk, x, y, z, (x == 0) ? (west ? dataW.getHeightMapValue(dimension.chunkW - 1, z) : y)//chunk edge - : data.getHeightMapValue(x - 1, z),//within chunk + : chunk.getHeightMapValue(x - 1, z),//within chunk (z == 0) ? (north ? dataN.getHeightMapValue(x, dimension.chunkL - 1) : y)//chunk edge - : data.getHeightMapValue(x, z - 1)//within chunk + : chunk.getHeightMapValue(x, z - 1)//within chunk ); paint.setColor(color); canvas.drawRect(new Rect(tX, tY, tX + pW, tY + pL), paint); @@ -52,16 +49,17 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int } //calculate color of one column - public static int getColumnColour(Chunk chunk, TerrainChunkData floorData, int x, int y, int z, int heightW, int heightN) throws Version.VersionException { + public static int getColumnColour(Chunk chunk, int x, int y, int z, int heightW, int heightN) throws Version.VersionException { float a = 1f; float r = 0f; float g = 0f; float b = 0f; // extract colour components as normalized doubles, from ARGB format - float biomeR = (float) (floorData.getGrassR(x, z) & 0xff) / 255f; - float biomeG = (float) (floorData.getGrassG(x, z) & 0xff) / 255f; - float biomeB = (float) (floorData.getGrassB(x, z) & 0xff) / 255f; + int colint = chunk.getGrassColor(x, z); + float biomeR = (float) Color.red(colint) / 255f; + float biomeG = (float) Color.green(colint) / 255f; + float biomeB = (float) Color.blue(colint) / 255f; float blendR, blendG, blendB; @@ -69,93 +67,64 @@ public static int getColumnColour(Chunk chunk, TerrainChunkData floorData, int x Block block; - int id, meta; - - Version cVersion = chunk.getVersion(); - int realY = y; - int offset = y % cVersion.subChunkHeight; - int subChunk = y / cVersion.subChunkHeight; - - TerrainChunkData data; - - subChunkLoop: - for (; subChunk >= 0; subChunk--) { - - data = chunk.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) { - //start at the top of the next chunk! (current offset might differ) - offset = cVersion.subChunkHeight - 1; - continue; - } + int id; - for (y = offset; y >= 0; y--) { + y--; + for (; y >= 0; y--) { - id = data.getBlockTypeId(x, y, z) & 0xff; + id = chunk.getBlockRuntimeId(x, y, z); - if (id == 0) continue;//skip air blocks + if (id == 0) continue;//skip air blocks + block = Block.getBlock(id); - meta = data.getBlockData(x, y, z) & 0xff; - block = Block.getBlock(id, meta); + //try the default meta value: 0 + if (block == null) block = Block.getBlock(id & 0xffffff00); - //try the default meta value: 0 - if (block == null) block = Block.getBlock(id, 0); + //TODO log null blocks to debug missing blocks + if (block == null) { + Log.w("UNKNOWN block: id: " + id); + continue; + } - //TODO log null blocks to debug missing blocks - if (block == null) { - Log.w("UNKNOWN block: id: " + id + " meta: " + meta); - continue; - } - - // no need to process block if it is fully transparent - if (block.color == null || block.color.alpha == 0) continue; + // no need to process block if it is fully transparent + if (block.color == null || block.color.alpha == 0) continue; - blockR = block.color.red / 255f; - blockG = block.color.green / 255f; - blockB = block.color.blue / 255f; - blockA = block.color.alpha / 255f; + blockR = block.color.red / 255f; + blockG = block.color.green / 255f; + blockB = block.color.blue / 255f; + blockA = block.color.alpha / 255f; - // alpha blend and multiply - blendR = a * blockA * blockR; - blendG = a * blockA * blockG; - blendB = a * blockA * blockB; + // alpha blend and multiply + blendR = a * blockA * blockR; + blendG = a * blockA * blockG; + blendB = a * blockA * blockB; - //blend biome-colored blocks - if (block.hasBiomeShading) { - blendR *= biomeR; - blendG *= biomeG; - blendB *= biomeB; - } + //blend biome-colored blocks + if (block.hasBiomeShading) { + blendR *= biomeR; + blendG *= biomeG; + blendB *= biomeB; + } - r += blendR; - g += blendG; - b += blendB; - a *= 1f - blockA; + r += blendR; + g += blendG; + b += blendB; + a *= 1f - blockA; - // break when an opaque block is encountered - if (block.color.alpha == 0xff) { - break subChunkLoop; - } + // break when an opaque block is encountered + if (block.color.alpha == 0xff) { + break; } - - //start at the top of the next chunk! (current offset might differ) - offset = cVersion.subChunkHeight - 1; } - //set y to the "real" y; consider all sub-chunks as a stack of chunks. - y = realY; - //height shading (based on slopes in terrain; height diff) float heightShading = getHeightShading(y, heightW, heightN); //go back to "surface" y++; - - TerrainChunkData surfaceChunk = chunk.getTerrain((byte) (y / cVersion.subChunkHeight)); //light sources - int lightValue = (surfaceChunk != null && surfaceChunk.loadTerrain()) - ? (surfaceChunk.getBlockLightValue(x, y % cVersion.subChunkHeight, z) & 0xff) - : 0; + int lightValue = chunk.getBlockLightValue(x, y, z) & 0xff; float lightShading = (float) lightValue / 15f + 1; //mix shading diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java index 14d74edc..88bba133 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SlimeChunkRenderer.java @@ -5,28 +5,24 @@ import android.graphics.Rect; import com.mithrilmania.blocktopograph.chunk.Chunk; +import com.mithrilmania.blocktopograph.chunk.TempChunk; import com.mithrilmania.blocktopograph.chunk.ChunkManager; import com.mithrilmania.blocktopograph.chunk.Version; -import com.mithrilmania.blocktopograph.chunk.terrain.TerrainChunkData; import com.mithrilmania.blocktopograph.map.Dimension; import com.mithrilmania.blocktopograph.util.MTwister; public class SlimeChunkRenderer implements MapRenderer { - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { - int x, z, i, j, tX, tY; - //the bottom sub-chunk is sufficient to get heightmap data. - TerrainChunkData data = chunk.getTerrain((byte) 0); - if (data == null || !data.load2DData()) - throw new RuntimeException(); + int x, z, tX, tY; - TerrainChunkData dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension).getTerrain((byte) 0); - TerrainChunkData dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension).getTerrain((byte) 0); + Chunk dataW = chunkManager.getChunk(chunkX - 1, chunkZ, dimension); + Chunk dataN = chunkManager.getChunk(chunkX, chunkZ - 1, dimension); - boolean west = dataW != null && dataW.load2DData(), - north = dataN != null && dataN.load2DData(); + boolean west = dataW != null && !dataW.isVoid(), + north = dataN != null && !dataN.isVoid(); //MapType.OVERWORLD_SATELLITE.renderer.renderToBitmap(chunk, canvas, dimension, chunkX, chunkZ, pX, pY, pW, pL, paint, version, chunkManager); @@ -37,13 +33,13 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { - int y = data.getHeightMapValue(x, z); + int y = chunk.getHeightMapValue(x, z); - color = SatelliteRenderer.getColumnColour(chunk, data, x, y, z, + color = SatelliteRenderer.getColumnColour(chunk, x, y, z, (x == 0) ? (west ? dataW.getHeightMapValue(dimension.chunkW - 1, z) : y)//chunk edge - : data.getHeightMapValue(x - 1, z),//within chunk + : chunk.getHeightMapValue(x - 1, z),//within chunk (z == 0) ? (north ? dataN.getHeightMapValue(x, dimension.chunkL - 1) : y)//chunk edge - : data.getHeightMapValue(x, z - 1)//within chunk + : chunk.getHeightMapValue(x, z - 1)//within chunk ); r = (color >> 16) & 0xff; g = (color >> 8) & 0xff; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java index 5f145042..2813fb06 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/XRayRenderer.java @@ -18,12 +18,9 @@ public class XRayRenderer implements MapRenderer { TODO make the X-ray viewable blocks configurable, without affecting performance too much... */ - public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, Version version, ChunkManager chunkManager) throws Version.VersionException { + public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int chunkX, int chunkZ, int pX, int pY, int pW, int pL, Paint paint, ChunkManager chunkManager) throws Version.VersionException { - //the bottom sub-chunk is sufficient to get heightmap data. - TerrainChunkData data; - - int x, y, z, color, i, j, tX, tY; + int x, y, z, color, tX, tY; //render width in blocks int rW = 16; @@ -38,46 +35,41 @@ public void renderToBitmap(Chunk chunk, Canvas canvas, Dimension dimension, int int average; int r, g, b; - int subChunk; - for (subChunk = 0; subChunk < version.subChunks; subChunk++) { - data = chunk.getTerrain((byte) subChunk); - if (data == null || !data.loadTerrain()) break; - - for (z = 0; z < 16; z++) { - for (x = 0; x < 16; x++) { - - for (y = 0; y < version.subChunkHeight; y++) { - block = Block.getBlock(data.getBlockTypeId(x, y, z) & 0xff, 0); - - index2D = (z * rW) + x; - if (block == null || block.id <= 1) - continue; - else if (block == Block.B_56_0_DIAMOND_ORE) { - bestBlock[index2D] = block; - break; - } else if (block == Block.B_129_0_EMERALD_ORE) bValue = 8; - else if (block == Block.B_153_0_QUARTZ_ORE) bValue = 7; - else if (block == Block.B_14_0_GOLD_ORE) bValue = 6; - else if (block == Block.B_15_0_IRON_ORE) bValue = 5; - else if (block == Block.B_73_0_REDSTONE_ORE) bValue = 4; - else if (block == Block.B_21_0_LAPIS_ORE) bValue = 3; - //else if(block == Block.COAL_ORE) bValue = 2; - //else if(b == Block.LAVA || b == Block.STATIONARY_LAVA) bValue = 1; - else bValue = 0; - - if (bValue > minValue[index2D]) { - minValue[index2D] = bValue; - bestBlock[index2D] = block; - } + for (z = 0; z < 16; z++) { + for (x = 0; x < 16; x++) { + + for (y = 0; y < chunk.getHeightLimit(); y++) { + block = Block.getBlock(chunk.getBlockRuntimeId(x, y, z)); + + index2D = (z * rW) + x; + if (block == null || block.id <= 1) + continue; + else if (block == Block.B_56_0_DIAMOND_ORE) { + bestBlock[index2D] = block; + break; + } else if (block == Block.B_129_0_EMERALD_ORE) bValue = 8; + else if (block == Block.B_153_0_QUARTZ_ORE) bValue = 7; + else if (block == Block.B_14_0_GOLD_ORE) bValue = 6; + else if (block == Block.B_15_0_IRON_ORE) bValue = 5; + else if (block == Block.B_73_0_REDSTONE_ORE) bValue = 4; + else if (block == Block.B_21_0_LAPIS_ORE) bValue = 3; + //else if(block == Block.COAL_ORE) bValue = 2; + //else if(b == Block.LAVA || b == Block.STATIONARY_LAVA) bValue = 1; + else bValue = 0; + + if (bValue > minValue[index2D]) { + minValue[index2D] = bValue; + bestBlock[index2D] = block; } } } } - if (subChunk == 0) { - MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, chunkX, chunkZ, pX, pY, pW, pL, paint, version, chunkManager); - return; - } + +// if (y == 0) { +// MapType.CHESS.renderer.renderToBitmap(chunk, canvas, dimension, chunkX, chunkZ, pX, pY, pW, pL, paint, version, chunkManager); +// return; +// } for (z = 0, tY = pY; z < 16; z++, tY += pL) { for (x = 0, tX = pX; x < 16; x++, tX += pW) { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java index 9807ec54..0247662e 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java @@ -12,6 +12,7 @@ import android.text.Editable; import android.text.Layout; import android.text.TextWatcher; + import com.mithrilmania.blocktopograph.Log; import android.util.DisplayMetrics; @@ -46,21 +47,19 @@ public class EditorFragment extends Fragment { /** - * * TODO: - * + *

* - The onSomethingChanged listeners should start Asynchronous tasks - * when directly modifying NBT. - * + * when directly modifying NBT. + *

* - This editor should be refactored into parts, it grew too large. - * + *

* - The functions lack documentation. Add it. Ask @mithrilmania for now... - * */ private EditableNBT nbt; - public void setEditableNBT(EditableNBT nbt){ + public void setEditableNBT(EditableNBT nbt) { this.nbt = nbt; } @@ -68,13 +67,13 @@ public static class ChainTag { public Tag parent, self; - public ChainTag(Tag parent, Tag self){ + public ChainTag(Tag parent, Tag self) { this.parent = parent; this.self = self; } } - public static class RootNodeHolder extends TreeNode.BaseNodeViewHolder{ + public static class RootNodeHolder extends TreeNode.BaseNodeViewHolder { public RootNodeHolder(Context context) { @@ -117,9 +116,9 @@ public NBTNodeHolder(EditableNBT nbt, Context context) { @Override public View createNodeView(TreeNode node, final ChainTag chain) { - if(chain == null) return null; + if (chain == null) return null; Tag tag = chain.self; - if(tag == null) return null; + if (tag == null) return null; final LayoutInflater inflater = LayoutInflater.from(context); @@ -191,7 +190,7 @@ public View createNodeView(TreeNode node, final ChainTag chain) { TextView tagName = (TextView) tagView.findViewById(R.id.tag_name); tagName.setText(tag.getName()); - switch (layoutID){ + switch (layoutID) { case R.layout.tag_boolean_layout: { final CheckBox checkBox = (CheckBox) tagView.findViewById(R.id.checkBox); final ByteTag byteTag = (ByteTag) tag; @@ -215,24 +214,26 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { final EditText editText = (EditText) tagView.findViewById(R.id.byteField); final ByteTag byteTag = (ByteTag) tag; //parse the byte as an unsigned byte - editText.setText(""+(((int) byteTag.getValue()) & 0xFF)); + editText.setText("" + (((int) byteTag.getValue()) & 0xFF)); editText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { String sValue = s.toString(); try { int value = Integer.parseInt(sValue); - if(value < 0 || value > 0xff) + if (value < 0 || value > 0xff) throw new NumberFormatException("No unsigned byte."); byteTag.setValue((byte) value); nbt.setModified(); - } catch (NumberFormatException e){ + } catch (NumberFormatException e) { editText.setError(String.format(context.getString(R.string.x_is_invalid), sValue)); } } @@ -245,10 +246,12 @@ public void afterTextChanged(Editable s) { editText.setText(shortTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { @@ -256,7 +259,7 @@ public void afterTextChanged(Editable s) { try { shortTag.setValue(Short.valueOf(sValue)); nbt.setModified(); - } catch (NumberFormatException e){ + } catch (NumberFormatException e) { editText.setError(String.format(context.getString(R.string.x_is_invalid), sValue)); } } @@ -269,10 +272,12 @@ public void afterTextChanged(Editable s) { editText.setText(intTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { @@ -280,7 +285,7 @@ public void afterTextChanged(Editable s) { try { intTag.setValue(Integer.valueOf(sValue)); nbt.setModified(); - } catch (NumberFormatException e){ + } catch (NumberFormatException e) { editText.setError(String.format(context.getString(R.string.x_is_invalid), sValue)); } } @@ -293,10 +298,12 @@ public void afterTextChanged(Editable s) { editText.setText(longTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { @@ -304,7 +311,7 @@ public void afterTextChanged(Editable s) { try { longTag.setValue(Long.valueOf(sValue)); nbt.setModified(); - } catch (NumberFormatException e){ + } catch (NumberFormatException e) { editText.setError(String.format(context.getString(R.string.x_is_invalid), sValue)); } } @@ -317,10 +324,12 @@ public void afterTextChanged(Editable s) { editText.setText(floatTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { @@ -328,7 +337,7 @@ public void afterTextChanged(Editable s) { try { floatTag.setValue(Float.valueOf(sValue)); nbt.setModified(); - } catch (NumberFormatException e){ + } catch (NumberFormatException e) { editText.setError(String.format(context.getString(R.string.x_is_invalid), sValue)); } } @@ -341,10 +350,12 @@ public void afterTextChanged(Editable s) { editText.setText(doubleTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { @@ -352,7 +363,7 @@ public void afterTextChanged(Editable s) { try { doubleTag.setValue(Double.valueOf(sValue)); nbt.setModified(); - } catch (NumberFormatException e){ + } catch (NumberFormatException e) { editText.setError(String.format(context.getString(R.string.x_is_invalid), sValue)); } } @@ -365,10 +376,12 @@ public void afterTextChanged(Editable s) { editText.setText(stringTag.getValue()); editText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { } + public void onTextChanged(CharSequence s, int start, int before, int count) { + } @Override public void afterTextChanged(Editable s) { @@ -409,16 +422,16 @@ public enum NBTEditOption { public final int stringId; - NBTEditOption(int stringId){ + NBTEditOption(int stringId) { this.stringId = stringId; } } - public String[] getNBTEditOptions(){ + public String[] getNBTEditOptions() { NBTEditOption[] values = NBTEditOption.values(); int len = values.length; String[] options = new String[len]; - for(int i = 0; i < len; i++){ + for (int i = 0; i < len; i++) { options[i] = getString(values[i].stringId); } return options; @@ -432,17 +445,17 @@ public enum RootNBTEditOption { public final int stringId; - RootNBTEditOption(int stringId){ + RootNBTEditOption(int stringId) { this.stringId = stringId; } } - public String[] getRootNBTEditOptions(){ + public String[] getRootNBTEditOptions() { RootNBTEditOption[] values = RootNBTEditOption.values(); int len = values.length; String[] options = new String[len]; - for(int i = 0; i < len; i++){ + for (int i = 0; i < len; i++) { options[i] = getString(values[i].stringId); } return options; @@ -453,14 +466,14 @@ public String[] getRootNBTEditOptions(){ //returns true if there is a tag in content with a name equals to key. - boolean checkKeyCollision(String key, List content){ - if(content == null || content.isEmpty()) return false; - if(key == null) key = ""; + boolean checkKeyCollision(String key, List content) { + if (content == null || content.isEmpty()) return false; + if (key == null) key = ""; String tagName; - for(Tag tag : content) { + for (Tag tag : content) { tagName = tag.getName(); - if(tagName == null) tagName = ""; - if(tagName.equals(key)) { + if (tagName == null) tagName = ""; + if (tagName.equals(key)) { return true; } } @@ -472,7 +485,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - if(nbt == null){ + if (nbt == null) { new Exception("No NBT data provided").printStackTrace(); getActivity().finish(); @@ -495,7 +508,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, root.setViewHolder(new RootNodeHolder(activity)); - for(Tag tag : nbt.getTags()){ + for (Tag tag : nbt.getTags()) { root.addChild(new TreeNode(new ChainTag(null, tag)).setViewHolder(new NBTNodeHolder(nbt, activity))); } @@ -516,9 +529,9 @@ public boolean onLongClick(final TreeNode node, final Object value) { //root tag has nbt as value - if(value instanceof EditableNBT){ + if (value instanceof EditableNBT) { - if(!nbt.enableRootModifications){ + if (!nbt.enableRootModifications) { Toast.makeText(activity, R.string.cannot_edit_root_NBT_tag, Toast.LENGTH_LONG).show(); return true; } @@ -528,7 +541,7 @@ public boolean onLongClick(final TreeNode node, final Object value) { builder.setTitle(R.string.root_NBT_options) .setItems(getRootNBTEditOptions(), new DialogInterface.OnClickListener() { - private void showMsg(int msg){ + private void showMsg(int msg) { Toast.makeText(activity, msg, Toast.LENGTH_LONG).show(); } @@ -536,8 +549,8 @@ public void onClick(DialogInterface dialog, int which) { try { RootNBTEditOption option = RootNBTEditOption.values()[which]; - switch (option){ - case ADD_NBT_TAG:{ + switch (option) { + case ADD_NBT_TAG: { final EditText nameText = new EditText(activity); nameText.setHint(R.string.hint_tag_name_here); @@ -601,7 +614,7 @@ public void onClick(DialogInterface dialog, int whichButton) { return; } case PASTE_SUB_TAG: { - if(clipboard == null){ + if (clipboard == null) { showMsg(R.string.clipboard_is_empty); return; } @@ -613,7 +626,7 @@ public void onClick(DialogInterface dialog, int whichButton) { return; } - case REMOVE_ALL_TAGS:{ + case REMOVE_ALL_TAGS: { //wrap layout in alert AlertDialog.Builder alert = new AlertDialog.Builder(activity); @@ -626,10 +639,10 @@ public void onClick(DialogInterface dialog, int whichButton) { List children = new ArrayList<>(node.getChildren()); //new tag name - for(TreeNode child : children){ + for (TreeNode child : children) { tree.removeNode(child); Object childValue = child.getValue(); - if(childValue != null && childValue instanceof ChainTag) + if (childValue != null && childValue instanceof ChainTag) nbt.removeRootTag(((ChainTag) childValue).self); } nbt.setModified(); @@ -649,10 +662,10 @@ public void onClick(DialogInterface dialog, int whichButton) { break; } default: { - Log.d("User clicked unknown NBTEditOption! "+option.name()); + Log.d("User clicked unknown NBTEditOption! " + option.name()); } } - } catch (Exception e){ + } catch (Exception e) { showMsg(R.string.failed_to_do_NBT_change); } } @@ -662,7 +675,7 @@ public void onClick(DialogInterface dialog, int whichButton) { return true; - } else if(value instanceof ChainTag){ + } else if (value instanceof ChainTag) { //other tags have a chain-tag as value @@ -671,23 +684,23 @@ public void onClick(DialogInterface dialog, int whichButton) { builder.setTitle(R.string.nbt_tag_options) .setItems(getNBTEditOptions(), new DialogInterface.OnClickListener() { - private void showMsg(int msg){ + private void showMsg(int msg) { Toast.makeText(activity, msg, Toast.LENGTH_LONG).show(); } @SuppressWarnings("unchecked") public void onClick(DialogInterface dialog, int which) { - try{ + try { NBTEditOption editOption = NBTEditOption.values()[which]; final Tag parent = ((ChainTag) value).parent; final Tag self = ((ChainTag) value).self; - if(self == null) return;//WTF? + if (self == null) return;//WTF? - if(editOption == null) return;//WTF? + if (editOption == null) return;//WTF? - switch (editOption){ + switch (editOption) { case CANCEL: { return; } @@ -696,12 +709,12 @@ public void onClick(DialogInterface dialog, int which) { return; } case PASTE_OVERWRITE: { - if(clipboard == null){ + if (clipboard == null) { showMsg(R.string.clipboard_is_empty); return; } - if(parent == null){ + if (parent == null) { //it is one of the children of the root node nbt.removeRootTag(self); Tag copy = clipboard.getDeepCopy(); @@ -720,7 +733,7 @@ public void onClick(DialogInterface dialog, int which) { } case COMPOUND: { content = ((CompoundTag) parent).getValue(); - if(checkKeyCollision(clipboard.getName(), content)){ + if (checkKeyCollision(clipboard.getName(), content)) { showMsg(R.string.clipboard_key_exists_in_compound); return; } @@ -731,7 +744,7 @@ public void onClick(DialogInterface dialog, int which) { return; } } - if(content != null){ + if (content != null) { content.remove(self); Tag copy = clipboard.getDeepCopy(); content.add(copy); @@ -739,13 +752,13 @@ public void onClick(DialogInterface dialog, int which) { tree.removeNode(node); nbt.setModified(); return; - } - else showMsg(R.string.error_cannot_overwrite_in_empty_parent); + } else + showMsg(R.string.error_cannot_overwrite_in_empty_parent); return; } } case PASTE_SUBTAG: { - if(clipboard == null){ + if (clipboard == null) { showMsg(R.string.clipboard_is_empty); return; } @@ -758,7 +771,7 @@ public void onClick(DialogInterface dialog, int which) { } case COMPOUND: { content = ((CompoundTag) self).getValue(); - if(checkKeyCollision(clipboard.getName(), content)){ + if (checkKeyCollision(clipboard.getName(), content)) { showMsg(R.string.clipboard_key_exists_in_compound); return; } @@ -769,7 +782,7 @@ public void onClick(DialogInterface dialog, int which) { return; } } - if(content == null){ + if (content == null) { content = new ArrayList<>(); self.setValue(content); } @@ -782,7 +795,7 @@ public void onClick(DialogInterface dialog, int which) { return; } case DELETE: { - if(parent == null){ + if (parent == null) { //it is one of the children of the root node tree.removeNode(node); nbt.removeRootTag(self); @@ -791,26 +804,26 @@ public void onClick(DialogInterface dialog, int which) { } ArrayList content; - switch (parent.getType()){ - case LIST:{ + switch (parent.getType()) { + case LIST: { content = ((ListTag) parent).getValue(); break; } - case COMPOUND:{ + case COMPOUND: { content = ((CompoundTag) parent).getValue(); break; } - default:{ + default: { showMsg(R.string.error_cannot_overwrite_tag_unknow_parent_type); return; } } - if(content != null){ + if (content != null) { content.remove(self); tree.removeNode(node); nbt.setModified(); - } - else showMsg(R.string.error_cannot_remove_from_empty_list); + } else + showMsg(R.string.error_cannot_remove_from_empty_list); return; } case RENAME: { @@ -827,9 +840,9 @@ public void onClick(DialogInterface dialog, int whichButton) { Editable newNameEditable = edittext.getText(); String newName = (newNameEditable == null || newNameEditable.toString().equals("")) ? null : newNameEditable.toString(); - if(parent != null + if (parent != null && parent instanceof CompoundTag - && checkKeyCollision(newName, ((CompoundTag) parent).getValue())){ + && checkKeyCollision(newName, ((CompoundTag) parent).getValue())) { showMsg(R.string.error_parent_already_contains_child_with_same_key); return; } @@ -855,9 +868,9 @@ public void onClick(DialogInterface dialog, int whichButton) { return; } case ADD_SUBTAG: { - switch (self.getType()){ + switch (self.getType()) { case LIST: - case COMPOUND:{ + case COMPOUND: { final EditText nameText = new EditText(activity); nameText.setHint(R.string.hint_tag_name_here); @@ -899,21 +912,19 @@ public void onClick(DialogInterface dialog, int whichButton) { ArrayList content; - if(self instanceof CompoundTag) { + if (self instanceof CompoundTag) { content = ((CompoundTag) self).getValue(); - if(checkKeyCollision(newName, content)){ + if (checkKeyCollision(newName, content)) { showMsg(R.string.error_key_already_exists_in_compound); return; } - } - else if(self instanceof ListTag){ + } else if (self instanceof ListTag) { content = ((ListTag) self).getValue(); - } - else return;//WTF? + } else return;//WTF? - if(content == null){ + if (content == null) { content = new ArrayList<>(); self.setValue(content); } @@ -942,14 +953,14 @@ public void onClick(DialogInterface dialog, int whichButton) { return; } - default:{ + default: { showMsg(R.string.sub_tags_only_add_compound_list); return; } } } default: { - Log.d("User clicked unknown NBTEditOption! "+editOption.name()); + Log.d("User clicked unknown NBTEditOption! " + editOption.name()); } } @@ -969,7 +980,6 @@ public void onClick(DialogInterface dialog, int whichButton) { frame.addView(treeView, 0); - // save functionality // ================================ @@ -978,7 +988,7 @@ public void onClick(DialogInterface dialog, int whichButton) { fabSaveNBT.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View view) { - if(!nbt.isModified()){ + if (!nbt.isModified()) { Snackbar.make(view, R.string.no_data_changed_nothing_to_save, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } else { @@ -988,21 +998,22 @@ public void onClick(final View view) { .setIcon(R.drawable.ic_action_save_b) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - Snackbar.make(view, "Saving NBT data...", Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - if(nbt.save()){ - //nbt is not "modified" anymore, in respect to the new saved data - nbt.modified = false; - - Snackbar.make(view, "Saved NBT data!", Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - ((WorldActivityInterface) activity).logFirebaseEvent(WorldActivity.CustomFirebaseEvent.NBT_EDITOR_SAVE); - } else { - Snackbar.make(view, "Error: failed to save the NBT data.", Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - } - }}) + public void onClick(DialogInterface dialog, int whichButton) { + Snackbar.make(view, "Saving NBT data...", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + if (nbt.save()) { + //nbt is not "modified" anymore, in respect to the new saved data + nbt.modified = false; + + Snackbar.make(view, "Saved NBT data!", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + Log.logFirebaseEvent(activity, Log.CustomFirebaseEvent.NBT_EDITOR_SAVE); + } else { + Snackbar.make(view, "Error: failed to save the NBT data.", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + } + }) .setNegativeButton(android.R.string.no, null).show(); } @@ -1022,7 +1033,7 @@ public void onStart() { Bundle bundle = new Bundle(); bundle.putString("title", nbt.getRootTitle()); - ((WorldActivityInterface) getActivity()).logFirebaseEvent(WorldActivity.CustomFirebaseEvent.NBT_EDITOR_OPEN, bundle); + Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.NBT_EDITOR_OPEN, bundle); } @Override diff --git a/leveldb/src/main/jni/com_litl_leveldb_DB.cc b/leveldb/src/main/jni/com_litl_leveldb_DB.cc index 639daf51..030387a0 100644 --- a/leveldb/src/main/jni/com_litl_leveldb_DB.cc +++ b/leveldb/src/main/jni/com_litl_leveldb_DB.cc @@ -126,14 +126,21 @@ nativeGet(JNIEnv *env, //if (iter->Valid() && key == iter->key()) { //leveldb::Slice value = iter->value(); std::string str; - db->Get(options, key, &str); - size_t len = str.size(); - result = env->NewByteArray(static_cast(len)); - env->SetByteArrayRegion(result, 0, static_cast(len), (const jbyte *) str.c_str()); + leveldb::Status status=db->Get(options, key, &str); + env->ReleaseByteArrayElements(keyObj, buffer, JNI_ABORT); + if(status.ok()){ + size_t len = str.size(); + result = env->NewByteArray(static_cast(len)); + env->SetByteArrayRegion(result, 0, static_cast(len), (const jbyte *) str.c_str()); + }else if(status.IsNotFound())result=NULL; + else { + throwException(env,status); + result=NULL; + } + //} else { //result = NULL; //} - env->ReleaseByteArrayElements(keyObj, buffer, JNI_ABORT); //delete iter; return result; From c3d9ce1aa9a3494ef98c8c6801374569e6f1b967 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Sun, 27 Jan 2019 17:57:32 +0800 Subject: [PATCH 21/83] + Entities fixed, icons updated, new entities added. + Added a notice suggests users to switch Minecraft to use external storage when no worlds are found. --- app/build.gradle | 4 +- app/release/output.json | 1 - app/src/main/assets/entity_wiki.png | Bin 28016 -> 33402 bytes .../com/mithrilmania/blocktopograph/Log.java | 5 +- .../mithrilmania/blocktopograph/World.java | 112 ++++++++ .../blocktopograph/WorldActivity.java | 20 +- .../blocktopograph/WorldData.java | 16 +- .../blocktopograph/map/Entity.java | 256 +++++++++--------- .../blocktopograph/map/MapFragment.java | 3 +- .../blocktopograph/map/MarkerAsyncTask.java | 2 +- .../worldlist/WorldItemListActivity.java | 50 ++-- app/src/main/res/layout/dialog_noworlds.xml | 20 ++ app/src/main/res/mipmap/external.png | Bin 0 -> 171280 bytes app/src/main/res/values/strings.xml | 14 +- .../java/com/litl/leveldb/NativeObject.java | 9 +- 15 files changed, 325 insertions(+), 187 deletions(-) delete mode 100644 app/release/output.json create mode 100644 app/src/main/res/layout/dialog_noworlds.xml create mode 100644 app/src/main/res/mipmap/external.png diff --git a/app/build.gradle b/app/build.gradle index e9212635..dc576cdf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,8 +6,8 @@ android { applicationId 'rbq2012.blocktopograph' minSdkVersion 16 targetSdkVersion 28 - versionCode 12 - versionName "1.8.2" + versionCode 13 + versionName "1.8.3" } buildTypes { diff --git a/app/release/output.json b/app/release/output.json deleted file mode 100644 index a14f730e..00000000 --- a/app/release/output.json +++ /dev/null @@ -1 +0,0 @@ -[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":10},"path":"app-release.apk","properties":{"packageId":"com.mithrilmania.blocktopograph","split":"","minSdkVersion":"16"}}] \ No newline at end of file diff --git a/app/src/main/assets/entity_wiki.png b/app/src/main/assets/entity_wiki.png index 5fa336a7c5e745c5deec9970ad05c1878df248e5..13b52d60de88ec1d153c2efb0ef4064f4848a301 100644 GIT binary patch literal 33402 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 Ty_|ml@J~rjP4<_xalrooTX_A5 literal 28016 zcmXt9Ra6~KvmM->;O_43E+JTgCBfYxxE(mSI{|{bySoMrF2Oz6!QJ8V{cGKanOUp* zrKhWES9k4Q;c6=KXvjp!005vVD#(0(|MvX%Ai}@DKd;&%000G`C?l!iv3#uO?n5$g z^LBcAlP7$m&oYv{+RJCZSoGTsx`;N;kb_kMQ8Z}^1}RelF=!$dTN=EGgfu>ghQl$4 zw$d_MS7Cg^wwCGQ`sevIb*X)2Rx#OD5PtjaL7`b#xc=tmZVzqY{yY#CSj{8_0C{5} z2FL5}0azeTkPyhhsLkhF&hot)Cy4>*@XW9QOqXk(IvD`ye(6t4^ZnjrJAGXJ>cI#Y z`q>d903SYlFlAUE<+V=Q>%wu8!?5ZoHX=s=RHapcp5N=*z%@^)LpSH zI=gQRJab6wW2hrfD@IADq9#crHUu6QSM(m#sH<#qaAW0|rH+q}r-4%b%!dLR2Zx7} zzP@6eSy>C~aan7jDoW{JidEnMk<0cKC_pRmw4@XF!$rMw-PGb#P}JBl2iEU^(Fc>e zr}j6kSzBVO6&Iytg-Y9O} z!QBq+sFg7D0XT=sO2F^_m2hZsWF$9(k2w+U}XA~1_2#Q=Z(Fbja z49w(JpqPQ1iWWIlSTgj`76KKNi^wi!NS?@YpFmyS`KMx2Y{>$W$n&Ia&vbR?ygIXL zYk}oJSH(oS>78S5dOEtgBOBVZNBy?8 zHULODW?4`GuM^@henAT(B7MyaFT8#hV-oB#A^}keSI0+_j}JbX41Qm_*XMg>dfycq zaIYc3W$J6o^;RD}MhuDryProNeaZ7;lA9cEfyaBu}fS5eS_eK)TAaVL_9x8OK$g@;6! z7I7Y>zzYknJLd*I)A#XETFW8?dPNLGd^XRw9V4H;;P`f@?@L1auvGIehTO3O0*74p zMBlu|Yv`xB|JGl}E8R9Jp23R&IG#OR9X^?0w`B?vwRwy}35(9*F^?(~o_vBf;B{sJ z45i`cPvGckd^(e)KZ@9$*IX5v3#L-{SNF!|Sfku@|o!$>UdeG&9qOfUWj zAL4h^bPNY{{9dlM=!U%hp}yjMB0XN{9W)pl>>gXL{xVcr#!p;*dNt$;#=BkMBl}ys zbN&Kbb>T1n9&!Z5DOV*wE1K2(>ieQJvmWB7*ChmX*$dAzHdg!Q|Nc$TKI=~Y>?Jh+ zg3I`u;d?n`&Rs3WY3n4`PXJ?pltGWX**hjIH$+Rw`X^t z$Exs7E!@W%?rMGd?EYE)y7l$Ts{pXsNbCy_=%jxVyj@Olx3VigDxIj{|6IKhVbL{D zl6~`s^IE8Q4Gr)($PMKgBKO#Y@&fI>b)R)T@eDb1XL?;_d7SG%u2yWwuH0GE^gk0Q zst*XXtQngNbrlev8SQ9}#9>o2CM@2=mV4gYg2|Cf$g?vbC*&{dLA-qAYlh^UumHh!>Yj;mEnfLYf@d) zLwV_RXP+$dZMtq`a|A;wj$c|Q3fk@&_V_V+p$!ydzJHH4*R@wcXOR;{+`0dNrh&bZ z+AYp3n&`xnj{}_dwxOk6;E(4oR92Ql0~$^06Wu1LAT+CyB<*E5qO+yTUH$QUBa#U2&7Uss(c3l}O2)>GQrgqXgH6<~q5`?({RhHd4& zUfJ+L^&^T%6`9Kv=JozdQBJEDo%%vu_1>Eo{ZvrsL&ez%30xIPK3gE>{*?iBPJhdi zijpuYuzH|aGvot=oxqKxYe=7>H9Z*m*PGiT(k!EefP#2aX&mI5KHG=uWXOpx&o{n_8ab_A`PBXpmZ}R-heJxcZ9Mv8i0LtwtzKqz z_JI*WWKrjjFJt_QAgjao+a+{B!aUhu(GjouV)|^6z`BqxB+~cppKYa$_r4cVZ)0IP z!9Fg}dX_vn$ff5FCmn^?VO?arYe2D6Bc0C<9!SEg4`WrmFxwYnYAsSNzCUztwxQ{X z8){z;QHA~uFANXk)#2W;8k10?ggk>DiL_a#mRHk5XHN1I880&T`cDf{D`Yihlu73qTNfn^!p#?1;EI) z*u4cfF_1_rD|;C^bG|0f)p*Wh`CYZs>*7=FPeixdqX@ytiV@lKU>cX!1|;`cRRXBU zT=woV`+Q$l*<7CN?FQ=@1X^qERJ2dc=nQGMRfA-MJUaHMk^c|~RZ%Sb%EkudpTZ>- zuT~T3zl7rv<2EjpDCg2i31#BOdU<)l7s?SEchyG-e?~<|^Aw2`MvH_c5O!m^liosn z*?lCLgnIP%@<%mMp#sib2zOoFmD7}CInezv|14j9j>!D3>RLKkX9ascQaJbaCNV$? zfH}1w^8)O_kEIr0_W=V9NcPp@=l+v5?Ma@yYCplIbql zN=NNtk&~#X#(c>M8laYo3iE!}iEh?mp-FQlXR76F%AVX8rrIkm$zz6JUUGm?E+?jj zU@bGk5}>jXc#CcFlkG&eH8A#EQRSZS^JBW< zPuCH<&+aPleMz*)fCns{)ZcEU;M;=;#8E$nqgMdm&Dl}_ugkEBNzFF)=VZ}Z_aSwbyo{!rPO33)wHK9&oX zT1TmwS*bLda6O&OeGHj${|JpKB?zji{=pIn`W0M7o=;Dfab={)u{ z9`dWt^rep!^cDO4ciaE~e7nw5h%&poeP4d~xOR)jkgup}+EjHsU7X*n`)p^`Se{y* zGIQpyXrO0r<`DJypV~7$Fu}XnE5=au`buf_(ZYNJf5^oQS81%(9w>NTp zCM+`X&Kp8RQ0rIcavw(?T;M;i2z{L178qrC-R?Y_av!3$Ma|C^Y|twIpkIyeyf-#| z@F4zjs5-r*IN~>+#(TdRi1An=`98g{A;mp-!>ED#vg>}P#=ele0R@olTKc(GRbFvH z*0^Y`1wFrfsSgPhCRtXn*L1xBljxGVZ{cbONa&_ zc^WWo60bh_&y`t6wnG!<-vJ}0kbHR3QN);iHc@o>Jvb=}?8Ua5zu%E0P^iP8(u|Fo zSAJZL^Vef2%+hChp9-G-#3vUnBBNZ}e`pYk_;vWmWMM!$tG;%3HTMQ`XN!heHOwJg zvFgrs{Yuxxfd-uSDYJ9&h9qoXD)R`R=bHKD2sKx6R={Eh_r&4+2$Z819}?*1R7E;F z8pB#ssAR)LVY-W5kD)~^pO2s?E7H%tMSsjqW-0W%X`^9UN+?qQOJ!=VmbfbonNPd1 zfvD=dF-2>$2*jET3*lHj^=oGrxBh*p_Co|5UEK#d4Sx3D@%wHSrdSOcxE#`^?O41f zX8C=i8fSU|jXjV*M=cwsw_D*I3Ri$u2$K?Vqqq;evum}E{5S6N-R!!rth&3m zVvHnz2iux&7IVhm>2!B9VQGZ-nv0mMYA?rBGIiB$h|popA$A-J+rgZFdlAq2RxpUr z_3m5exi~5UMiOiK(QwC$wL1bwV(3Y^L~=3o(@8nA>geouT;etug`A>SZ+}>*#%c*o zR9`YDi0>D^mDM-BgwCvYY0jsi6i((R2|p1EalU*$V+Fu|4KL)l>i)&$#7-I{;2l8; zsOPyQSZA>>ff79^J_4AxvB3sb8fgb$Z9hy>!28gZDE8pENT{PG!70Zs@z5CoW=_e) zi?`F?z~G~WW97OE{XDzZs~|d{|Gef33y36qNYRstKAm>n<7z+P270bk*F#pZ=F;8z zso8C!(oA^Q4_y*Ggg_PeCEYu0DUmSXqpJJ`ZR#Vyao~e?P%}QeKFPY)RoiDLmX?RE zVqPJTEeQq7mWoigZ;IUtPg74gXOm9#WAu^({1e(fc1f(+TZ{sbsSbv<4AAT78B(?1 zexlnd#oi3UQ900li`{<}VS|f-p$4>I=EfjBJZck_iwr}_I%^cASzd%Jv^-Kw+%amY zGIn9O%s1$uR%?@0l*X!DT(Xkf^&;^U^-*?#>7`-^f(OqYk-Rfb$w_KTMy!r3jL9!y zyp|?<*=0%^aIULQ*`4UXy$^AtI!U<3OyT_WenJKhbeGUR1+ba%E$!vrw7%)q0)&<+ zxjURmWdB958aZaQw4{0o`|bsG{~j)7_PL@DYhX^y=n4Pz-<;Tv$;)Ht_PzWclmf^l z)BaiP&fP`Uz;U@4--q&`Ma5o-ox5+SEfBNQbM$aNE>kRZ-qv-h+RGrpfryDJ@}KW; z+>s-0#VYyd!z}2!^FsmM-Ed&HJSBJ4e=C{p>z<%NF|Rl7)&4tCkZq@gA-6SR`W9{E zTzMbX-Vh&$cWb0r&V-5;^PE&CQsj$9=!9V41e5}a{LqUzXQprl^EBZn_YKMY7_!xm zia)NQwR}e55aH8Sa57Q%5A$%do@?K3Yp~9iZ>@^IJy5ZIKKfhEGqk$vg7>Z1P9yswa2#56xmB|E^;O3~&DK<)sT;W!V#j%X0ttKlh*hmN z?LLlDK*a+@EB73EB6^X124j~@6dWaz;HS?Y-gqI*yOG4exDO0#NV$6P%|^~U+y$33 z4~^~tkzz#jQ-7k&k8vrBH|}u@)iCC_xwuN!DxLoRGfkt;*NRrN2rF%*u5L;3MApdA ziuPk0^wC}kPj*3bSAqPgv|lCTx3%>!!WPun_->!;pJ|Z|a#K6ue?E}gMsM5V5Q&^q$39lW>*dz&r-TV5zA?mrYMHv1|Ws{ z#^4eev|)3aV)dODJf@iH{-UzVV}O45CAT>6wt2U{ulwlMQcyh5{96#=FReX(#<$Z# z|4-REGGlT`37>LsJPOmt(Q_^2@$8TyR3&r_;{T2M!K zE_${cB@$?CS6i@zGSS!8*4~W~9v;rX#uhSSI)e>nmJz(3p(melGF2}&>-DO{-(2=) zEGpH^Y!*>0L-c|2}J)q28y6PdZYkMyiAcvn*#rc zXY_m+`?K8e9wW1iY(?lAsugmFBE&4ylzy4>i_jHb87yVkGS(Uk+HvN-{5cJQw=%)Gn3VJSwDiB@5`ID3q^a%xkgdq>3k7v4NEf=iLH(V#oHVm<1^ zPTYhquddY9)&1-0>clh!KY#h6uBHYkCQw9dW5`3x5Z8v_(_wWvI^w&nNV7tkt_?YX&%sO_8%(qkn-6adGaQ{hi<-aTO<2X8vHPEAikNn-YG z#O!;%*Ktg4kC${>oeGnIWU&VkU~_b1byOi7-RmmW&)if|@cyU!+F+8(M_-4odlq2x zmEN*@PXuz-9Bo>DW~VJ5nT5bZ1JfFJO3v7L4iNj~v~6`vRSC zs=@JcvML7oBx@p@T2FrLOI-!aq|gx&X#s=!VLckT$s)#x*x9P#j)z$9N3k)&jR!xM z-C4tT%czFDltGJ8dytvV`U*FDha!!7Q2hL!OM9CnqW_XDQhBV97VNk!SHC5nbTSPr z^!2NZ`frIHTE~{d82O!O`isF>5>X_4mMA|^ujSw*o_?@1dsvisml(K=jB`R5kBK1yT2}phUUojUP5`0)3pNi(lbGULF?j^q=4~zK_{xt7p3R~MtkW(_xdK0z zCwDX&byQ)#G@>J;ditE=As8t{A|ePy1Qij)O3p-YP13{LdrD0krFA0kwuwYPEcnXt1$cagC%`{e3LEmxmAy>BlMTdlLd66LC=UtH4#*99nl-@Nkmo2*BMa#4}u>FMnoZ)h)s=N=y8vmkQ?F;Aw&7EYL?dz~( zvIee!m;r=-e*%?j{4ufjE+I`nn*(+P#_qponOmDTpyl-aorig%-&q0x#|a+0Zp$}6 z9!VMU_RtWc^Okg{Ykd2Hr}9)o(?Y$AXLg1mJrMs%$^`Q3%U0UgBphTCuTH4r%ERAd z;jM_UG_V1bN%JnokVO^-Yil89_BO!)3_xizBaw zz&9I1#e&MqTf|Wa=Y1mK^XI9ZAE72vLr%{aqbNzgGNTyX*?RxdQ-?cJ>j;CzhK!LY z((127Fk*K4j5T<}O#TRUwcG}a3B5gZdwh70DTHN)y68{s$wk<@_kQ=|E6c$H^y3It zcE!Wb&(=`<<0<{!({^Q06=d0NC+7;mm&+=!v`BP;WWL>q>58$Pnxt_waFpr+^94gq zAXW;X0#CH#eb2_|6zZU3V9b>pdv@IN>+4AcTOTYPY-F|E(1{>6@kvPm6BBL)#TI$3 ztyp~Dp;u4s|LyICYU0)JF2*eQSF-M++?z<5jveK9S;+pq{h}Rhdi%Rfvz(`2kaYK1 z=JSH{Pu`cgA9>a@-0Z=S7tR`&&{NDC?W6f?gujzj+~~{~jwA^0e=Juux~mC2b#$YK zOKYz;t8;HV54yij9R5_QD&7i{T}kAD&U14rY7m>yd-p`ugic5ovkKoaiQzQ%D6 z;`lYD@KHsuXvfcy53KWrZ2h%A0{_QL1pW@$(Ih&iELI&0H7+tGF0y2@nL+~WJ6Qg? z%=s%tAXte=UHsvuog9UUi+*YM+~C@`RVd6;S@1N1ojvnN?IBbCMxXI-(_S; z%=hd{f{w7f@=XF!Qd^$**_XVnT{!l^L*?OIG!Lz^&S0>&-xaA}HelXGn2GY#?^*UY z@#GEGnRrV|BQfvxr@|@AD@ts#pEk^VV{`4T< zPRAY$dkY4E4 zBC%T*NF?YD<`H74kav={cU!`r!!di;RO>f>w4zx*D8w>wHRr(r3~BZHvA4I&iyKFb zr&4p8g#aQexFKo++}wo=3W|3d-b*7(Q4)&w0$dqLj_%%T9qR-nSBb(ire8xCLUM3i zHa*Dl2m&_tr71Q;M@&Q3R5B3~SbBHdLbV;}gD9AV@&5}yDd=Ys3ULI5ldo5g6zyA@ zCmrDB`n}+%p)v_TZ*JVYzSq9Fzp9>EG8<^0KxcM9qxNgS%E(TuzBp(B+0VHyyc;t5 z`6A4GIx38_f~ZT$A=i(I}+qcC&4mh|<}MNy10% zrs*n`gqs>Ia;xPkvH8AOIM>Xz$k*q)@k6~#p3S-GzwnC5Q;TXCo$KJ!Q?}Gk<~+Q- zIpyUz2pC$D)%+64v;~D0eOsS`REmdc$*NIQ9k{#HKPvy!q_jiSfM$^!n7C-z zh980wne^WmyghC}3-XFE4F4LcEjp0Dk^2_LczvE=7cb3#A*6) zs%*hhKr;Emb%+$E2iYcW%PQr+B487!HV7G!kiZR?c{b%~Y*pXDMfNrVdMvqmjT@yL zrl|j+^_p_M`&i(P=c29K_3ey{r~f`BGqR~7^w8Ci&-0rL+k5rywSR9sy%AN}u-&O( z5EnqSeFq9I-sA;DsbeoMw|f~J`!f?7vP+M5_V(UGUVbPpmjBtn_ugZIw^3RwD;|PM z?qKDP_Hb_~Qg|F|F|m~|mU0sj?5v_eh7v5U!|&&m6f$?Zf-6pR{XVv z^hMkHQNRBpxNe8K4UkCXwMpKM;+{x-93*zI9sToX2RNWnOjxKk*!Y8(yYVUOPj#o3 zG=)RwB{DzmZv=3={WE9NX@#`D+vOG}boiBS;3W+Vruh9P4DZgEp8)$b7jOP$_y21F z+8BI58(sbBuE`qMBaoBjZthdTWxnYMkBlD9^q&*lSl965HgUf!R4WAtaHWTk41wSA zfWpy2HAa;|x9H6`JHmHBkZMtmybWb7eknrlNe}!3VA*6Z8Le9EfwdI9*!d^+p zRx_u#IWkhquC0m0i?Xet)GACXSS3@GTBM8HnzCJjqp#)Dpn~k)_4P1vayF>eUdLc> zcR^n%4~sl(u4%Q+3McK)XwBu-Bmzx<4z!xl(S*>m>&Tx0L4XtCidB zHO(FzW;DyIplwAMd!Url^xtIbcJZA0ji%{LJZH)T&K;=sCQc?!LnuBf$~_&gp066l4iZ{z4eIl!P9|D<(`?{2EM%^ zSb{TDBMVnmp5lEVr=cNy{ z$>Nz03I#Ox#P~ci8mSp1n_~*D`$rgZgY1l9x_(=|VaQE1W#Vyx?yqF6*?pl*eJc;y zvZJAA*`!}dj0a%yc^3R0{~rEJOkwY@LdM@yo){Fycn$-=OQwCjw<-lIjg3LyygL}! z*)f!2EvepVq6+ho>@o(b5~p@`{Ca_FwHChy+=EHHR9;saU%fh9B{JxVEpV*&ZQg(w z9zGjED0>PE95mY}qU_j{cIvaMQpRrDY~4#CxAyY${3t;Wn_F&JfQ615-A$WnJfo6V z=p}lw*&&-rRKd!M(SlG<2759!dat^xH{t6}-*9yc^&~S5-MeE*XeYs-NEmD820j1y zSK#>-@es7w0wOL$RW*4*sXTX6@HktKPW5(X%z8c$%5Yz2)M+=T7m66d(cZN`ZGjb# zYI*skto!c2t%W6snwpvfQLrA6(ksPxqfUrD9gBfXm{Us=&n`*OH2}nWANiB7qh-rK zeteQ0o^)7=WVN}KvH;v*i2fmfg_~Z(|8t4vXp(Rk1aw0V?9FhLEQ+Vwlm6EItd(L1 zk$PfuE81XiyJOn=!(tVJi-2>zyD}LszJM zCkRr&i1FcF$9MO?174sI*lfQ>1b+P=l*w$xh3aB{AgHG2T}2HCSpAc5gHbh2KlsRJ z!0~R){qAt6Llh=}SJX@b+H!FL{U`}wK_@1{xRy%B1tVZ{vz&XuSUMo*xg-t9T#KZ+rRUv40(cqPRdbJep7-jD-@B)cyXQ z*mK9V4R4t^B|B*C?Vf0!*kAmPU^4}r@%*Vl9SM`n;M=!R`4i{R9VBB2YHfxPTtYd5 zRGu4%hmsTO(657VJofE_lcuz;YFx1!Msf4OaMGWN-sC#A^A0fgDQUPW`AX~;-^ADu zCw{sQ*`dnm%u^kW&}EOaD~irFrvNMUD=vfo$$Fib*i~QHj(!e-THb43e8)R6)q8Yd z__Q}})Li^nO!a1Hc#~pA6coEohC1h_uDw6t;1eirZ`+%{hWoH0k}C#LovSaK^!vU3 zVSefZHcp@4e``abpAh+{GflrmPTsJS`^P}axihS;vQvRD5xjQf&B3%QlHocdb-Z#A zNJYAAw2K1E>o;TvriNi#cAg>6jIe0O;gcIYC!SW+du{?=;Cv1QdN#{} zQ(ifj(`qnPvXOv@_z-Q6A5_fTq&O;);}FfPwycv9E)+U1ei#-MP4_3Um2$hI1nuDhHK|;lJnJG3X zJuZ%wQ6q+@EN0b#C5uTIbyLhd3S8{mg`=%21VbJo!}=vm2aVtznG z1qCjqde<3H;X|d8>P8kN2Lqdl4xdPdgv3`+K$BZn6rub%PBJVC=EKu_%~#-r&vv;5 ziYyjk!+wQC&PQ97eNdGkwx|d`Dfx4ADGM_V19L(Acq&HSi~nmE1Z(8(SHC&k68ciH zyiZ!vJS&ZvE6Ur^BohpQ`sJG6fP4k?;Rlq@30!fT3O(yE!g0b>SjdQ|cpt;5ZCf#2 zJ4@E;vCvJ3(!SsJun^|X9>trE{4NNm@0*{E{F{tt-ioDThpw@pK}{R{?e_h^;lH9h z4!OV?%pW@OrwQI{_(Bs05WoqQ-rqDDVCVNZS69Cz;3m`Ow>98sza8M7z7HoHY^atK zBLXzVvmdlNIIWOHZ4LBB=s3d%d3{(mO-|`^=E?WZFwplOb!+b8Zare}d0PJ^ZRFMY z=Lm#Ke_fN}qH}m>i`fKfVk6~&zzA-ZZw(Cg+3p*l=E;|A$5BQAoE(VII5`O&3wa`* z)XKEADI}8RDvx#f4LdcbhY-5~-K7T}F%D^82`n&0Xlq}M3y02Bk>J1#%DL*ZuBQ@Z zg{#Yx*!1+doww%U^ENb7s`IS}eZTKeM17d$=2oAy78W|;J6>)?xpC!1gxg_sQIHG z4V1&t5rv?T)zwzMp#@5Y%qGQ2qRg1cfL%;h zQ?Yq?`Ayz>*J_rJ6+XI9y;p@WQK=4o+QK|Fa&bui@R2>iT~@ ze-D$p+Ah-gPwYDN9=aI!1u4W#w7DK}SWJ*qd~as+`DFLSXn`#aern#Y^1V4dvv z;g+|d$&G?>#6L$oEa#uU{#5RsA?pmb6NlTW;$pi4vF#&`$4`4x`q&P_0bNvGVR`kxSR{NAe-@_gXhVP zi)PBs`|s6lNgt;qb^V5#H@0c{PIu#zcspGfMlw9H!G+TUb#KH|^6up7Qfl>n2Q6)e zy#X!yB{~8p0Vn`qhDz=_maMUn{a9Y5$RALtney9~J#%z7KVCn0@#R3wTKRT6&&Ojx z1sC^Up72xzR#VV_0LoSr^cbhH8Wh~U-shu;)5NeDYR#pZk zHrarmPA%`w4GRtRUPw$8Or-1Tyjx!pFnX53WK5W?^?TEg%66pcy0?buNRZ;|{s#X< z!`*h&q0qJY2|U|-f6OJ|Ad0~lG<0C`Rw?$OtL>Fr_gsDAF5v3y{&s~jR^1NF{BC}< z@63+V2?nSm@Tr_8avheJhBZ2~#pdFnSRi`v#XY)aD!~+d*zV&+k0nQ6j({OwdhZ1x z;(g%e1K?5zH~+L8_cK106Q{1`IgpltVK}gP2%(WoJ9aT9Z~Jo;6PbUeuzSz(O>Su} zIxTI!QDbVY?E`|Pja?uC@_)!1(q`?EKVz@S9v1^srX*Z@R%;&hsz`^u@rL{J#`pO9 zQq-@r>b{|BO%wAfLSp^$!Q=?n)R!4u$BHJA)a;g~GN!-XV^QMP7RS}|bP1(alVt|o zj}#v+7T8*%eh{6=q@`ufk$IX4>a85#Se%OfyHUa*L3Dnc=RAAK40n+wJ7$TEd`@9+ z#k3-vokGMH`EGOmhZY}w98*k+pa(NKl$%U##O2A4eOe_bl|MFlL_@p5pw>8e?_SmK zaaMsOr>+kB=g*(ueZJgj&xbc#lpom#1%A)N3__iMg0?Rff!v6{gy| z6}0y)lK$p}d@VO|65l>*!@((l~vhdb(#N5^ z0-DQWscI=ZFzbh-^S{n6R5Hd@g!>g8Q2%Ymn8AnbC1E@hrGd5@G{?UsSn_B{0aT3+ zd2e%EB!R)7o08_#1mCXnR({+hpu}hXz}DAqf;U4DY@i@Fu&?ZM$P3%FB<{PQ$okoBS0!`Upx@`wSkOAkulD zF)cO+;KfBm#pV{~r5&d7`RBst=4f2pTzgMW<+RN4#RYWhF9p0WzN z8P0@%>3fkcpq2AE^vas`@IM7wpZ8Gw{VIDt0h8;s1P&*Dsge7f9~Fw$1AgpSsa1x0 zx@$NDKzP+?1KJ#njwsKT6;n7sx4r2lP(uAk+2XKo6Rc_YdQLMMI%6S@-4LR0=CtcK zSF^{KCQSp*Y(y9eDtS$a3re&(pSEqV1_Vh=rvhYe5QI0aaV2tkz8cELwaUa6p*il0 z7CWhmy}}8SNPBPy`H>SFP7eU!^s_u>l#`V%(bKyxv{G(Ztf(CR6XPKh*Nd)}Sb{7m zV3l8+F2psO|E88FyGGz!@@^wHT1f>!Sv1as!zuzk@ib`nTk=TKh+3ZFNt!~d zqZ`dA(i>jUSzlxS+B9^OY;Cok<3LK$dZgt39u{+H!b! zOl4T2jzF|-D3t_38;_Ms8+V8mnwq>U<|thCT)?mXqUvU4rRaP(=}n!s};~2M-L~Uq&`E7>W6Pkoy@UY2(Z-q^mbP${0nAkhA*g8?b<> zp0ZQmDZ^e+kX?3P(df~P_tV@7pLM_h1meCO=EtXN*P;eaNUX^O>=D)0$nbn$J;S^Bea0k-Zt z8#Jhz8nsS_-ytSW!uOHE42m#2Njq3jzQM~`^7g~OSqQw7xe&Sf``0`1*bmE3qq;(UXRu%M^fXcRP{#x}*|C>J0tD)swLmV=N7P z{M6#vMPJ{k%)X0D8CfvFtYy@ESxw^Kk2>xYzl(}EHz~4+lsr-LJjL@sod`Z6B1|X zIzb#t|DNM5C=}*npZ&Ax3_9R@H*^nd;@NK~|?<{?AT> zrrYHv+MhqF-%{rPDE^Sbp2-YPL?saX z8=%4n8)z)$-i(lN5a2L0YVwHwdT~&OB&)PtE%uQ9fYP#?&X=I2&Hq~GCHOG@3lhEI%148nZer9j$lR^Z}mPIvg|sS`kh5q;~PbQEuYD zjyQBZyg;Y~XnJUSd3SA#K1W^cVkh4Z8ZqP((iYuJLk{L&U97tUSu_O?%x)G;TxQk4 zF|+B6l}lz2@f5hP{STUIg1#!~%9_LP%5MnhbfcTxq4O4-h0|G>dv+W~tYq|I-nR^v z0$H-U9cOa&#^L}?4l^G=g9bbdjVnkf6o_C_gu7!{i4}ebe=j5)A6FFGE{o`l1?XJG zGV@Z2hwo--@U^JxeiW5IxPck7|K&59sLS)e{#9TA>_naZOV**gS$eT;?pQ3!K7de99&iA+?#>*3p2njf+X zvqE5-@9znR@9MP?@B`SW-}*IQc(O2Qx7EePw!%&y(%qSAj2|P47{3(h{3igV%l*&F zaI;oqu}n}J#ssKXj)ALUM2|@Y`+7oI8t}AMiDcNYy|z>I1v8rSu2Q))p?s3iE*V%I z`YBw;`Z)ms(9z<$<5?)M&_6mc%c7^IA)hhSV$Oc@X1sSO?gi?w%(*EzO?f-4tmg_= z3n58$b}@N&tShLzk@D-|h*jzIuA+!w!Hj`buB zF$K;^da$6qalNgOumz&MS^}|E(lrXauz*wWS~P0LK1QUWoZ-;hb3Ob z#_z*%=&|#s5v35{nQHxNc@Bc0HT(bGc?_7aP$WaWM>ocIr>}p~!#Md0P}}Si5hbRM zrkQ2UCt0740&%-DG)Cb&=8lq+^hRk!L?;)>MJH{62yJD}m2Ly<&6xBmK_U8EXG{TV z+--jukJXncn<-~58nK`xSQu137?n{aNqafyj$C~j`aQ~n6vMgMS|fbB<{%yv*PnZ- zkNSf~01hXAS)|t_Hv_1E78Wy$pZB#?F;l!pXJ!U2-$w? zCYwJbBoM44D5Rw=L4#L!Kc`?C9j(c&N2#}%%o_oGwMZ z8{mpRemwB%fUM5FQWda9CnajV>=>@@%!e7E@}B8_J1z^`H7^nBO4roB@`h^;D+Sb6zyTdDK3|rCN~t5CT1F$4xNAry!u6&~gdxaxhC>inl7t!RyX1bV zKLG_qyBgd7Yc@P{HfiG{{Wqh;-f4OCx3ZdDzeDGA5?;ZbE^mHbGzaZ`48;Lk5x3T8 z$L%}-4Tnwzh7vZfr2lm7=cWLqH&rkc}{Fl#h9c)K}?Nv!uqaai4M%G+2O9v+rbQo?zcNvUhQV}4x{ zJg~Hp_BFyXReYCg2siD-YCuPkDbL29wV#QN`@c7b;R11KX~=-bfIDrOs4uEga^f$l&8;M+FEDoQM1RJl$?M4t8qim#@L-TMZ-;qhKPsQ42f3 z&s4Hnq6F3KhbxFpScwxxni-r0_7C^tBv3e#xY+V~Gk*NHf+nK!G@`S4u{(CA>N)ja zYvrlU>CN?nd83=Le8$ZK{5M;x-fO~DwGIi!($Y9H!gq1iyGY2&ijX7i6M?!8YWE~U zF*jDHD49TJ`1^z+BjdqEOK-j4hj*FEs2TYe_En`6>#W1y&J1LbXx#3mj_OKlv8S1C zwUvEK$9XV|G~S1C0|~OUv*Vwsj6dXAu?gZNX$bYZkwye(v1VH1K{=#*LF`-0)994( zJ*l&3!}hp%DxtlGl3~IyZ4(-f{=?j^jcz$3#8Tz_L9l`72~wDGA7D-45~L^?VY#S- zs{Y1EuC27R?fJ%5>eL$dRCHPv#L|U`>2pNFy$jsXZW`ZLePUP1@twuey233koy`$tq!102;@J_Xo)PM?e-q8@LE{+_ zc2Da=V;1657@>pXE4_>-Q*n{{@V-U;JNfXB8C36Rv&S zU4sW$T!RO9m*Bx6XmEE6?i$=7cyJFcL4z*t?yigNp8bDS-{rYH7gHluQ`Ozo-Sc+8 zzvr0(1JV8^-g&&u);IdwM7fOq;=LrhL)^m)0$-azNDzCN@aNC z>@xxiE-DLsKT1*^l~(?ERP(Q5`U)g(FKzi2$QMT?eF#}wz?3MA_&sX@H+=_fJEOcj z=-4)$ks>I)ZXL6b)o?{iH~^(`luae~vX>fHIxTk9+#ppNQxV3f{3X_YdE2F;e_g1&sJV>^=5s4rb#Z0N_#+&dhPhys?&q-CP9lgx_Pn!NEw&(G7218bdE?#Jd;X4X`}uB=vk*7My6 z#cgc`g(-<;2@=9smE@JCr8z%{JE={7q6!*vfl0fo?hs*))RmYVh|4o4$r&t>lq@4` ztO&0E_&rHx_nbrr<)w$uD6DjZKviM(f!0>+W9{`1r>7(!kt5DUM zqF?S97J8B=HnvhL7pPD=5K5I5Rae7*_CbUqUPvXlNZqi|32MasE&8qkX;y=`{q~dQ z)GxdCNm0v1FDba+Ca))-x<#kQGP9|rOQqn0JJba4m98>>c-ejxbca)lq|ELZ1Gysg10yO5h&ahga}Fvd#6u4PnT2xmww#KeyL7Q#nrl(O^|e zHYYJ!c%KWocRn&TAlIA#1z`X+x#-JrRKZRKPOv2S=$eX`-Ag@>m_+RJ1y9`Q#1Gof zu-ZbsKR%!prU7@}p393rav&#I2#vQnfjK^VW3ydgdx?%5)!CQW{a9h#H5N>@!s}{0 zNQ8zp^gz}P2ZPp6@S>?cLN>xo4;f2gS8_5I`A4`INJYRCXff`d$Z#-_2Ez)WcXz_y zjv9tSW^HDz;F0&H*JCHDsJaS|qrPO~9Qeim6Z>O*np6-}XK*|H`N5shR~_uRCrcAW z`E7<|G{%S*z`>R_n(x@?2BiPig2UxI5G_Y`qoA)(O8MtbGLAxs2uh;uJnppj$<_J$ zNbhL|`KS=oCOJP#<{wd`NPg(>5TuY+M;LK|h5(JMQYpXc1?Q;*$g!XjTofgkazp?l zJe*o({~q!Ym8scIKMB#6*uzG)JuUf2QCmqQ?f49;x1=JTSA7}j7!kLL`(Y`$y{g3& zBE2GwcL9dFjwy!wVB49Pjm@Q4><^wEGlXAQ&MQlp?J)t+ z5<7P7^8D0pF+aH#0qst0T(=v2ZM?Nye>w>WIIk|6I`V!BbkIq!%h**^!j7L%Rwzjg zSk3Zc;qI(7*-u=2U8tzDg^wymQ70IDmZVQbZDftqthUs{vSuEk@%{L*@sn@G8VskZ z7tIQ7K($X$SB3Qcp7V`E!fPOI@XB{;H0mTel0yK?$pc2Wpg{zxvu{p=#}a$rq4ayL zEBW#t z10wqgP3xox*3hF*J^eQ`?%Vlk@} zMl=e7OdP3R)Jc=|j&;U*AFOx}CIANxkM*aVi(qJaqlYj+;lGqL@sH%^tV~fqQc6fK zHt&3^&jdsBirW6M7%CLfT^ZG?q2;5-Hw|5E`6r&MBja= zE_=**(WeW3h@q4ucKqhJr0L;20-iff-Y#dcJnu!w`FLM#7z&iFmA(L5_@R=o-Fj3j zBVldvtp{?xz<`>jn2X7AtITqBPf} z5l1V{1lQ6eSsnzINP;P*WxI;mLybtGl|G{EBv^VQ%FNIWIGtpfWdb>m^d<Dzwfn%#6r(%m{Uw|V`$#(1N{ej(3^3Ut*!Td!`e_) zn$xG(p`hZx`&q=NJe&|sF)yTO=?IgzTO|6p0$J;fhui5)JKR|}mP_KI(|XQu@ia>W zycDzDJ3obU{uNLdp%oRkiq*f|20$mCO%}Pnpj;!Js=d<^^${@R+t->O*Yhq5Pu2~7 zN1|akhAs~cVdIkW`bxdhzPn;7higDeYJ6VM>ao&}+V%Bjr=Itr`#h#w`)OwN*G*@t zE5~JSz?6~Vo0JUG4e{QBu8D{8@ps^Fjm2+l#on&=>&t#urUe{~txYY+vr7yNUj&(; z*su%_X&TIYE>_jI4M5JqZSHvFQiNJ36Zh+(ntO{IJ zRl83(f!%~au`&AuZ`RW;W28h3;{t4wXj;2>#NCVr zW^9D*CrPdn+zD$>I98KKj3d@#$|O{A#`CL&fgF`L7^j-RJPSX`+YIEe@7d|EY+083 z0I_JB7`4C$6g&qKK~lldUy-R3hw^_lq_KzO`50#CVreOCMg7%`9rZ)a475^KBh5#m zC#jlUArU5@AG`f8OlHjSFvmo?>d%smqqhF2nLLo+4I-U+KIXZ5u1%&dmA6cu_JEGf zbwq#aTPj==H|v@nZ8OAPJTw^PwZ?Pe`0m{Y_9CaK?p~Q8_-W0=cM*(YJM)$A+!3AV zW~}W`aSD8csjs}${D)WuptNk-| ze`lQ12eBPJp#5?)MWN76#x0YjhpcIkf<%)3_0N|KbNbFZJN8&-4T(&^e09Ez-hB&S zQgG^dXl{Rhq~gBI++a0&Q5x7$#EZT}o8xs7rc)IFE8+D8ZQd6WRG^`nWsatVeF1Pa zVC20{Bu#RjJYq;{RzijV_rDXEQ0MEhOJ)J7qcgKJ?1&)}xUsU|ot!M)-@ob`U~#(6 z+dT4~7gbe7JU;3~V3J8_0s{wjP-bly9v;lmnKhZPnwgJ%8L%Oq@LSKdX^pS3LQB^V zGTbOKFVh*mClB$;-Rzh`PPcT%>DhH3?&%Rs`Tul5QLbSL_uvEPo?L1sD@jBYak&%~DL2FH5GY z{4!*JVo6D$H(a7PAGScrtuX73#Y15_G>RKX!kulwB_o^hJ$ z$G`8zjm21gG%$?$1SurqkBR^xyf8U=9DJJ#J^d%Xqh!n8nC{+rkD-X>-3mh%`4hN4+={9GNf#<)?~zQ8!e`zP_s1+iebQ z-utHWuP@QkC!IGAznt6Mk;t3Z_$DAG_u~e!I;?0%=N=$tZ`xOW?-}hUtjR2k?*1dq zo-xRy;%+o@^@Od6w;Z;WgKKDyL3})gz=N-GLT+3aQ#^Y7M`CkxkOwj1`*jnsekp7& zWZylsEZs93at~)*fGpMK-v;L$_~1M8pw@e`JtFPzSNd5_@O-=1b;5x?GD5lJ{hMJ|` zd>&O?z8fcEhQt9rX8OLSBx3nobO?NU8{n($IsBTUUQ>7%NfQ_gdicRI#_n^g0X~}O zJ!}x$=x3qmHB(J%xwqIv6lCmrmA*Q2AS^eBj5w9w`@E~zE%s#>nTUCuYMEuzXO|IT z7z@doB2Sd5<}vd*ZL&Tbo-onBjSb@>N^)$PRAE*AfsK)-?j7J0w=eOoXIIfyzxELZ zV1_E|Na+x0$@KI53$b0edT1mrV35-gXGczfrTqJHesy|BY|H@5!`~KVnP|dq0kNwP zWX0GJPoV?o4HMs|M3Y(+>-k$sxJL6-ww49D%VX2Z#>)a-w z01;Gr*MB8|nFf1`C}(l~q6&Ds89`s`bxMpC;e)!ID5RjRjKefpP2x$SEdJKh^iF@- z52HL)4dM&gx}wgRTxzgzM?rkw=Mi!Gr-)*PHCDi(zy<9KRu#v8ejrrD#897o82&g^ z$-1K{uKU|N41w{{Q%oV_uW?~@9oWlEDRiUL%k9182@3?%iu>Cdt^y1=u_0wU3$5j2 z9-cS1VRAj`8N3WS^M4ej?=RG(j%j$DeJW*2RP8?xJ%Sdvbn9vvEGe7KWqG{hC!8;d zl_Uboqr&39`T_-+8+0qCw={;{{WL?*#|jTIlon>)^9-kAx_pEe6B|;J0T|}n(I0U> zlsz0v^b%{UCjo9Z<0QhCmCe|^SwC*K(E#y6>sH$g3#DkmA>cawGk@QAt{;ssWyMK0 z!okVB$%It1!#&*}BeT^Y)ZgS&EG|l}sDAE0LoE{>V|!n2%#qnTFb17Lpjf(+nx{1v zSRuM%(3=epv!%a5xXN)UbFv6(OldF$`j@+Z#CqxIsB(|*7pNbxX*xBN54spog{xv{ z6h>p>szx!aBv53M(PemIYigfSs(iX-WvCZC;4PkhCK z{SjUFPpyl0)r;WSDR(+h;5viBi(KOv+;ul4^y^Fo=>D7X=;;-GeT&}RXo-T^w^AZ^ zvh0_AtJswWmEmp=w`^3EJPhoyRYLHRoId}o3cfZ{A`NxYEangU5Hp+k1?|SBqIhTV zosf~I02?3dPN!_PxQiO)w++5h>A2;ez&aZ}aHMrOr&+&`HJ10gCHkS@(Bu1rj{N(@ z)d8L0RH~(&LVM36q}z0l3_@4zFoojdqtau`o<^Om=;@SEXl6}YZgLGYE;vF5+DYqh zkA6U+PK?ZGZ4P3b4j7^ELRkwylRD#Y8N}q5xAo~r%*`LX!o;}`Uv3hUXkU*tJI%xc z4VY;!^6b3RYsgUO!`@Bd2HGkImEu1>>^)!Ebn; z1c)O^jZ;XV;f>Fh2i3o2E^((R6En@naM_}09-+=r)*W8{N#?m+|9gJIT@mwh*6i08jA(CV zoR>mIHgTKG4`G9r2%#+qq1r>cEf#aAq>twoi`JrQ0vZzRgd@_$HFV)jpFNnlo!S=W zKij2NpUUcyms)NW<{vyqQo1;Dv@n;bc7p7vPSK4X{m1$h)iF8mCsifQ*WA>m`ps`4HRHJF3F6NCe1flwm}hja3CV+Pm@|UCFNc^y02lc z$e*H0k1KAc#IlqA?mh&s6~Q83hQA&(%%3Eb(#Mug$slSv_v-|p`Pp>^fQ-eHsxmAQ z^4JRB%y(5kV;h(Gcidkk?Ro>{tdQr4n}p zr#Ot$<*EK4{J+YlC#YXy!#g0qi~JqJF4W0*mOsFt5fy82k669sj40YeXBF4K`!W`eHKn=H+`UCLqa`%l@V+|CP@4TElhi3Hpg7f@ zMusM*Q1EL6fXQ734x}7io1pqnniNRO-O`w#l644o_ZVr5f&q}d+Bq=Hl? zM7{zOAT~DT=}LmH&0ASTA!AVOX6a&fOn3MLXm~6tnk1vEp`JDg)~4^9KcQW@&5hPg zLw`pdc=m{1)%=_w3hB)CT{WbH8b+#u$}cYk8UAJUmsxN15eQ~~Z)|PMSxXeNHQRC1 zodv*rB>_{jn81NN61Oyr%oGY53q1-XJYz2Kw}c<@zfA;A)R4s88!HHAg7Kg}hOXGP z3|@4u-hk*<@03p^vcP2JLhl+6*SYMzJ?+Z6*ll&X$F`ACGaNMU6J;v_!?Z+-N`S*G zV`YNCjaEvGI7wb5psbd=A&{m)IV46rDJ)rr*WyunZS*VH4F6;5X(9Kb3=btk#_7;e z9Zd$*gvboEOj{GsZ;Qbe5iKbv2Op76Iuix9Gkv~2*lk%d7TPa5kFlK(56}XM#GzHi zVfaLWv;=vw)J>k*?}&3J(5LH6Ut{E=%Kk(T@C8oLWLa`8CFn{lhhl#*Zt{cD)@BSghSALd$=z8ZPVcKSV zo+NawkWe){{*m6CCCTOe3Ba+M?ODGUYni%Y6QwV0C&3Mr6f-k$1w z<>1=)y;+vFlm!-Q`|mRZz+tq@%|4~aHI&jdH5jS0jaqX4fM(VfUTO`!yvE>+Q$;ak zK`uZElF_dd#nPKst;Rp9P~}**|8e#GC1{m^w)MjDvPvl9yBw8@oM1n%+w?ETApwAL z5FC}rZ|F2O0=l{?&^C3Vtcz^5`FSfo z;}Xb|_w8bpIO{WhnD}z4=_Cu8jgo~=sgzlBd4#zen{xa0tH0-^a5}q@;>+L24`esM zpNo2TieC69UR&$3Uw=qXRqigRv}GDh$evM<5$pfxzQH!oD{ZydZq1)=4wz=&^b0yG zsuU^DXT9_*@*Bh7d1$zA;g5K$1z&8{p0-g+KQMDuq3j!FhvYiS>_SPn`CMx2{Y-yp zwzD=KO`{VjAVLgArNw|PDCyF5ol~24faHK%$^9s|sq}|n2hn#2bO$yzEyO_tK$aQp zNXYlwt-Bfo$d$;Az@S_lJ#^z8U)(}MGB^pR4C~mvmACk100p--iYn9jpFK_p_NGyM zn?GPBy)CIaLXVP4Q;Oib_@7d|2FE;0dE@5=j_BPB7uv`tp&1sPeS@+D^1~?f$d$guM<5!HJ`(y;8zy z8sosEXj~RS(#NV?PHWk=OaFC(d~Yb$>a0V#8RoLn47US6(4H5cEz5e%N9k+4ER+FMasv>L!<)n@pRxC~y1Ol_t{UW{z??iLbGuN^%g4#XMj< zAd0AvtPU6@p--MEsutf1tXJ&#K0prF|IsLmph!Fu{?!YPW-EE-#s2%IoKIn#4WEpM z9-&ySFEc*XY8k7*l~N2&JV=UKW6bjn4AY-pTt@Poi~B4NU)2kz0`*&3*tt1BTECmp z0g;(Q=dof>lp$=Q9b)|6nhAb7E+X^gmIR=xP(0M`wf=0UfRnXC@CBa$xu@9SqPd(m z8;>NS*TcdL?_TjO^-ug!PZZU57O}}L^k6;)JLI7*I&JfG$gKm%47Wc3vP| zClU%ZY}hm;Tn40qnS!m^>9qoTveF;fIGaLFxx`Z7^ag*iWJz%qIa;v!JzbQu)KU=9 zjE*AVQmgNv(`0eAi9%Rgez*$uc%xA&93ILA%CV*v3*t~0zkDLh@=ivr9VNvXx~d!c z*(luLz|x;U9u#;*Xdy#)TrHge#319tD&PoXtwtHlilRSaP-MPBYxYxGSsT-WMBA-W zJQ;vN3yUEgZumM#CoJgfu%?f{^#WF5BF!--LaCKC>z7889{|fQZ+lux%G-HA$5AV- zWYeC%k3Lz|ne47Je5(V?dBg`DUK*Jy5XD(3GLza zb!i%v0^V4Bet$-*xBg8&OScqzk6I?J^cZr!JidFpzxwE*d44LMs}i(@z^e z0_k;T=dz27g~jc3F%m~YnNW+LgCngz#E}u1jr6EKo;yjSL?9oMI<{< zXw!NitWsW52Kdc_8}DETXGJU5bW)m&smX+6ro*H)W{iJ5a6?R*e0u}a&!=@^uw3?N zlrOObx+MaGiT~d}0tSg06r1VVZf)@6sz=R6(_?^X;|+&37g%KC#PpJk?xnoLR9gbK(nc+9gjuf)t@Vs! zP|E&%zyD_b=Ff_BzG-biv4=iJfxE1<{@hb6JI`k;gr}FfxDB-?O3~Chob0?M`c-!;; zS-<;qJm@t-TBQ}(`XhhBJe};7Z5#bbe|qd!z|HDyF1zuwNMLIkHz?7I&vo2QJD}w2 z%eU4wfMw@MVgMV-q!hj^W{IHmeC0jG#6KMXxDh$H)93QI4ik!<=%NYOOPl=K+G}mI z9!sI?dOpmN%6GAq1fqC%0UDY3uASMu)UF|!`8&(_Rt@01Hq;0y!>3I&nuCze zi{;czc^X&`T2n(kIy`!c8b1^S=GkPOe*n?tW& z_eTbs9TrJ8*7O4tKcv_2{KT|>z zzP~SEGuS~#5!%~H>52@6FV!}x^}s$gDi z<9PXtrRY-?R~$V;ngmr=R049A`xM}V&L@@7*5e(slD(jA(So!NL8|UXMa3X*AnV}r z=5T1Q-)eHp=LQ_{39^)P(%0GOZSRsuI{NS74dGa$ldQXfkLO* zIpx5t{TM%e^16Ct>GMJ{SV_4cB2)Lc{SHh~u&sQuyu8GeL3K=|J`wu3HIcZ>-ytXszVlW?Pyk z)d5MstaJT$-w+lOSXCX#?bW~VKGzpxDMom4F$vHl*<5Lle5Ld*p1wPp%4XI1y(tO^ z@pt~~2DiNr>@n>aqkE9T)cRvMgIMFB^U^0iWa6I?pb|6`)W3d`dq4I`$_j_?1{*QE zEPA(Bnr8OOL;?{`;N({->mDPi2Rpf-_|A=8UH4+>Uf5p$Hr@tpk(;Od{0_C6DTy_rHFtdTi-p>wmDStCC zlLbW3uP2Q-az#^Vz z|61Qxr95e$s+jHG^@K$Jv@V{2b&e);7*=w7z4c-?Ta`XR9oJ8Wnx;=43TyVTo3gnH z+#z*&0j=hKV|;cp4ONZ)$+G#}x`%ZAiC0(MB%vebGVJjwo;;AJBwp`L@rtGQR=}uj zBty*mj8ov=a(=J#PSR9?;H&b;2P&6)=*kqqV5#@Qn?FZa#83Xoq{%A&ep2S{Z-AFE z8&V@Y(j3I0W#kY{(ZB)&IkHeQNW zQECxJY4M#7&hL7@g2X!4#z}5uBZwbYs?^vQ-;$f*CC%U7S7F_V4cSEAs?VfNULY|G zYo!*0Fy{Mt8 zXnDYC`R^7_K8Gn?wK1X$uGq{Y{;9SQj?FjytPWx8_et8R)#WQg|4%Rmtp;YGyCZ|M zG5fD(;iVJReot*IRls*EE5D_92P_jE^@C*&EhX$0T|{q>j5@w|E^GI-mSifg&D>t2niq|u|=K9+nzFrW~*W3rs`d# zptdaJ-v$?z%2o2v!~u9NSlxfma0xivEiOGZf8tq9|;65buS7&rnM|qp(|0j z9ZnRuhAhad>il*IG&|@_*fSA1(i-u6E@xG^Z&B0Fy=hZ>n{|0RyJLGe^g8plZjmuT*ntWBD?bf-+ujoN1*0u?h|DE zpnEH*`5|EmyrY}&TBir>j&TU`6t4t};Rt^FK4NXsZgWYJ@cQ%g4L~3jBDe#+HvO{m&h`_|&2%2GUaseyz-lTgd!|rcm3(lMnh>10f2M8H|H-_!b6MBig#&`yqe1HQmC zH|rl*xr)9v)l5_}Ebf;o337ytPq1;Q4caPQC6wp6HMEre>NL&cR&v{yudaOv7c(HY zpF}%@rQ6sQFnZ0U^C2urJoec3*PKd$ne)rX&9?AKVDCI9z5Mg=ZNx{{xcBGl6@)_x zLZkp^UQ_YhR@*FCqn2rUkiEu|>ae2$J#I@cm@4_}in(zRg%!PcNTQ=9>01(glUy>f`0N z;aTpXpV1Gh){-=T3ye1g>Q}2I0+t@j79}dhBhfv@JvBDp9f=n~cX{55PeNKBbAWXw zPP`eQr?VPSb+S7{3*UUdb@!$NDY1i}nt$T+{0nz3o;J^(kkcRCTV!;k0y|kv56+vt z^p6&_1@8KUI)8V)RBEz`xpmzI4_2GI1+JZG#>~BUSS&@}LRX5>} z#A^|pN7W~KyLS^=6!vq+O8mg>DY3PJhvuI!4w7f6gCbQxt$+_`SoFllPrZ=t1B~_o z+lOYqC&ysPX)`~_fB5nfLjC6;%_hR*hQX0|6{4s2{-Cdy<!43iRRE{$eSh@&+!@!6&f9b88~dNr z)4;mPAp>l(hLu0v7Yj^j2z*GpZ$a^>>QzJ!r4SSUgGq9E`VP=I$L`bX^He#P^BV8& z)hD8D=(vFY_SJ!qCYiZH=Dj}#$plC%V#PQV#MvRf1%7O!ds69e-Wdo-CuTQn|G&Nn zDsaw0YX?OR7t6$k38ArzF5+*S*1H|4HrUM4?6k_e-X! ver; + if (!(tag instanceof ListTag)) ver = null; + else ver = ((ListTag) tag).getValue(); + StringBuilder sb = new StringBuilder(); + if (ver != null) for (Tag t : ver) { + int iver; + if (!(t instanceof IntTag)) iver = -1; + else iver = ((IntTag) t).getValue(); + sb.append(iver).append('.'); + } + bundle.putString(KEY_LAST_VERSION_SHORT, sb.toString()); + } + { + Tag tag = level.getChildTagByKey(KEY_MINIMUM_VERSION); + ArrayList ver; + if (!(tag instanceof ListTag)) ver = null; + else ver = ((ListTag) tag).getValue(); + StringBuilder sb = new StringBuilder(); + if (ver != null) for (Tag t : ver) { + int iver; + if (!(t instanceof IntTag)) iver = -1; + else iver = ((IntTag) t).getValue(); + sb.append(iver).append('.'); + } + bundle.putString(KEY_MINIMUM_VERSION_SHORT, sb.toString()); + } + Iterator iterator = null; + try { + SparseIntArray versions = new SparseIntArray(); + //SparseIntArray dimensions = new SparseIntArray(); + iterator = getWorldData().db.iterator(); + int count = 0; + for (iterator.seekToFirst(); iterator.isValid() && count < 800; iterator.next(), count++) { + byte[] key = iterator.getKey(); + if ((key.length == 9 && key[8] == 0x76) || (key.length == 13 && key[12] == 0x76)) { + //if (key.length == 9) dimensions.put(0, dimensions.get(0) + 1); + //else { + // int dimension = (key[8] & 0xff) | ((key[9] & 0xff) << 8) | ((key[10] & 0xff) << 16) | (key[11] << 24); + // dimensions.put(dimension, dimensions.get(dimension) + 1); + //} + byte[] val = iterator.getValue(); + versions.put(val[0], versions.get(val[0]) + 1); + } + } + iterator.close(); + StringBuilder sb = new StringBuilder(); + for (int i = 0, lim = Math.min(versions.size(), 8); i < lim; i++) { + sb.append(versions.keyAt(i)).append(':').append(versions.valueAt(i)).append(','); + } + bundle.putString("chunks", sb.toString()); + } catch (Exception e) { + e.printStackTrace(); + if (iterator != null) { + iterator.close(); + } + } + return bundle; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + public void writeLevel(CompoundTag level) throws IOException { LevelDataConverter.write(level, this.levelFile); this.level = level; diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java index 834839d2..ef684da2 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java @@ -160,15 +160,6 @@ Send anonymous world data to the Firebase (Google analytics for Android) server. String worldSeed = String.valueOf(this.world.getWorldSeed()); subtitle.setText(worldSeed); - Bundle bundle = new Bundle(); - bundle.putString("seed", worldSeed); - bundle.putString("name", this.world.getWorldDisplayName()); - - - // anonymous global counter of opened worlds - Log.logFirebaseEvent(this, Log.CustomFirebaseEvent.WORLD_OPEN, bundle); - - // Open the world-map as default content openWorldMap(); @@ -176,11 +167,20 @@ Send anonymous world data to the Firebase (Google analytics for Android) server. try { //try to load world-data (Opens chunk-database for later usage) this.world.getWorldData().load(); - } catch (WorldData.WorldDataLoadException e) { + world.getWorldData().openDB(); + } catch (Exception e) { finish(); e.printStackTrace(); } + Bundle bundle = new Bundle(); + bundle.putString("seed", worldSeed); + bundle.putString("name", this.world.getWorldDisplayName()); + bundle.putAll(world.getMapVersionData()); + + // anonymous global counter of opened worlds + Log.logFirebaseEvent(this, Log.CustomFirebaseEvent.WORLD_OPEN, bundle); + Log.d("World activity created"); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java index 2be46212..1866dda8 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java @@ -117,8 +117,8 @@ public void openDB() throws WorldDBException { //close db to make it available for other apps (Minecraft itself!) public void closeDB() throws WorldDBException { if (this.db == null) - throw new WorldDBException("DB is null!!! (db is not loaded probably)"); - + return; + //Why bother throw an exception, isn't it good enough being able to skip closing as it's null? try { this.db.close(); } catch (Exception e) { @@ -127,18 +127,6 @@ public void closeDB() throws WorldDBException { } } - /** - * WARNING: DELETES WORLD !!! - */ - public void destroy() throws WorldDBException { - if (this.db == null) - throw new WorldDBException("DB is null!!! (db is not loaded probably)"); - - this.db.close(); - this.db.destroy(); - this.db = null; - } - public byte[] getChunkData(int x, int z, ChunkTag type, Dimension dimension, byte subChunk, boolean asSubChunk) throws WorldDBException, WorldDBLoadException { //ensure that the db is opened diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java index 2f250962..97b77350 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java @@ -13,113 +13,129 @@ import java.util.Map; /* -Entity enum for MCPE -- by @mithrilmania +Entity enum for MCPE __ by @mithrilmania + +___ Please attribute @mithrilmania for generating+updating this enum + + Format: + + ENTITY( + numeric_id, deprecated! + Display Name, + DataName, not used? + wiki_name, as identifier, originally dumped as wiki-name cases. + icon + ) + + icon is from assets/entity_wiki.png which was download from + https://d1u5p3l4wpay3k.cloudfront.net/minecraft_gamepedia/4/40/EntityCSS.png ---- Please attribute @mithrilmania for generating+updating this enum */ public enum Entity implements NamedBitmapProviderHandle, NamedBitmapProvider { - CHICKEN(10, "Chicken", new String[]{ "Chicken" }, "chicken", 4), - COW(11, "Cow", new String[]{ "Cow" }, "cow", 3), - PIG(12, "Pig", new String[]{ "Pig" }, "pig", 1), - SHEEP(13, "Sheep", new String[]{ "Sheep" }, "sheep", 2), - WOLF(14, "Wolf", new String[]{ "Wolf" }, "wolf", 18), - VILLAGER(15, "Villager", new String[]{ "Villager" }, "villager", 7), - MUSHROOM_COW(16, "Mooshroom", new String[]{ "MushroomCow" }, "mooshroom", 6), - SQUID(17, "Squid", new String[]{ "Squid" }, "squid", 5), - RABBIT(18, "Rabbit", new String[]{ "Rabbit" }, "rabbit", 89), - BAT(19, "Bat", new String[]{ "Bat" }, "bat", 64), - VILLAGER_GOLEM(20, "Iron Golem", new String[]{ "VillagerGolem", "IronGolem" }, "iron-golem", 48), - SNOW_MAN(21, "Snow Golem", new String[]{ "SnowMan", "SnowGolem" }, "snow-golem", 31), - OZELOT(22, "Ocelot", new String[]{ "Ozelot", "Ocelot" }, "ozelot", 37), - HORSE(23, "Donkey", new String[]{ "EntityHorse", "Horse" }, "horse", 73), - HORSE_DONKEY(24, "Donkey", new String[]{ "EntityHorse", "Donkey" }, "donkey", 74), - HORSE_MULE(25, "Mule", new String[]{ "EntityHorse", "Mule" }, "mule", 75), - HORSE_SKELETON(26, "Skeleten Horse", new String[]{ "EntityHorse", "SkeletonHorse" }, "skeleton-horse", 76), - HORSE_ZOMBIE(27, "Zombie Horse", new String[]{ "EntityHorse", "ZombieHorse" }, "zombie-horse", 77), + CHICKEN(10, "Chicken", new String[]{"Chicken"}, "chicken", 4), + COW(11, "Cow", new String[]{"Cow"}, "cow", 3), + PIG(12, "Pig", new String[]{"Pig"}, "pig", 1), + SHEEP(13, "Sheep", new String[]{"Sheep"}, "sheep", 2), + WOLF(14, "Wolf", new String[]{"Wolf"}, "wolf", 18), + VILLAGER(15, "Villager", new String[]{"Villager"}, "villager", 7), + MUSHROOM_COW(16, "Mooshroom", new String[]{"MushroomCow"}, "mooshroom", 6), + SQUID(17, "Squid", new String[]{"Squid"}, "squid", 5), + RABBIT(18, "Rabbit", new String[]{"Rabbit"}, "rabbit", 89), + BAT(19, "Bat", new String[]{"Bat"}, "bat", 64), + VILLAGER_GOLEM(20, "Iron Golem", new String[]{"VillagerGolem", "IronGolem"}, "iron_golem", 48), + SNOW_MAN(21, "Snow Golem", new String[]{"SnowMan", "SnowGolem"}, "snow_golem", 31), + OZELOT(22, "Ocelot", new String[]{"Ozelot", "Ocelot"}, "ozelot", 37), + HORSE(23, "Donkey", new String[]{"EntityHorse", "Horse"}, "horse", 73), + HORSE_DONKEY(24, "Donkey", new String[]{"EntityHorse", "Donkey"}, "donkey", 74), + HORSE_MULE(25, "Mule", new String[]{"EntityHorse", "Mule"}, "mule", 75), + HORSE_SKELETON(26, "Skeleten Horse", new String[]{"EntityHorse", "SkeletonHorse"}, "skeleton_horse", 76), + HORSE_ZOMBIE(27, "Zombie Horse", new String[]{"EntityHorse", "ZombieHorse"}, "zombie_horse", 77), //28 //29 - ARMOR_STAND(30, "Armor Stand", new String[]{ "ArmorStand" }, "TODO", 106),//30 ; ArmorStand is not yet in the game + ARMOR_STAND(30, "Armor Stand", new String[]{"ArmorStand"}, "TODO", 106),//30 ; ArmorStand is not yet in the game //31 - ZOMBIE(32, "Zombie", new String[]{ "Zombie" }, "zombie", 9), - CREEPER(33, "Creeper", new String[]{ "Creeper" }, "creeper", 14), - SKELETON(34, "Skeleton", new String[]{ "Skeleton" }, "skeleton", 10), - SPIDER(35, "Spider", new String[]{ "Spider" }, "spider", 11), - PIG_ZOMBIE(36, "Zombie Pigman", new String[]{ "PigZombie" }, "zombie-pigman", 17), - SLIME(37, "Slime", new String[]{ "Slime" }, "slime", 15), - ENDERMAN(38, "Enderman", new String[]{ "Enderman", "EnderMan" }, "enderman", 21), - SILVERFISH(39, "Silverfish", new String[]{ "Silverfish" }, "silverfish", 22), - CAVE_SPIDER(40, "Cave Spider", new String[]{ "CaveSpider" }, "cave-spider", 13), - GHAST(41, "Ghast", new String[]{ "Ghast" }, "ghast", 16), - LAVA_SLIME(42, "Magma Cube", new String[]{ "LavaSlime" }, "magma-cube", 24), - BLAZE(43, "Blaze", new String[]{ "Blaze" }, "blaze", 32), - ZOMBIE_VILLAGER(44, "Zombie Villager", new String[]{ "ZombieVillager" }, "zombie-villager", 62), - WITCH(45, "Witch", new String[]{ "Witch" }, "witch", 54), - SKELETON_STRAY(46, "Stray", new String[]{ "Skeleton", "StraySkeleton" }, "stray", 98), - ZOMBIE_HUSK(47, "Husk", new String[]{ "Zombie", "HuskZombie" }, "husk", 99), - SKELETON_WITHER(48, "Wither Skeleton", new String[]{ "Skeleton", "WitherSkeleton" }, "wither", 61), - GUARDIAN(49, "Guardian", new String[]{ "Guardian" }, "guardian", 87), - ELDER_GAURDIAN(50, "Elder Gaurdian", new String[]{ "ElderGaurdian" }, "elder-gaurdian", 88), - NPC(51, "NPC", new String[]{ "Npc" }, "npc", 100), - WITHER_BOSS(52, "Wither Boss", new String[]{ "WitherBoss" }, "blue-wither-skull", 72), - ENDER_DRAGON(53, "Ender Dragon", new String[]{ "EnderDragon" }, "ender-dragon", 29), - SHULKER(54, "Shulker", new String[]{ "Shulker" }, "shulker", 30), - ENDERMITE(55, "Endermite", new String[]{ "Endermite" }, "endermite", 86), - LEARN_TO_CODE_MASCOT(56, "Learn To Code Mascot", new String[]{ "LearnToCodeMascot" }, "learn-to-code-mascot", 108), - GIANT(57, "Giant Zombie", new String[]{ "Giant" }, "giant", 9),//53 ; Giant is not yet in the game + ZOMBIE(32, "Zombie", new String[]{"Zombie"}, "zombie", 9), + CREEPER(33, "Creeper", new String[]{"Creeper"}, "creeper", 14), + SKELETON(34, "Skeleton", new String[]{"Skeleton"}, "skeleton", 10), + SPIDER(35, "Spider", new String[]{"Spider"}, "spider", 11), + PIG_ZOMBIE(36, "Zombie Pigman", new String[]{"PigZombie"}, "zombie_pigman", 17), + SLIME(37, "Slime", new String[]{"Slime"}, "slime", 15), + ENDERMAN(38, "Enderman", new String[]{"Enderman", "EnderMan"}, "enderman", 21), + SILVERFISH(39, "Silverfish", new String[]{"Silverfish"}, "silverfish", 22), + CAVE_SPIDER(40, "Cave Spider", new String[]{"CaveSpider"}, "cave_spider", 13), + GHAST(41, "Ghast", new String[]{"Ghast"}, "ghast", 16), + LAVA_SLIME(42, "Magma Cube", new String[]{"LavaSlime"}, "magma_cube", 24), + BLAZE(43, "Blaze", new String[]{"Blaze"}, "blaze", 32), + ZOMBIE_VILLAGER(44, "Zombie Villager", new String[]{"ZombieVillager"}, "zombie_villager", 62), + WITCH(45, "Witch", new String[]{"Witch"}, "witch", 54), + SKELETON_STRAY(46, "Stray", new String[]{"Skeleton", "StraySkeleton"}, "stray", 98), + ZOMBIE_HUSK(47, "Husk", new String[]{"Zombie", "HuskZombie"}, "husk", 99), + SKELETON_WITHER(48, "Wither Skeleton", new String[]{"Skeleton", "WitherSkeleton"}, "wither", 61), + GUARDIAN(49, "Guardian", new String[]{"Guardian"}, "guardian", 87), + ELDER_GAURDIAN(50, "Elder Gaurdian", new String[]{"ElderGaurdian"}, "elder_gaurdian", 88), + NPC(51, "NPC", new String[]{"Npc"}, "npc", 100), + WITHER_BOSS(52, "Wither Boss", new String[]{"WitherBoss"}, "blue_wither_skull", 72), + ENDER_DRAGON(53, "Ender Dragon", new String[]{"EnderDragon"}, "ender_dragon", 29), + SHULKER(54, "Shulker", new String[]{"Shulker"}, "shulker", 30), + ENDERMITE(55, "Endermite", new String[]{"Endermite"}, "endermite", 86), + LEARN_TO_CODE_MASCOT(56, "Learn To Code Mascot", new String[]{"LearnToCodeMascot"}, "learn_to_code_mascot", 108), + GIANT(57, "Giant Zombie", new String[]{"Giant"}, "giant", 9),//53 ; Giant is not yet in the game //58 //59 //60 //61 //61 - CAMERA(62, "Tripod Camera", new String[]{ "TripodCamera" }, "camera", 144), - PLAYER(63, "Player", new String[]{ "Player" }, "player", 8), - ITEM(64, "Dropped Item", new String[]{ "ItemEntity" }, "item", -1),//do not render items - PRIMED_TNT(65, "Primed TNT", new String[]{ "PrimedTnt" }, "primed-tnt", 49), - FALLING_SAND(66, "Falling Block", new String[]{ "FallingBlock" }, "falling-sand", 50), - ITEM_FRAME(67, "Item Frame", new String[]{ "ItemFrame" }, "empty-item-frame", 66),//67 ; ItemFrame is not yet in the game - THROWN_EXP_BOTTLE(68, "Bottle o' Enchanting", new String[]{ "ThrownExpBottle", "ExperiencePotion" }, "ThrownExpBottle", 56), - XP_ORB(69, "Experience Orb", new String[]{ "XPOrb", "ExperienceOrb" }, "experience-orb", 59), - EYE_OF_ENDER_SIGNAL(70, "Eye of Ender", new String[]{ "EyeOfEnderSignal" }, "eye-of-ender", 47), - ENDER_CRYSTAL(71, "Ender Crystal", new String[]{ "EnderCrystal" }, "ender-crystal", 52), + CAMERA(62, "Tripod Camera", new String[]{"TripodCamera"}, "camera", 144), + PLAYER(63, "Player", new String[]{"Player"}, "player", 8), + ITEM(64, "Dropped Item", new String[]{"ItemEntity"}, "item", -1),//do not render items + PRIMED_TNT(65, "Primed TNT", new String[]{"PrimedTnt"}, "primed_tnt", 49), + FALLING_SAND(66, "Falling Block", new String[]{"FallingBlock"}, "falling_sand", 50), + ITEM_FRAME(67, "Item Frame", new String[]{"ItemFrame"}, "empty_item_frame", 66),//67 ; ItemFrame is not yet in the game + THROWN_EXP_BOTTLE(68, "Bottle o' Enchanting", new String[]{"ThrownExpBottle", "ExperiencePotion"}, "ThrownExpBottle", 56), + XP_ORB(69, "Experience Orb", new String[]{"XPOrb", "ExperienceOrb"}, "experience_orb", 59), + EYE_OF_ENDER_SIGNAL(70, "Eye of Ender", new String[]{"EyeOfEnderSignal"}, "eye_of_ender", 47), + ENDER_CRYSTAL(71, "Ender Crystal", new String[]{"EnderCrystal"}, "ender_crystal", 52), //72 //73 - TURTLE(74, "Turtle", new String[]{ "Turtle" }, "turtle", 79), + TURTLE(74, "Turtle", new String[]{"Turtle"}, "turtle", 79), //75 - SHULKER_BULLET(76, "Shulker Bullet", new String[]{ "ShulkerBullet" }, "shulker-bullet", 79), - FISHING_HOOK(77, "Fishing Hook", new String[]{ "FishingHook" }, "fishing-hook", 57), - CHALKBOARD(78, "Chalkboard", new String[]{ "Chalkboard" }, "chalkboard", 144), - DRAGON_FIREBALL(79, "Dragon Fireball", new String[]{ "DragonFireball" }, "dragon-fireball", 80), - ARROW(80, "Arrow", new String[]{ "Arrow" }, "arrow", 41), - SNOWBALL(81, "Snowball", new String[]{ "Snowball" }, "snowball", 42), - THROWN_EGG(82, "Thrown Egg", new String[]{ "ThrownEgg" }, "thrown-egg", 43), - PAINTING(83, "Painting", new String[]{ "Painting" }, "painting", 65), - MINECART_RIDEABLE(84, "Minecart", new String[]{ "MinecartRideable", "Minecart" }, "minecart", 34), - LARGE_FIREBALL(85, "Ghast Fireball", new String[]{ "Fireball", "LargeFireball" }, "fireball", 44), - THROWN_POTION(86, "Splash Potion", new String[]{ "ThrownPotion" }, "ThrownPotion", 95), - THROWN_ENDERPEARL(87, "Ender Pearl", new String[]{ "ThrownEnderpearl" }, "ender-pearl", 46), - LEASH_KNOT(88, "Lead Knot", new String[]{ "LeashKnot", "LeashFenceKnotEntity" }, "lead-knot", 94), - WITHER_SKULL(89, "Wither Skull", new String[]{ "WitherSkull" }, "wither-skull", 60), - BOAT(90, "Boat", new String[]{ "Boat" }, "boat", 33), + SHULKER_BULLET(76, "Shulker Bullet", new String[]{"ShulkerBullet"}, "shulker_bullet", 79), + FISHING_HOOK(77, "Fishing Hook", new String[]{"FishingHook"}, "fishing_hook", 57), + CHALKBOARD(78, "Chalkboard", new String[]{"Chalkboard"}, "chalkboard", 144), + DRAGON_FIREBALL(79, "Dragon Fireball", new String[]{"DragonFireball"}, "dragon_fireball", 80), + ARROW(80, "Arrow", new String[]{"Arrow"}, "arrow", 41), + SNOWBALL(81, "Snowball", new String[]{"Snowball"}, "snowball", 42), + THROWN_EGG(82, "Thrown Egg", new String[]{"ThrownEgg"}, "thrown_egg", 43), + PAINTING(83, "Painting", new String[]{"Painting"}, "painting", 65), + MINECART_RIDEABLE(84, "Minecart", new String[]{"MinecartRideable", "Minecart"}, "minecart", 34), + LARGE_FIREBALL(85, "Ghast Fireball", new String[]{"Fireball", "LargeFireball"}, "fireball", 44), + THROWN_POTION(86, "Splash Potion", new String[]{"ThrownPotion"}, "ThrownPotion", 95), + THROWN_ENDERPEARL(87, "Ender Pearl", new String[]{"ThrownEnderpearl"}, "ender_pearl", 46), + LEASH_KNOT(88, "Lead Knot", new String[]{"LeashKnot", "LeashFenceKnotEntity"}, "lead_knot", 94), + WITHER_SKULL(89, "Wither Skull", new String[]{"WitherSkull"}, "wither_skull", 60), + BOAT(90, "Boat", new String[]{"Boat"}, "boat", 33), //91 //92 - LIGHTNING(93, "Lightning Bolt", new String[]{ "LightningBolt" }, "lightning", 58), - SMALL_FIREBALL(94, "Blaze Fireball", new String[]{ "SmallFireball" }, "fireball", 44), - AREA_EFFECT_CLOUD(95, "Area effect cloud", new String[]{ "AreaEffectCloud" }, "area-effect-cloud", 144), - MINECART_HOPPER(96, "Minecart with Hopper", new String[]{ "MinecartHopper" }, "minecart-with-hopper", 70), - MINECART_TNT(97, "Minecart with TNT", new String[]{ "MinecartTNT" }, "minecart-with-tnt", 69), - MINECART_CHEST(98, "Storage Minecart", new String[]{ "MinecartChest" }, "minecart-chest", 35), - LINGERING_POTION(101, "Lingering potion", new String[]{ "LingeringPotion" }, "lingering-potion", 144), - - CAT(122, "Cat", new String[]{ "Cat" }, "cat", 144),//95 ; FireworksRocketEntity is not in the game yet - PANDA(123, "Panda", new String[]{ "Panda" }, "panda", 144),//95 ; FireworksRocketEntity is not in the game yet - - //id 900+ is ignored for functions like map-filtering, these are placeholders for when the game adds more expected features. - MINECART_SPAWNER(900, "Minecart with Spawner", new String[]{ "MinecartSpawner" }, "minecart-with-spawner", 71),//99 ; MinecartSpawner is not yet in the game - MINECART_COMMAND_BLOCK(901, "Minecart with Command Block", new String[]{ "MinecartCommandBlock" }, "minecart-with-command-block", 78),//100 ; MinecartCommandBlock is not yet in the game - MINECART_FURNACE(902, "Powered Minecart", new String[]{ "MinecartFurnace" }, "minecart-furnace", 36),//101 ; MinecartFurnace is not yet in the game - FIREWORKS_ROCKET_ENTITY(903, "Firework Rocket", new String[]{ "FireworksRocketEntity" }, "fireworks-rocket", 144),//95 ; FireworksRocketEntity is not in the game yet - UNKNOWN(999, "Unknown", new String[]{ "Unknown" }, "unknown", 144); + LIGHTNING(93, "Lightning Bolt", new String[]{"LightningBolt"}, "lightning", 58), + SMALL_FIREBALL(94, "Blaze Fireball", new String[]{"SmallFireball"}, "fireball", 44), + AREA_EFFECT_CLOUD(95, "Area effect cloud", new String[]{"AreaEffectCloud"}, "area_effect_cloud", 144), + MINECART_HOPPER(96, "Minecart with Hopper", new String[]{"MinecartHopper"}, "minecart_with_hopper", 70), + MINECART_TNT(97, "Minecart with TNT", new String[]{"MinecartTNT"}, "minecart_with_tnt", 69), + MINECART_CHEST(98, "Storage Minecart", new String[]{"MinecartChest"}, "minecart_chest", 35), + LINGERING_POTION(101, "Lingering potion", new String[]{"LingeringPotion"}, "lingering_potion", 144), + + DROWNED(110, "Drowned", new String[]{"Drowned"}, "drowned", 133), + + CAT(122, "Cat", new String[]{"Cat"}, "cat", 142),//95 ; FireworksRocketEntity is not in the game yet + PANDA(123, "Panda", new String[]{"Panda"}, "panda", 136),//95 ; FireworksRocketEntity is not in the game yet + + //id 900+ is ignored for functions like map_filtering, these are placeholders for when the game adds more expected features. + MINECART_SPAWNER(900, "Minecart with Spawner", new String[]{"MinecartSpawner"}, "minecart_with_spawner", 71),//99 ; MinecartSpawner is not yet in the game + MINECART_COMMAND_BLOCK(901, "Minecart with Command Block", new String[]{"MinecartCommandBlock"}, "minecart_with_command_block", 78),//100 ; MinecartCommandBlock is not yet in the game + MINECART_FURNACE(902, "Powered Minecart", new String[]{"MinecartFurnace"}, "minecart_furnace", 36),//101 ; MinecartFurnace is not yet in the game + FIREWORKS_ROCKET_ENTITY(903, "Firework Rocket", new String[]{"FireworksRocketEntity"}, "fireworks_rocket", 121),//95 ; FireworksRocketEntity is not in the game yet + UNKNOWN(999, "Unknown", new String[]{"Unknown"}, "unknown", 144); public final int id, sheetPos; public final String displayName, wikiName; @@ -128,7 +144,7 @@ public enum Entity implements NamedBitmapProviderHandle, NamedBitmapProvider { public Bitmap bitmap; - Entity(int id, String displayName, String[] dataNames, String wikiName, int sheetPos){ + Entity(int id, String displayName, String[] dataNames, String wikiName, int sheetPos) { this.id = id; this.displayName = displayName; this.dataNames = dataNames; @@ -138,19 +154,19 @@ public enum Entity implements NamedBitmapProviderHandle, NamedBitmapProvider { } @Override - public Bitmap getBitmap(){ + public Bitmap getBitmap() { return this.bitmap; } @NonNull @Override - public NamedBitmapProvider getNamedBitmapProvider(){ + public NamedBitmapProvider getNamedBitmapProvider() { return this; } @NonNull @Override - public String getBitmapDisplayName(){ + public String getBitmapDisplayName() { return this.displayName; } @@ -162,52 +178,44 @@ public String getBitmapDataName() { return this.dataNames[0]; } - private static final Map entityMap; - private static final Map entityByID; + //private static final Map entityMap; + //private static final Map entityByID; static { - entityMap = new HashMap<>(); - entityByID = new HashMap<>(); - for(Entity e : Entity.values()){ - for(String dataName : e.dataNames){ - entityMap.put(dataName, e); - } - entityByID.put(e.id, e); - } + //entityMap = new HashMap<>(); + //entityByID = new HashMap<>(); + //for (Entity e : Entity.values()) { + //for(String dataName : e.dataNames){ + // entityMap.put(e.wikiName.replace("_", "_"), e); + //} + // entityByID.put(e.id, e); + //} } + //We don't need a cache for this! - public static Entity getEntity(String dataName){ - try { - return entityMap.get(dataName); - } - catch (Exception e) - { - android.util.Log.d("myTag","error finding id for: " + dataName); - return entityMap.get(dataName); + public static Entity getEntity(@NonNull String dataName) { + for (Entity e : Entity.values()) { + if (dataName.equals(e.wikiName)) return e; } + return Entity.UNKNOWN; } - public static Entity getEntity(int id){ - try { - return entityByID.get(id); - } - catch (Exception e ) - { - android.util.Log.d("myTag","error finding id for: " + id); - return entityByID.get(id); + public static Entity getEntity(int id) { + for (Entity e : Entity.values()) { + if (id == e.id) return e; } + return Entity.UNKNOWN; } - public static void loadEntityBitmaps(AssetManager assetManager) throws IOException { Bitmap sheet = BitmapFactory.decodeStream(assetManager.open("entity_wiki.png")); int w = sheet.getWidth(); int tileSize = 16; - for(Entity e : Entity.values()){ - if(e.bitmap == null && e.sheetPos >= 0){ + for (Entity e : Entity.values()) { + if (e.bitmap == null && e.sheetPos >= 0) { //sheetpos; first sprite has pos 1. - int p = (e.sheetPos-1) * tileSize; + int p = (e.sheetPos - 1) * tileSize; int x = p % w; int y = ((p - x) / w) * tileSize; //read tile from sheet, scale to 32x32 diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java index 56d01b05..80bb8504 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java @@ -125,7 +125,6 @@ public void onStart() { super.onStart(); WorldActivityInterface worldProvider = this.worldProvider.get(); getActivity().setTitle(worldProvider.getWorld().getWorldDisplayName()); - Log.logFirebaseEvent(getActivity(), Log.CustomFirebaseEvent.MAPFRAGMENT_OPEN); } @@ -926,7 +925,7 @@ public String[] getLongClickOptions() { public void onLongClick(final double worldX, final double worldZ) { - final Activity activity = MapFragment.this.getActivity(); + final Activity activity = getActivity(); final Dimension dim = this.worldProvider.get().getDimension(); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java index 44ce41bb..72a96f1f 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java @@ -76,7 +76,7 @@ private void loadEntityMarkers(int chunkX, int chunkZ) { //int id = ((IntTag) compoundTag.getChildTagByKey("id")).getValue(); String tempName = compoundTag.getChildTagByKey("identifier").getValue().toString(); tempName = tempName.replace("minecraft:", ""); - tempName = tempName.substring(0, 1).toUpperCase() + tempName.substring(1); + //tempName = tempName.substring(0, 1).toUpperCase() + tempName.substring(1); int id = Entity.getEntity(tempName).id; Entity e = Entity.getEntity(id & 0xff); if (e != null && e.bitmap != null) { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java index 088e17f8..30dec8dd 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java @@ -80,8 +80,8 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onClick(View view) { - if(worldItemAdapter != null){ - if(worldItemAdapter.reloadWorldList()) { + if (worldItemAdapter != null) { + if (worldItemAdapter.reloadWorldList()) { Snackbar.make(view, R.string.reloaded_world_list, Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); } else { @@ -117,29 +117,29 @@ public void onClick(DialogInterface dialog, int whichButton) { //new tag name Editable pathEditable = pathText.getText(); String path = (pathEditable == null || pathEditable.toString().equals("")) ? null : pathEditable.toString(); - if(path == null){ + if (path == null) { return;//no path, no world } String levelDat = "/level.dat"; int levelIndex = path.lastIndexOf(levelDat); //if the path ends with /level.dat, remove it! - if(levelIndex >= 0 && path.endsWith(levelDat)) + if (levelIndex >= 0 && path.endsWith(levelDat)) path = path.substring(0, levelIndex); - String defaultPath = Environment.getExternalStorageDirectory().toString()+"/games/com.mojang/minecraftWorlds/"; + String defaultPath = Environment.getExternalStorageDirectory().toString() + "/games/com.mojang/minecraftWorlds/"; File worldFolder = new File(path); String errTitle = null, errMsg = String.format(getString(R.string.report_path_and_previous_search), path, defaultPath); - if(!worldFolder.exists()){ + if (!worldFolder.exists()) { errTitle = getString(R.string.no_file_folder_found_at_path); } - if(!worldFolder.isDirectory()){ + if (!worldFolder.isDirectory()) { errTitle = getString(R.string.worldpath_is_not_directory); } - if(!(new File(worldFolder, "level.dat").exists())){ + if (!(new File(worldFolder, "level.dat").exists())) { errTitle = getString(R.string.no_level_dat_found); } - if(errTitle != null) { + if (errTitle != null) { new AlertDialog.Builder(WorldItemListActivity.this) .setTitle(errTitle) .setMessage(errMsg) @@ -166,7 +166,7 @@ public void onClick(DialogInterface dialog, int whichButton) { WorldItemListActivity.this.startActivity(intent); } - } catch (Exception e){ + } catch (Exception e) { Snackbar.make(view, R.string.error_opening_world, Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); } @@ -187,8 +187,7 @@ public void onClick(DialogInterface dialog, int whichButton) { }); - - if(verifyStoragePermissions(this)){ + if (verifyStoragePermissions(this)) { //directly open the world list if we already have access initWorldList(); } @@ -196,14 +195,19 @@ public void onClick(DialogInterface dialog, int whichButton) { } - public void initWorldList(){ + public void initWorldList() { View recyclerView = findViewById(R.id.worlditem_list); assert recyclerView != null; boolean hasWorlds = setupRecyclerView((RecyclerView) recyclerView); - if(!hasWorlds){ - Snackbar.make(recyclerView, R.string.could_not_find_worlds, Snackbar.LENGTH_SHORT) - .setAction("Action", null).show(); + if (!hasWorlds) { + AlertDialog dia = new AlertDialog.Builder(this) + .setTitle(R.string.err_noworld_1) + .setView(R.layout.dialog_noworlds) + .create(); + dia.show(); + //Snackbar.make(recyclerView, R.string.could_not_find_worlds, Snackbar.LENGTH_SHORT) + // .setAction("Action", null).show(); } } @@ -227,7 +231,7 @@ public void onRequestPermissionsResult(int requestCode, AlertDialog.Builder builder = new AlertDialog.Builder(this); TextView msg = new TextView(this); float dpi = this.getResources().getDisplayMetrics().density; - msg.setPadding((int)(19*dpi), (int)(5*dpi), (int)(14*dpi), (int)(5*dpi)); + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); msg.setMaxLines(20); msg.setMovementMethod(LinkMovementMethod.getInstance()); msg.setText(R.string.no_sdcard_access); @@ -242,7 +246,6 @@ public void onRequestPermissionsResult(int requestCode, } - // Storage Permissions private static final int REQUEST_EXTERNAL_STORAGE = 4242; private static String[] PERMISSIONS_STORAGE = { @@ -252,9 +255,8 @@ public void onRequestPermissionsResult(int requestCode, /** * Checks if the app has permission to write to device storage - * + *

* If the app does not has permission then the user will be prompted to grant permissions - * */ public static boolean verifyStoragePermissions(Activity activity) { // Check if we have write permission @@ -311,14 +313,14 @@ public class WorldItemRecyclerViewAdapter extends RecyclerView.Adapter(); - String path = Environment.getExternalStorageDirectory().toString()+"/games/com.mojang/minecraftWorlds/"; + String path = Environment.getExternalStorageDirectory().toString() + "/games/com.mojang/minecraftWorlds/"; Log.d("minecraftWorlds path: " + path); this.savesFolder = new File(path); } //returns true if it has loaded a new list of worlds, false otherwise - boolean reloadWorldList(){ + boolean reloadWorldList() { mValues.clear(); File[] saves = savesFolder.exists() ? savesFolder.listFiles(new FileFilter() { @Override @@ -327,7 +329,7 @@ public boolean accept(File pathname) { } }) : null; - if(saves != null) { + if (saves != null) { Log.d("Number of minecraft worlds: " + saves.length); for (File save : saves) { @@ -365,7 +367,6 @@ public int compare(World a, World b) { } - @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) @@ -374,7 +375,6 @@ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { } - @SuppressLint("SetTextI18n") @Override public void onBindViewHolder(final ViewHolder holder, int position) { diff --git a/app/src/main/res/layout/dialog_noworlds.xml b/app/src/main/res/layout/dialog_noworlds.xml new file mode 100644 index 00000000..8c2b063c --- /dev/null +++ b/app/src/main/res/layout/dialog_noworlds.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap/external.png b/app/src/main/res/mipmap/external.png new file mode 100644 index 0000000000000000000000000000000000000000..14421553411a96e66dd84fadc40502f7c47deabb GIT binary patch literal 171280 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 + Blocktopograph @@ -309,5 +309,15 @@ [%5$s]. Teleport! Go to spawn - + No worlds found from your device storage. + + Note that newer Minecrafts puts your worlds in its private data by default. + For this reason, all tools won\'t be able to access the worlds. + You\'d have to set Minecraft to use \"external\" storage, by go to Minecraft-> + Settings->Profile->File Storage Location, and change the option from + \"Application\" to \"External\". + After that, newly created worlds would be accessible by Blocktopograph, while + existing worlds may not be opened. Moving those worlds to external may requires + root your device, and we\'re not available to help. + diff --git a/leveldb/src/main/java/com/litl/leveldb/NativeObject.java b/leveldb/src/main/java/com/litl/leveldb/NativeObject.java index 1e3cd6bd..52bb2d51 100644 --- a/leveldb/src/main/java/com/litl/leveldb/NativeObject.java +++ b/leveldb/src/main/java/com/litl/leveldb/NativeObject.java @@ -30,7 +30,7 @@ synchronized protected long getPtr() { return mPtr; } - synchronized public boolean isClosed(){ + synchronized public boolean isClosed() { return getPtr() == 0; } @@ -61,9 +61,10 @@ synchronized void unref() { @Override public synchronized void close() { - if (mPtr != 0) { - unref(); - } + closeNativeObject(mPtr); +// if (mPtr != 0) { +// unref(); +// } } @Override From 429f614fea5c9f17157fd00c7efd000fe45fde6b Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Mon, 28 Jan 2019 12:05:43 +0800 Subject: [PATCH 22/83] * Fixed bug causing markers not displayed after quit nbt editor. * Fixed bug keeping database opened after quit world. --- .../blocktopograph/WorldActivity.java | 7 ++- .../blocktopograph/map/Entity.java | 31 ++++++------- .../blocktopograph/map/MapFragment.java | 8 +++- .../blocktopograph/map/MarkerAsyncTask.java | 37 +++++++++------- .../src/main/java/com/litl/leveldb/DB.java | 24 ---------- .../java/com/litl/leveldb/NativeObject.java | 44 ++----------------- 6 files changed, 51 insertions(+), 100 deletions(-) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java index ef684da2..58c06a9d 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java @@ -195,7 +195,7 @@ public void onStart() { public void onResume() { Log.d("World activity resuming..."); super.onResume(); - +// // anonymous global counter of resumed world-activities Log.logFirebaseEvent(this, Log.CustomFirebaseEvent.WORLD_RESUME); @@ -257,6 +257,11 @@ public void onBackPressed() { .setCancelable(false) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { + try { + world.closeDown(); + } catch (WorldData.WorldDBException e) { + e.printStackTrace(); + } WorldActivity.this.finish(); } }) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java index 97b77350..17e851b3 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/Entity.java @@ -153,9 +153,14 @@ public enum Entity implements NamedBitmapProviderHandle, NamedBitmapProvider { this.identifier = "minecraft:" + wikiName; } + @NonNull @Override public Bitmap getBitmap() { - return this.bitmap; + if (bitmap != null) return bitmap; + if (Entity.UNKNOWN.bitmap == null) { + Entity.UNKNOWN.bitmap = Bitmap.createBitmap(24, 24, Bitmap.Config.RGB_565); + } + return Entity.UNKNOWN.bitmap; } @NonNull @@ -178,24 +183,14 @@ public String getBitmapDataName() { return this.dataNames[0]; } - //private static final Map entityMap; - //private static final Map entityByID; - - static { - //entityMap = new HashMap<>(); - //entityByID = new HashMap<>(); - //for (Entity e : Entity.values()) { - //for(String dataName : e.dataNames){ - // entityMap.put(e.wikiName.replace("_", "_"), e); - //} - // entityByID.put(e.id, e); - //} - } - //We don't need a cache for this! - - public static Entity getEntity(@NonNull String dataName) { + public static Entity getEntity(@NonNull String identifier) { + int i = identifier.indexOf(':'); + if (i != -1) { + if (!"minecraft".equals(identifier.substring(0, i))) return Entity.UNKNOWN; + identifier = identifier.substring(i + 1); + } for (Entity e : Entity.values()) { - if (dataName.equals(e.wikiName)) return e; + if (identifier.equals(e.wikiName)) return e; } return Entity.UNKNOWN; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java index 80bb8504..9bef8ef1 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java @@ -868,9 +868,13 @@ public void removeMarker(AbstractMarker marker) { * * @param marker The marker to add to the tile view. */ - public void addMarker(AbstractMarker marker) { + public synchronized void addMarker(AbstractMarker marker) { - if (proceduralMarkers.contains(marker)) return; + for (AbstractMarker abstractMarker : proceduralMarkers) { + if (abstractMarker.equals(marker)) { + removeMarker(abstractMarker); + } + } if (shrinkProceduralMarkersTask == null && ++proceduralMarkersInterval > MARKER_INTERVAL_CHECK) { diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java index 72a96f1f..33c87a07 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java @@ -71,23 +71,30 @@ private void loadEntityMarkers(int chunkX, int chunkZ) { if (entityData.tags == null) return; for (Tag tag : entityData.tags) { - if (tag instanceof CompoundTag) { - CompoundTag compoundTag = (CompoundTag) tag; - //int id = ((IntTag) compoundTag.getChildTagByKey("id")).getValue(); - String tempName = compoundTag.getChildTagByKey("identifier").getValue().toString(); - tempName = tempName.replace("minecraft:", ""); - //tempName = tempName.substring(0, 1).toUpperCase() + tempName.substring(1); - int id = Entity.getEntity(tempName).id; - Entity e = Entity.getEntity(id & 0xff); - if (e != null && e.bitmap != null) { - List pos = ((ListTag) compoundTag.getChildTagByKey("Pos")).getValue(); - float xf = ((FloatTag) pos.get(0)).getValue(); - float yf = ((FloatTag) pos.get(1)).getValue(); - float zf = ((FloatTag) pos.get(2)).getValue(); - - this.publishProgress(new AbstractMarker(Math.round(xf), Math.round(yf), Math.round(zf), dimension, e, false)); + if (!(tag instanceof CompoundTag)) continue; + CompoundTag compoundTag = (CompoundTag) tag; + Entity e = null; + { + Tag idTag = compoundTag.getChildTagByKey("id"); + if (idTag instanceof IntTag) { + Integer id = ((IntTag) idTag).getValue(); + if (id != null) e = Entity.getEntity(id); } } + if (e == null) { + Tag idenTag = compoundTag.getChildTagByKey("identifier"); + if (idenTag instanceof StringTag) { + String identifier = ((StringTag) idenTag).getValue(); + if (identifier != null) e = Entity.getEntity(identifier); + } + } + if (e == null) e = Entity.UNKNOWN; + List pos = ((ListTag) compoundTag.getChildTagByKey("Pos")).getValue(); + float xf = ((FloatTag) pos.get(0)).getValue(); + float yf = ((FloatTag) pos.get(1)).getValue(); + float zf = ((FloatTag) pos.get(2)).getValue(); + + this.publishProgress(new AbstractMarker(Math.round(xf), Math.round(yf), Math.round(zf), dimension, e, false)); } } catch (Exception e) { diff --git a/leveldb/src/main/java/com/litl/leveldb/DB.java b/leveldb/src/main/java/com/litl/leveldb/DB.java index cde3cece..7230c026 100644 --- a/leveldb/src/main/java/com/litl/leveldb/DB.java +++ b/leveldb/src/main/java/com/litl/leveldb/DB.java @@ -29,10 +29,6 @@ public void open() { @Override protected void closeNativeObject(long ptr) { nativeClose(ptr); - - if (mDestroyOnClose) { - destroy(mPath); - } } public void put(byte[] key, byte[] value) { @@ -98,43 +94,23 @@ public Iterator iterator() { public Iterator iterator(final Snapshot snapshot) { assertOpen("Database is closed"); - ref(); - - if (snapshot != null) { - snapshot.ref(); - } - return new Iterator(nativeIterator(mPtr, snapshot != null ? snapshot.getPtr() : 0)) { @Override protected void closeNativeObject(long ptr) { super.closeNativeObject(ptr); - if (snapshot != null) { - snapshot.unref(); - } - - DB.this.unref(); } }; } public Snapshot getSnapshot() { assertOpen("Database is closed"); - ref(); return new Snapshot(nativeGetSnapshot(mPtr)) { protected void closeNativeObject(long ptr) { nativeReleaseSnapshot(DB.this.getPtr(), getPtr()); - DB.this.unref(); } }; } - public void destroy() { - mDestroyOnClose = true; - if (getPtr() == 0) { - destroy(mPath); - } - } - public static void destroy(File path) { nativeDestroy(path.getAbsolutePath()); } diff --git a/leveldb/src/main/java/com/litl/leveldb/NativeObject.java b/leveldb/src/main/java/com/litl/leveldb/NativeObject.java index 52bb2d51..f9575dca 100644 --- a/leveldb/src/main/java/com/litl/leveldb/NativeObject.java +++ b/leveldb/src/main/java/com/litl/leveldb/NativeObject.java @@ -2,18 +2,11 @@ import java.io.Closeable; -import android.text.TextUtils; -import android.util.Log; - abstract class NativeObject implements Closeable { - private static final String TAG = NativeObject.class.getSimpleName(); + protected long mPtr; - private int mRefCount = 0; protected NativeObject() { - // The Java wrapper counts as one reference, will - // be released when closed - ref(); } protected NativeObject(long ptr) { @@ -40,47 +33,18 @@ protected void assertOpen(String message) { } } - synchronized void ref() { - mRefCount++; - } - - synchronized void unref() { - if (mRefCount <= 0) { - throw new IllegalStateException("Reference count is already 0"); - } - - mRefCount--; - - if (mRefCount == 0) { - closeNativeObject(mPtr); - mPtr = 0; - } - } - protected abstract void closeNativeObject(long ptr); @Override public synchronized void close() { closeNativeObject(mPtr); -// if (mPtr != 0) { -// unref(); -// } + mPtr = 0; } @Override protected void finalize() throws Throwable { - if (mPtr != 0) { - Class clazz = getClass(); - String name = clazz.getSimpleName(); - while (TextUtils.isEmpty(name)) { - clazz = clazz.getSuperclass(); - name = clazz.getSimpleName(); - } - - Log.w(TAG, "NativeObject " + name + " refcount: " + mRefCount - + " id: " + System.identityHashCode(this) - + " was finalized before native resource was closed, did you forget to call close()?"); - } + if (mPtr != 0) closeNativeObject(mPtr); + mPtr = 0; super.finalize(); } From 36099052177624645c84de7168775e2d28085981 Mon Sep 17 00:00:00 2001 From: oO0oO0oO0o0o00 Date: Fri, 1 Feb 2019 21:19:04 +0800 Subject: [PATCH 23/83] =?UTF-8?q?*=20=E8=BF=9B=E5=BA=A6=E5=AD=98=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 12 + app/src/main/AndroidManifest.xml | 34 +- .../blocktopograph/CreateWorldActivity.java | 24 + .../com/mithrilmania/blocktopograph/Log.java | 37 +- .../blocktopograph/MenuHelper.java | 85 ---- .../mithrilmania/blocktopograph/World.java | 72 +-- .../blocktopograph/WorldActivity.java | 139 +++--- .../blocktopograph/WorldData.java | 6 +- .../blocktopograph/WrappedApp.java | 24 + .../blocktopograph/flat/EditFlatFragment.java | 174 +++++++ .../blocktopograph/flat/EditLayerDialog.java | 128 +++++ .../blocktopograph/flat/FlatLayers.java | 48 ++ .../blocktopograph/flat/Layer.java | 28 ++ .../flat/PickBlockActivity.java | 193 ++++++++ .../blocktopograph/map/Block.java | 339 ++------------ .../mithrilmania/blocktopograph/map/Item.java | 342 ++++++++++++++ .../blocktopograph/map/MapFragment.java | 73 ++- .../blocktopograph/map/MarkerAsyncTask.java | 6 +- .../blocktopograph/map/MarkerManager.java | 5 +- .../map/renderer/SatelliteRenderer.java | 2 +- .../blocktopograph/nbt/EditorFragment.java | 77 +-- .../blocktopograph/util/UiUtil.java | 52 +++ .../worldlist/WorldItemDetailFragment.java | 6 +- .../worldlist/WorldItemListActivity.java | 438 ++++++++++-------- .../worldlist/WorldListUtil.java | 36 +- app/src/main/res/drawable/ic_add.xml | 5 + app/src/main/res/drawable/ic_add_black.xml | 5 + app/src/main/res/drawable/ic_help_black.xml | 5 + .../res/drawable/rect_border_round_corner.xml | 11 + .../main/res/layout/activity_create_world.xml | 72 +++ .../main/res/layout/activity_worldlist.xml | 26 +- .../common_dialog_activity_posibtn_bar.xml | 15 + app/src/main/res/layout/dialog_edit_layer.xml | 97 ++++ app/src/main/res/layout/dialog_pick_block.xml | 38 ++ app/src/main/res/layout/frag_layers.xml | 16 + app/src/main/res/layout/general_wait.xml | 20 + app/src/main/res/layout/item_pick_block.xml | 52 +++ app/src/main/res/layout/item_world_layer.xml | 56 +++ .../res/layout/worlditem_list_content.xml | 105 +++-- app/src/main/res/menu/world.xml | 10 +- app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 16 +- app/src/main/res/values/styles.xml | 11 + build.gradle | 6 + leveldb/build.gradle | 2 +- 45 files changed, 2079 insertions(+), 870 deletions(-) create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java delete mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/WrappedApp.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/flat/EditFlatFragment.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/flat/EditLayerDialog.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/flat/FlatLayers.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/flat/Layer.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/flat/PickBlockActivity.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/map/Item.java create mode 100644 app/src/main/java/com/mithrilmania/blocktopograph/util/UiUtil.java create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/drawable/ic_add_black.xml create mode 100644 app/src/main/res/drawable/ic_help_black.xml create mode 100644 app/src/main/res/drawable/rect_border_round_corner.xml create mode 100644 app/src/main/res/layout/activity_create_world.xml create mode 100644 app/src/main/res/layout/common_dialog_activity_posibtn_bar.xml create mode 100644 app/src/main/res/layout/dialog_edit_layer.xml create mode 100644 app/src/main/res/layout/dialog_pick_block.xml create mode 100644 app/src/main/res/layout/frag_layers.xml create mode 100644 app/src/main/res/layout/general_wait.xml create mode 100644 app/src/main/res/layout/item_pick_block.xml create mode 100644 app/src/main/res/layout/item_world_layer.xml diff --git a/app/build.gradle b/app/build.gradle index dc576cdf..d16be4b0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'io.fabric' android { compileSdkVersion 28 @@ -9,11 +10,15 @@ android { versionCode 13 versionName "1.8.3" } + dataBinding { + enabled true + } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + ext.alwaysUpdateBuildId = false } release { @@ -21,6 +26,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + } } dependencies { @@ -33,8 +42,11 @@ dependencies { implementation 'com.github.clans:fab:1.6.4' implementation 'com.android.support:design:28.0.0' implementation 'com.github.bmelnychuk:atv:1.2.8' + implementation 'com.github.woxthebox:draglistview:1.6.3' + implementation 'com.andreabaccega:android-edittext-validator:1.3.5' //core is the new recommended alias for analytics implementation 'com.google.firebase:firebase-core:16.0.6' + implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8' implementation 'org.jetbrains:annotations-java5:15.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fe3b4aca..fd8afa99 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,10 +6,11 @@ - - + + + + + android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"> @@ -33,8 +38,7 @@ android:name="com.mithrilmania.blocktopograph.worldlist.WorldItemListActivity" android:label="@string/title_worlditem_list" android:parentActivityName="com.mithrilmania.blocktopograph.SplashScreen" - android:theme="@style/AppTheme.NoActionBar"> - + android:theme="@style/AppTheme.NoActionBar" /> + + + + + + + + tools:node="replace" /> diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java new file mode 100644 index 00000000..10bb4d71 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/CreateWorldActivity.java @@ -0,0 +1,24 @@ +package com.mithrilmania.blocktopograph; + +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.View; + +import com.mithrilmania.blocktopograph.databinding.ActivityCreateWorldBinding; + + +public final class CreateWorldActivity extends AppCompatActivity { + + private ActivityCreateWorldBinding mBinding; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mBinding = DataBindingUtil.setContentView(this, R.layout.activity_create_world); + } + + public void onClickPositiveButton(View view) { + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/Log.java b/app/src/main/java/com/mithrilmania/blocktopograph/Log.java index 77eed52f..73ed4480 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/Log.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/Log.java @@ -4,8 +4,12 @@ import android.os.Bundle; import android.support.annotation.NonNull; +import com.crashlytics.android.Crashlytics; import com.google.firebase.analytics.FirebaseAnalytics; +import java.io.PrintWriter; +import java.io.StringWriter; + public class Log { //TODO This is kind of lazy, but repeating the Log.d(*msg*) everywhere is obnoxious @@ -15,23 +19,38 @@ public class Log { private static FirebaseAnalytics mFirebaseAnalytics; - public static void i(@NonNull String msg) { - android.util.Log.i(LOG_TAG, msg); + private static String concat(@NonNull Object caller, @NonNull String msg) { + Class clazz; + if (caller instanceof Class) clazz = (Class) caller; + else clazz = caller.getClass(); + return clazz.getSimpleName() + ": " + msg; + } + + public static void d(@NonNull Object caller, @NonNull String msg) { + android.util.Log.d(LOG_TAG, concat(caller, msg)); } - public static void d(@NonNull String msg) { - android.util.Log.d(LOG_TAG, msg); + public static void d(@NonNull Object caller, @NonNull Throwable throwable) { + StringWriter sw = new StringWriter(4096); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + android.util.Log.e(LOG_TAG, concat(caller, sw.toString())); } - public static void w(@NonNull String msg) { - android.util.Log.w(LOG_TAG, msg); + public static void e(@NonNull Object caller, @NonNull String msg) { + if (!BuildConfig.DEBUG) + Crashlytics.log(android.util.Log.DEBUG, LOG_TAG, concat(caller, msg)); } - public static void e(@NonNull String msg) { - android.util.Log.e(LOG_TAG, msg); + public static void e(@NonNull Object caller, @NonNull Throwable throwable) { + StringWriter sw = new StringWriter(4096); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + android.util.Log.e(LOG_TAG, concat(caller, sw.toString())); + if (!BuildConfig.DEBUG) Crashlytics.logException(throwable); } - synchronized static public FirebaseAnalytics getFirebaseAnalytics(@NonNull Context context) { + private synchronized static FirebaseAnalytics getFirebaseAnalytics(@NonNull Context context) { if (mFirebaseAnalytics == null) { mFirebaseAnalytics = FirebaseAnalytics.getInstance(context); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java b/app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java deleted file mode 100644 index 1d6a39cf..00000000 --- a/app/src/main/java/com/mithrilmania/blocktopograph/MenuHelper.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.mithrilmania.blocktopograph; - -import android.app.AlertDialog; -import android.content.Context; -import android.text.Html; -import android.text.TextUtils; -import android.text.method.LinkMovementMethod; -import android.view.MenuItem; -import android.widget.TextView; - -public class MenuHelper { - - public interface MenuContext { - - Context getContext(); - - boolean propagateOnOptionsItemSelected(MenuItem menuItem); - - } - - public static boolean onOptionsItemSelected(MenuContext menuContext, MenuItem item) { - - Context ctx = menuContext.getContext(); - - //some text pop-up dialogs, some with simple HTML tags. - switch (item.getItemId()) { - case R.id.action_about: { - - AlertDialog.Builder builder = new AlertDialog.Builder(ctx); - TextView msg = new TextView(ctx); - msg.setEllipsize(TextUtils.TruncateAt.MARQUEE); - float dpi = ctx.getResources().getDisplayMetrics().density; - msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); - msg.setMaxLines(20); - msg.setMovementMethod(LinkMovementMethod.getInstance()); - msg.setText(R.string.app_about); - builder.setView(msg) - .setTitle(R.string.action_about) - .setCancelable(true) - .setNeutralButton(android.R.string.ok, null) - .show(); - - return true; - } - case R.id.action_help: { - AlertDialog.Builder builder = new AlertDialog.Builder(ctx); - TextView msg = new TextView(ctx); - float dpi = ctx.getResources().getDisplayMetrics().density; - msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); - msg.setMaxLines(20); - msg.setMovementMethod(LinkMovementMethod.getInstance()); - msg.setText(R.string.app_help); - builder.setView(msg) - .setTitle(R.string.action_help) - .setCancelable(true) - .setNeutralButton(android.R.string.ok, null) - .show(); - - return true; - } - case R.id.action_changelog: { - AlertDialog.Builder builder = new AlertDialog.Builder(ctx); - TextView msg = new TextView(ctx); - float dpi = ctx.getResources().getDisplayMetrics().density; - msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); - msg.setMaxLines(20); - msg.setMovementMethod(LinkMovementMethod.getInstance()); - String content = String.format(ctx.getResources().getString(R.string.app_changelog), BuildConfig.VERSION_NAME); - //noinspection deprecation - msg.setText(Html.fromHtml(content)); - builder.setView(msg) - .setTitle(R.string.action_changelog) - .setCancelable(true) - .setNeutralButton(android.R.string.ok, null) - .show(); - - return true; - } - default: { - return menuContext.propagateOnOptionsItemSelected(item); - } - } - } - -} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/World.java b/app/src/main/java/com/mithrilmania/blocktopograph/World.java index 93ffdcbe..90a8df9b 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/World.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/World.java @@ -1,31 +1,25 @@ package com.mithrilmania.blocktopograph; import android.os.Bundle; +import android.support.annotation.NonNull; import android.util.SparseIntArray; +import com.litl.leveldb.Iterator; import com.mithrilmania.blocktopograph.map.MarkerManager; import com.mithrilmania.blocktopograph.nbt.convert.LevelDataConverter; import com.mithrilmania.blocktopograph.nbt.convert.NBTConstants; import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; -import com.mithrilmania.blocktopograph.nbt.tags.IntArrayTag; import com.mithrilmania.blocktopograph.nbt.tags.IntTag; import com.mithrilmania.blocktopograph.nbt.tags.ListTag; import com.mithrilmania.blocktopograph.nbt.tags.LongTag; import com.mithrilmania.blocktopograph.nbt.tags.StringTag; import com.mithrilmania.blocktopograph.nbt.tags.Tag; import com.mithrilmania.blocktopograph.util.io.TextFile; -import com.litl.leveldb.Iterator; - -import org.json.JSONArray; -import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.io.Serializable; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.Arrays; public class World implements Serializable { @@ -45,18 +39,21 @@ public class World implements Serializable { public static final String KEY_LAST_VERSION_SHORT = "lastWithVersion"; public static final String KEY_SHOW_COORDINATES = "showcoordinates"; - public final String worldName; + public String mark; + private final String worldName; public final File worldFolder; public final File levelFile; - public CompoundTag level; + private CompoundTag level; private transient WorldData worldData; - private MarkerManager markersManager; + private transient MarkerManager markersManager; - public World(File worldFolder) throws WorldLoadException { + public World(File worldFolder, String mark) throws WorldLoadException { if (!worldFolder.exists()) throw new WorldLoadException("Error: '" + worldFolder.getPath() + "' does not exist!"); + this.mark = mark; + this.worldFolder = worldFolder; // check for a custom world name @@ -70,15 +67,19 @@ public World(File worldFolder) throws WorldLoadException { if (!levelFile.exists()) throw new WorldLoadException("Error: Level-file: '" + levelFile.getPath() + "' does not exist!"); - try { - this.level = LevelDataConverter.read(levelFile); - } catch (IOException e) { - e.printStackTrace(); - throw new WorldLoadException("Error: failed to read level: '" + levelFile.getPath() + "' !"); - } } + public CompoundTag getLevel() { + if (level == null) + try { + this.level = LevelDataConverter.read(levelFile); + } catch (IOException e) { + e.printStackTrace(); + } + return level; + } + public String getWorldDisplayName() { if (worldName == null) return null; //return worldname, without special color codes @@ -174,7 +175,7 @@ public Bundle getMapVersionData() { } return bundle; } catch (Exception e) { - e.printStackTrace(); + Log.d(this, e); return null; } } @@ -198,6 +199,7 @@ public String getID() { return this.worldFolder.getName(); } + @NonNull public WorldData getWorldData() { if (this.worldData == null) { this.worldData = new WorldData(this); @@ -213,36 +215,6 @@ public void pause() throws WorldData.WorldDBException { closeDown(); } - /* - public byte[] loadChunkData(int chunkX, int chunkZ, ChunkTag dataType, Dimension dimension) throws WorldData.WorldDBLoadException, WorldData.WorldDBException { - return getWorldData().getChunkData(chunkX, chunkZ, dataType, dimension); - } - - public void saveChunkData(int chunkX, int chunkZ, ChunkData chunkData) throws IOException, WorldData.WorldDBException { - byte[] bData = chunkData.toByteArray(); - if (bData != null) getWorldData().writeChunkData(chunkX, chunkZ, chunkData.dataType, bData, chunkData.dimension); - else getWorldData().removeChunkData(chunkX, chunkZ, chunkData.dataType, chunkData.dimension); - } - - - //returns true if creating and saving was successful - public ChunkData createEmptyChunkData(int chunkX, int chunkZ, ChunkTag dataType, Dimension dimension){ - - ChunkData data = dataType.newInstance(dimension); - if(data == null) return null; - - data.createEmpty(); - - try { - this.saveChunkData(chunkX, chunkZ, data); - return data; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - */ - public void resume() throws WorldData.WorldDBException { this.getWorldData().openDB(); @@ -264,7 +236,7 @@ public void logDBKeys() { byte[] key = it.getKey(); byte[] value = it.getValue(); /*if(key.length == 9 && key[8] == RegionDataType.TERRAIN.dataID) */ - Log.d("key: " + new String(key) + " key in Hex: " + WorldData.bytesToHex(key, 0, key.length) + " size: " + value.length); + Log.d(this, "key: " + new String(key) + " key in Hex: " + WorldData.bytesToHex(key, 0, key.length) + " size: " + value.length); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java index 58c06a9d..171aef31 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldActivity.java @@ -3,24 +3,21 @@ import android.app.AlertDialog; import android.app.FragmentManager; import android.app.FragmentTransaction; -import android.content.Context; import android.content.DialogInterface; import android.os.Build; import android.os.Bundle; -import android.support.design.widget.Snackbar; -import android.support.v7.app.ActionBar; -import android.text.Editable; -import android.view.View; +import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; - +import android.support.design.widget.Snackbar; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; - -import android.view.Menu; +import android.text.Editable; import android.view.MenuItem; +import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.EditText; @@ -28,26 +25,24 @@ import android.widget.TextView; import android.widget.Toast; -import com.google.firebase.analytics.FirebaseAnalytics; -import com.mithrilmania.blocktopograph.map.Dimension; -import com.mithrilmania.blocktopograph.map.marker.AbstractMarker; -import com.mithrilmania.blocktopograph.nbt.convert.NBTConstants; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import com.mithrilmania.blocktopograph.chunk.NBTChunkData; +import com.mithrilmania.blocktopograph.map.Dimension; import com.mithrilmania.blocktopograph.map.MapFragment; +import com.mithrilmania.blocktopograph.map.marker.AbstractMarker; import com.mithrilmania.blocktopograph.map.renderer.MapType; import com.mithrilmania.blocktopograph.nbt.EditableNBT; import com.mithrilmania.blocktopograph.nbt.EditorFragment; import com.mithrilmania.blocktopograph.nbt.convert.DataConverter; +import com.mithrilmania.blocktopograph.nbt.convert.NBTConstants; import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; import com.mithrilmania.blocktopograph.nbt.tags.Tag; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + public class WorldActivity extends AppCompatActivity - implements NavigationView.OnNavigationItemSelectedListener, WorldActivityInterface, MenuHelper.MenuContext { + implements NavigationView.OnNavigationItemSelectedListener, WorldActivityInterface { private World world; @@ -83,15 +78,22 @@ public void onWindowFocusChanged(boolean hasFocus) { } @Override - protected void onCreate(Bundle savedInstanceState) { - Log.d("World activity creating..."); - - super.onCreate(savedInstanceState); + protected void onRestart() { + super.onRestart(); + } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putSerializable(World.ARG_WORLD_SERIALIZED, world); + } + @Override + protected void onCreate(Bundle savedInstanceState) { /* Retrieve world from previous state or intent */ + Log.d(this, "World activity creating..."); this.world = (World) (savedInstanceState == null ? getIntent().getSerializableExtra(World.ARG_WORLD_SERIALIZED) : savedInstanceState.getSerializable(World.ARG_WORLD_SERIALIZED)); @@ -104,21 +106,23 @@ protected void onCreate(Bundle savedInstanceState) { return; } + super.onCreate(savedInstanceState); + /* Layout */ setContentView(R.layout.activity_world); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + Toolbar toolbar = findViewById(R.id.toolbar); assert toolbar != null; setSupportActionBar(toolbar); - NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); + NavigationView navigationView = findViewById(R.id.nav_view); assert navigationView != null; navigationView.setNavigationItemSelectedListener(this); // Main drawer, quick access to different menus, tools and map-types. - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + DrawerLayout drawer = findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); assert drawer != null; @@ -130,12 +134,12 @@ protected void onCreate(Bundle savedInstanceState) { assert headerView != null; // Title = world-name - TextView title = (TextView) headerView.findViewById(R.id.world_drawer_title); + TextView title = headerView.findViewById(R.id.world_drawer_title); assert title != null; title.setText(this.world.getWorldDisplayName()); // Subtitle = world-seed (Tap worldseed to choose to copy it) - TextView subtitle = (TextView) headerView.findViewById(R.id.world_drawer_subtitle); + TextView subtitle = headerView.findViewById(R.id.world_drawer_subtitle); assert subtitle != null; /* @@ -181,19 +185,19 @@ Send anonymous world data to the Firebase (Google analytics for Android) server. // anonymous global counter of opened worlds Log.logFirebaseEvent(this, Log.CustomFirebaseEvent.WORLD_OPEN, bundle); - Log.d("World activity created"); + Log.d(this, "World activity created"); } @Override public void onStart() { - Log.d("World activity starting..."); + Log.d(this, "World activity starting..."); super.onStart(); - Log.d("World activity started"); + Log.d(this, "World activity started"); } @Override public void onResume() { - Log.d("World activity resuming..."); + Log.d(this, "World activity resuming..."); super.onResume(); // // anonymous global counter of resumed world-activities @@ -205,12 +209,12 @@ public void onResume() { this.onFatalDBError(e); } - Log.d("World activity resumed"); + Log.d(this, "World activity resumed"); } @Override public void onPause() { - Log.d("World activity pausing..."); + Log.d(this, "World activity pausing..."); super.onPause(); try { @@ -219,26 +223,26 @@ public void onPause() { e.printStackTrace(); } - Log.d("World activity paused"); + Log.d(this, "World activity paused"); } @Override public void onStop() { - Log.d("World activity stopping..."); + Log.d(this, "World activity stopping..."); super.onStop(); - Log.d("World activity stopped"); + Log.d(this, "World activity stopped"); } @Override public void onDestroy() { - Log.d("World activity destroying..."); + Log.d(this, "World activity destroying..."); super.onDestroy(); - Log.d("World activity destroyed..."); + Log.d(this, "World activity destroyed..."); } @Override public void onBackPressed() { - DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + DrawerLayout drawer = findViewById(R.id.drawer_layout); assert drawer != null; if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); @@ -289,38 +293,16 @@ public void onClick(DialogInterface dialog, int id) { } } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.world, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return MenuHelper.onOptionsItemSelected(this, item); - } - - @Override - public Context getContext() { - return this; - } - - @Override - public boolean propagateOnOptionsItemSelected(MenuItem item) { - return super.onOptionsItemSelected(item); - } - @SuppressWarnings("StatementWithEmptyBody") @Override - public boolean onNavigationItemSelected(MenuItem item) { + public boolean onNavigationItemSelected(@NonNull MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); - Log.d("World activity nav-drawer menu item selected: " + id); + Log.d(this, "World activity nav-drawer menu item selected: " + id); - final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + final DrawerLayout drawer = findViewById(R.id.drawer_layout); assert drawer != null; @@ -530,7 +512,7 @@ public void onOpen() { } default: //Warning, we might have messed with the menu XML! - Log.w("pressed unknown navigation-item in world-activity-drawer"); + Log.d(this, "pressed unknown navigation-item in world-activity-drawer"); break; } @@ -612,7 +594,7 @@ public void removeRootTag(Tag tag) { * Loads local player data "~local-player" or level.dat>"Player" into an EditableNBT. * * @return EditableNBT, local player NBT data wrapped in a handle to use for saving + metadata - * @throws Exception + * @throws Exception wtf */ public EditableNBT getEditablePlayer() throws Exception { @@ -659,7 +641,7 @@ public void openSpecialDBEntry(final World.SpecialDBEntryType entryType) { //throw new Exception("\"" + entryType.keyName + "\" not found in DB."); } - Log.i("Opening NBT editor for \"" + entryType.keyName + "\" from world database."); + Log.d(this, "Opening NBT editor for \"" + entryType.keyName + "\" from world database."); openNBTEditor(editableEntry); @@ -668,10 +650,11 @@ public void openSpecialDBEntry(final World.SpecialDBEntryType entryType) { String msg = e.getMessage(); if (e instanceof IOException) - msg = String.format(getString(R.string.failed_to_read_x_from_db), entryType.keyName); + Log.d(this, String.format(getString(R.string.failed_to_read_x_from_db), entryType.keyName)); + else Log.d(this, e); new AlertDialog.Builder(WorldActivity.this) - .setMessage(msg) + .setMessage(msg == null ? "" : msg) .setCancelable(false) .setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { @@ -759,7 +742,7 @@ public void onOpen() { } catch (Exception e) { e.printStackTrace(); - Log.d("Failed to open player entry in DB. key: " + playerKey); + Log.d(this, "Failed to open player entry in DB. key: " + playerKey); if (content != null) Snackbar.make(content, String.format(getString(R.string.failed_read_player_from_db_with_key_x), playerKey), Snackbar.LENGTH_LONG) @@ -807,7 +790,7 @@ public void onOpen() { public EditableNBT openEditableNbtLevel(String subTagName) { //make a copy first, the user might not want to save changed tags. - final CompoundTag workCopy = world.level.getDeepCopy(); + final CompoundTag workCopy = world.getLevel().getDeepCopy(); final ArrayList workCopyContents; final String contentTitle; if (subTagName == null) { @@ -905,7 +888,7 @@ public boolean getShowGrid() { @Override public void onFatalDBError(WorldData.WorldDBException worldDBException) { - Log.d(worldDBException.getMessage()); + Log.d(this, worldDBException.getMessage()); worldDBException.printStackTrace(); //already dead? (happens on multiple onFatalDBError(e) calls) @@ -990,11 +973,16 @@ public void onClick(DialogInterface dialog, int id) { */ public void openNBTEditor(EditableNBT editableNBT) { + if (editableNBT == null) { + Toast.makeText(this, "Empty data.", Toast.LENGTH_SHORT).show(); + return; + } + // see changeContentFragment(callback) this.confirmContentClose = getString(R.string.confirm_close_nbt_editor); EditorFragment editorFragment = new EditorFragment(); - editorFragment.setEditableNBT(editableNBT); + editorFragment.setNbt(editableNBT); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.world_content, editorFragment); @@ -1042,7 +1030,7 @@ public void openChunkNBTEditor(final int chunkX, final int chunkZ, final NBTChun if (nbtChunkData == null) { //should never happen - Log.w("User tried to open null chunkData in the nbt-editor!!!"); + Log.e(this, "User tried to open null chunkData in the nbt-editor!!!"); return; } @@ -1064,8 +1052,6 @@ public void openChunkNBTEditor(final int chunkX, final int chunkZ, final NBTChun .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - Snackbar.make(viewGroup, R.string.creating_and_saving_chunk_NBT_data, Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); nbtChunkData.createEmpty(); try { nbtChunkData.write(); @@ -1075,6 +1061,7 @@ public void onClick(DialogInterface dialog, int whichButton) { } catch (Exception e) { Snackbar.make(viewGroup, R.string.failed_to_create_or_save_chunk_NBT_data, Snackbar.LENGTH_LONG) .setAction("Action", null).show(); + Log.d(this, e); } } }) diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java index 1866dda8..e59e33a7 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WorldData.java @@ -84,14 +84,14 @@ public void load() throws WorldDataLoadException { throw new WorldDataLoadException("World-db folder is not writable! World-db folder: " + dbFile.getAbsolutePath()); } - Log.d("WorldFolder: " + world.worldFolder.getAbsolutePath()); - Log.d("WorldFolder permissions: read: " + dbFile.canRead() + " write: " + dbFile.canWrite()); + Log.d(this, "WorldFolder: " + world.worldFolder.getAbsolutePath()); + Log.d(this, "WorldFolder permissions: read: " + dbFile.canRead() + " write: " + dbFile.canWrite()); if (dbFile.listFiles() == null) throw new WorldDataLoadException("Failed loading world-db: cannot list files in worldfolder"); for (File dbEntry : dbFile.listFiles()) { - Log.d("File in db: " + dbEntry.getAbsolutePath()); + Log.d(this, "File in db: " + dbEntry.getAbsolutePath()); } this.db = new DB(dbFile); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/WrappedApp.java b/app/src/main/java/com/mithrilmania/blocktopograph/WrappedApp.java new file mode 100644 index 00000000..a1a57252 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/WrappedApp.java @@ -0,0 +1,24 @@ +package com.mithrilmania.blocktopograph; + +import android.app.Application; + +import com.crashlytics.android.Crashlytics; + +import io.fabric.sdk.android.Fabric; + +public class WrappedApp extends Application implements Thread.UncaughtExceptionHandler { + + private Thread.UncaughtExceptionHandler mDefaultUEhan; + + public WrappedApp() { + if (!BuildConfig.DEBUG) Fabric.with(this, new Crashlytics()); + mDefaultUEhan = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + Log.e(this, throwable); + mDefaultUEhan.uncaughtException(thread, throwable); + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/flat/EditFlatFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/flat/EditFlatFragment.java new file mode 100644 index 00000000..a3b1e713 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/flat/EditFlatFragment.java @@ -0,0 +1,174 @@ +package com.mithrilmania.blocktopograph.flat; + +import android.app.Activity; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.mithrilmania.blocktopograph.Log; +import com.mithrilmania.blocktopograph.R; +import com.mithrilmania.blocktopograph.databinding.FragLayersBinding; +import com.mithrilmania.blocktopograph.databinding.ItemWorldLayerBinding; +import com.mithrilmania.blocktopograph.map.Block; +import com.mithrilmania.blocktopograph.util.UiUtil; +import com.woxthebox.draglistview.DragItemAdapter; +import com.woxthebox.draglistview.DragListView; + +import java.lang.ref.WeakReference; +import java.util.LinkedList; + +public final class EditFlatFragment extends Fragment { + + public static final int REQUEST_CODE_EDIT_LAYER = 2013; + public static final String EXTRA_KEY_LIST_INDEX = "index"; + public static final String EXTRA_KEY_LIST_IS_ADD = "isAdd"; + public static final String EXTRA_KEY_LIST_LAYER = "layer"; + public static final String EXTRA_KEY_LIST_EXISTING_SUM = "existingSum"; + private FragLayersBinding mBinding; + private MeowAdapter mMeowAdapter; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + mBinding = DataBindingUtil.inflate(inflater, R.layout.frag_layers, container, false); + new LoadTask(this).execute(); + return mBinding.getRoot(); + } + + private void onClickAddOrEditLayer(int index, Layer layer, boolean isAdd, int existingSum) { + Activity activity = getActivity(); + if (activity == null) return; + startActivityForResult( + new Intent(activity, EditLayerDialog.class) + .putExtra(EXTRA_KEY_LIST_INDEX, index) + .putExtra(EXTRA_KEY_LIST_LAYER, layer) + .putExtra(EXTRA_KEY_LIST_IS_ADD, isAdd) + .putExtra(EXTRA_KEY_LIST_EXISTING_SUM, existingSum), + REQUEST_CODE_EDIT_LAYER + ); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_EDIT_LAYER: { + if (resultCode != Activity.RESULT_OK) return; + return; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + private class MeowAdapter extends DragItemAdapter { + + MeowAdapter() { + setItemList(new LinkedList<>()); + } + + @Override + public long getUniqueItemId(int i) { + Layer layer = mItemList.get(i); + return (layer.block.id << 16) | (layer.block.subId << 8) | layer.amount; + } + + void loadDefault() { + Layer layer = new Layer(Block.B_7_0_BEDROCK, 1); + addItem(0, layer); + layer = new Layer(Block.B_3_0_DIRT, 2); + addItem(0, layer); + layer = new Layer(Block.B_2_0_GRASS, 1); + addItem(0, layer); + } + + @NonNull + @Override + public MeowHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { + ItemWorldLayerBinding binding = DataBindingUtil.inflate( + getLayoutInflater(), R.layout.item_world_layer, mBinding.list, false); + return new MeowHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull MeowHolder holder, int position) { + super.onBindViewHolder(holder, position); + Layer layer = mItemList.get(position); + holder.binding.setLayer(layer); + holder.binding.icon.setImageBitmap(layer.block.bitmap); + holder.binding.add.setOnClickListener(view -> { + int sum = 0;//Existing height. Total height shall be less then 128 we need to ensure. + for (Layer l : mItemList) { + sum += l.amount; + } + onClickAddOrEditLayer(position, new Layer(), true, sum); + }); + } + + private class MeowHolder extends DragItemAdapter.ViewHolder { + + ItemWorldLayerBinding binding; + + MeowHolder(@NonNull ItemWorldLayerBinding binding) { + super(binding.getRoot(), R.id.icon, false); + this.binding = binding; + } + } + } + + private static class LoadTask extends AsyncTask { + + private final WeakReference thiz; + private AlertDialog mWaitDialog; + + private LoadTask(EditFlatFragment thiz) { + this.thiz = new WeakReference<>(thiz); + } + + @Override + protected void onPreExecute() { + Activity activity = thiz.get().getActivity(); + if (activity == null) return; + mWaitDialog = UiUtil.buildWaitDialog(activity); + mWaitDialog.show(); + } + + @Override + protected Void doInBackground(Void... voids) { + try { + Activity activity = thiz.get().getActivity(); + if (activity == null) return null; + Block.loadBitmaps(activity.getAssets()); + } catch (Exception e) { + Log.d(this, e); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + EditFlatFragment thiz = this.thiz.get(); + //If the activity quit nothing needs to be done. + if (thiz == null) return; + try { + DragListView listView = thiz.mBinding.list; + thiz.mMeowAdapter = thiz.new MeowAdapter(); + listView.setLayoutManager(new LinearLayoutManager(thiz.getActivity())); + listView.setCanDragHorizontally(false); + listView.setAdapter(thiz.mMeowAdapter, false); + thiz.mMeowAdapter.loadDefault(); + } catch (Exception e) { + Log.d(this, e); + Activity activity = thiz.getActivity(); + if (activity != null) UiUtil.toastError(activity); + } + mWaitDialog.dismiss(); + } + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/flat/EditLayerDialog.java b/app/src/main/java/com/mithrilmania/blocktopograph/flat/EditLayerDialog.java new file mode 100644 index 00000000..a6925a83 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/flat/EditLayerDialog.java @@ -0,0 +1,128 @@ +package com.mithrilmania.blocktopograph.flat; + +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; + +import com.andreabaccega.formedittextvalidator.Validator; +import com.andreabaccega.widget.FormEditText; +import com.mithrilmania.blocktopograph.Log; +import com.mithrilmania.blocktopograph.R; +import com.mithrilmania.blocktopograph.databinding.DialogEditLayerBinding; +import com.mithrilmania.blocktopograph.map.Block; +import com.mithrilmania.blocktopograph.util.UiUtil; + +import java.io.Serializable; + +import static com.mithrilmania.blocktopograph.flat.EditFlatFragment.EXTRA_KEY_LIST_EXISTING_SUM; +import static com.mithrilmania.blocktopograph.flat.EditFlatFragment.EXTRA_KEY_LIST_INDEX; +import static com.mithrilmania.blocktopograph.flat.EditFlatFragment.EXTRA_KEY_LIST_IS_ADD; +import static com.mithrilmania.blocktopograph.flat.EditFlatFragment.EXTRA_KEY_LIST_LAYER; + +public final class EditLayerDialog extends AppCompatActivity { + + public static final int REQUEST_CODE_PICK_BLOCK = 2014; + private boolean mIsAdd; + private int mExistingSum; + private int mPositon; + private DialogEditLayerBinding mBinding; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mBinding = DataBindingUtil.setContentView(this, R.layout.dialog_edit_layer); + + Intent intent = getIntent(); + if (savedInstanceState != null || intent == null) { + //The activity does not do anything worth restore. + finish(); + return; + } + Serializable ser = intent.getSerializableExtra(EXTRA_KEY_LIST_LAYER); + if (!(ser instanceof Layer)) { + Log.d(this, "wtf?"); + finish(); + return; + } + Layer layer = (Layer) ser; + + mPositon = intent.getIntExtra(EXTRA_KEY_LIST_INDEX, -1); + mBinding.setLayer(layer); + mIsAdd = intent.getBooleanExtra(EXTRA_KEY_LIST_IS_ADD, true); + mExistingSum = intent.getIntExtra(EXTRA_KEY_LIST_EXISTING_SUM, 0); + + if (mIsAdd) setTitle(R.string.edit_flat_add_layer_title); + else setTitle(R.string.edit_flat_edit_layer_title); + + FormEditText amountBar = mBinding.amount; + amountBar.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void afterTextChanged(Editable editable) { + mBinding.amount.testValidity(); + } + }); + amountBar.addValidator(new AmountValidator(getString(R.string.edit_layer_amount_constrait))); + + mBinding.icon.setImageBitmap(layer.block.bitmap); + } + + public void onClickChangeBlock(View view) { + startActivityForResult( + new Intent(this, PickBlockActivity.class), REQUEST_CODE_PICK_BLOCK + ); + } + + public void onClickPositiveButton(View view) { + // + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + switch (requestCode) { + case REQUEST_CODE_PICK_BLOCK: { + if (resultCode != RESULT_OK) return; + assert data != null; + Block block = (Block) data.getSerializableExtra(PickBlockActivity.EXTRA_KEY_BLOCK); + mBinding.getLayer().block = block; + UiUtil.blendBlockColor(mBinding.frame, block); + mBinding.notifyChange(); + return; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + public class AmountValidator extends Validator { + + AmountValidator(String customErrorMessage) { + super(customErrorMessage); + } + + @Override + public boolean isValid(EditText et) { + String text = et.getText().toString(); + int val; + try { + val = Integer.parseInt(text); + } catch (NumberFormatException e) { + et.setText(""); + return true; + } + return val >= 0 && val < 128 - mExistingSum; + } + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/flat/FlatLayers.java b/app/src/main/java/com/mithrilmania/blocktopograph/flat/FlatLayers.java new file mode 100644 index 00000000..4d6ccc47 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/flat/FlatLayers.java @@ -0,0 +1,48 @@ +package com.mithrilmania.blocktopograph.flat; + +import com.mithrilmania.blocktopograph.Log; + +import java.util.List; + +public final class FlatLayers { + + private int biomeId; + private int encodingVersion; + private final List mLayers; + + private FlatLayers(List mLayers) { + this.mLayers = mLayers; + biomeId = 1; + encodingVersion = 4; + //biome_id + //block_layers + //block_name + //block_data + //count + //encoding_version + } + + public List getLayers() { + return mLayers; + } + + public void clear() { + mLayers.clear(); + } + + public int getBiomeId() { + return biomeId; + } + + public void setBiomeId(int biomeId) { + this.biomeId = biomeId; + } + + public int getEncodingVersion() { + return encodingVersion; + } + + public void setEncodingVersion(int encodingVersion) { + this.encodingVersion = encodingVersion; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/flat/Layer.java b/app/src/main/java/com/mithrilmania/blocktopograph/flat/Layer.java new file mode 100644 index 00000000..9a1b1a1a --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/flat/Layer.java @@ -0,0 +1,28 @@ +package com.mithrilmania.blocktopograph.flat; + +import com.mithrilmania.blocktopograph.map.Block; + +import java.io.Serializable; + +public final class Layer implements Serializable { + + public Block block; + public int amount; + + public Layer() { + block = Block.B_0_0_AIR; + amount = 1; + } + + Layer(Block block, int amount) { + this.block = block; + this.amount = amount; + } + + public Layer(int legacyId, int amount) { + Block block = Block.getBlockWithLegacyId(legacyId); + if (block == null) this.block = Block.B_0_0_AIR; + else this.block = block; + amount = 0; + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/flat/PickBlockActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/flat/PickBlockActivity.java new file mode 100644 index 00000000..be68f949 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/flat/PickBlockActivity.java @@ -0,0 +1,193 @@ +package com.mithrilmania.blocktopograph.flat; + +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.graphics.ColorUtils; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewGroup; + +import com.mithrilmania.blocktopograph.Log; +import com.mithrilmania.blocktopograph.R; +import com.mithrilmania.blocktopograph.databinding.DialogPickBlockBinding; +import com.mithrilmania.blocktopograph.databinding.ItemPickBlockBinding; +import com.mithrilmania.blocktopograph.map.Block; +import com.mithrilmania.blocktopograph.util.Color; +import com.mithrilmania.blocktopograph.util.UiUtil; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +public final class PickBlockActivity extends AppCompatActivity { + + public static final String EXTRA_KEY_BLOCK = "block"; + private DialogPickBlockBinding mBinding; + private LinearLayoutManager mListManager; + private MeowAdapter mAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mBinding = DataBindingUtil.setContentView(this, R.layout.dialog_pick_block); + + RecyclerView list = mBinding.list; + mListManager = new LinearLayoutManager(this); + list.setLayoutManager(mListManager); + mAdapter = new MeowAdapter(); + list.setAdapter(mAdapter); + + mBinding.text.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void afterTextChanged(Editable editable) { + new UpdateListTask(PickBlockActivity.this).execute(); + } + }); + // + } + + private class MeowAdapter extends RecyclerView.Adapter { + + private final List mBlocks; + + private MeowAdapter() { + mBlocks = new ArrayList<>(4096); + } + + @NonNull + @Override + public MeowHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { + ItemPickBlockBinding binding = DataBindingUtil.inflate( + getLayoutInflater(), R.layout.item_pick_block, mBinding.list, false); + View root = binding.getRoot(); + MeowHolder holder = new MeowHolder(root); + holder.binding = binding; + root.setOnClickListener(v -> { + setResult(RESULT_OK, new Intent().putExtra(EXTRA_KEY_BLOCK, mBlocks.get(i)));//;(Block) v.getTag())); + finish(); + }); + return holder; + } + + @Override + public void onBindViewHolder(@NonNull MeowHolder meowHolder, int i) { + Block block = mBlocks.get(i); + ItemPickBlockBinding binding = meowHolder.binding; + binding.setBlock(block); + //binding.getRoot().setTag(block); + binding.icon.setImageBitmap(block.getBitmap()); + UiUtil.blendBlockColor(binding.getRoot(), block); + } + + @Override + public int getItemCount() { + return mBlocks.size(); + } + + List getListControl() { + return mBlocks; + } + + class MeowHolder extends RecyclerView.ViewHolder { + + ItemPickBlockBinding binding; + + MeowHolder(@NonNull View itemView) { + super(itemView); + } + } + + } + + private static class UpdateListTask extends AsyncTask { + + private final WeakReference thiz; + private String keyword; + private int index1, index2; + + private UpdateListTask(PickBlockActivity thiz) { + this.thiz = new WeakReference<>(thiz); + } + + @Override + protected void onPreExecute() { + PickBlockActivity activity = thiz.get(); + if (activity == null) return; + keyword = activity.mBinding.text.getText().toString(); + + //Save for restoring. + index1 = activity.mListManager.findFirstVisibleItemPosition(); + index2 = activity.mListManager.findLastVisibleItemPosition(); + if (index2 <= index1) index2 = index1; + } + + @Override + protected Void doInBackground(Void... voids) { + PickBlockActivity activity = thiz.get(); + if (activity == null) return null; + List list = activity.mAdapter.getListControl(); + + //Backup all candidates. + Block[] olds = null; + if (index1 >= 0) { + olds = new Block[index2 - index1 + 1]; + for (int i = index1, limit = list.size(); i < olds.length && i < limit; i++) { + olds[i] = list.get(i); + } + } + //Reset. + list.clear(); + + //Restore. + String text = keyword; + int num; + try { + num = Integer.parseInt(text); + } catch (NumberFormatException e) { + num = -1; + } + for (Block b : Block.values()) + if (b.id == num || + (b.str != null && b.str.contains(text)) || + (b.subName != null && b.subName.contains(text))) + list.add(b); + int position = -1; + if (olds != null) for (Block b : olds) { + int i = list.indexOf(b); + if (i != -1) { + position = i; + break; + } + } + index1 = position; + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + PickBlockActivity activity = thiz.get(); + if (activity == null) return; + activity.mAdapter.notifyDataSetChanged(); + if (index1 >= 0) activity.mListManager.scrollToPosition(index1); + } + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java index 51a17883..7a70b949 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/Block.java @@ -6,6 +6,7 @@ import android.support.annotation.NonNull; import android.util.SparseArray; +import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.util.NamedBitmapProvider; import com.mithrilmania.blocktopograph.util.NamedBitmapProviderHandle; import com.mithrilmania.blocktopograph.util.Color; @@ -568,252 +569,37 @@ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { B_211_0_DENY("deny", null, 211, 0, "blocks/deny.png", 0xff6ca28a, false), B_212_0_BORDER_BLOCK("border_block", null, 212, 0, "blocks/border_block.png", 0xff76a7fc, false), B_230_0_CHALKBOARD("chalkboard", null, 230, 0, "blocks/chalkboard.png", 0xff3d6e86, false), - B_242_0_CAMERA("camera", null, 242, 0, "blocks/camera.png", 0xff3d6e86, false), + B_242_0_CAMERA("camera", null, 242, 0, "blocks/camera.png", 0xff3d6e86, false); + private static final Map byDataName = new HashMap<>(); + private static final SparseArray> blockMap; - - - - - /* - * ============================== - * Items - * ============================== - */ - - I_256_0_IRON_SHOVEL("iron_shovel", null, 256, 0, "items/iron_shovel.png"), - I_257_0_IRON_PICKAXE("iron_pickaxe", null, 257, 0, "items/iron_pickaxe.png"), - I_258_0_IRON_AXE("iron_axe", null, 258, 0, "items/iron_axe.png"), - I_259_0_FLINT_AND_STEEL("flint_and_steel", null, 259, 0, "items/flint_and_steel.png"), - I_260_0_APPLE("apple", null, 260, 0, "items/apple.png"), - I_261_0_BOW("bow", null, 261, 0, "items/bow.png"), - I_262_0_ARROW("arrow", null, 262, 0, "items/arrow.png"), - I_263_0_COAL_COAL("coal", "coal", 263, 0, "items/coal_coal.png"), - I_263_1_COAL_CHARCOAL("coal", "charcoal", 263, 1, "items/coal_charcoal.png"), - I_264_0_DIAMOND("diamond", null, 264, 0, "items/diamond.png"), - I_265_0_IRON_INGOT("iron_ingot", null, 265, 0, "items/iron_ingot.png"), - I_266_0_GOLD_INGOT("gold_ingot", null, 266, 0, "items/gold_ingot.png"), - I_267_0_IRON_SWORD("iron_sword", null, 267, 0, "items/iron_sword.png"), - I_268_0_WOODEN_SWORD("wooden_sword", null, 268, 0, "items/wooden_sword.png"), - I_269_0_WOODEN_SHOVEL("wooden_shovel", null, 269, 0, "items/wooden_shovel.png"), - I_270_0_WOODEN_PICKAXE("wooden_pickaxe", null, 270, 0, "items/wooden_pickaxe.png"), - I_271_0_WOODEN_AXE("wooden_axe", null, 271, 0, "items/wooden_axe.png"), - I_272_0_STONE_SWORD("stone_sword", null, 272, 0, "items/stone_sword.png"), - I_273_0_STONE_SHOVEL("stone_shovel", null, 273, 0, "items/stone_shovel.png"), - I_274_0_STONE_PICKAXE("stone_pickaxe", null, 274, 0, "items/stone_pickaxe.png"), - I_275_0_STONE_AXE("stone_axe", null, 275, 0, "items/stone_axe.png"), - I_276_0_DIAMOND_SWORD("diamond_sword", null, 276, 0, "items/diamond_sword.png"), - I_277_0_DIAMOND_SHOVEL("diamond_shovel", null, 277, 0, "items/diamond_shovel.png"), - I_278_0_DIAMOND_PICKAXE("diamond_pickaxe", null, 278, 0, "items/diamond_pickaxe.png"), - I_279_0_DIAMOND_AXE("diamond_axe", null, 279, 0, "items/diamond_axe.png"), - I_280_0_STICK("stick", null, 280, 0, "items/stick.png"), - I_281_0_BOWL("bowl", null, 281, 0, "items/bowl.png"), - I_282_0_MUSHROOM_STEW("mushroom_stew", null, 282, 0, "items/mushroom_stew.png"), - I_283_0_GOLDEN_SWORD("golden_sword", null, 283, 0, "items/golden_sword.png"), - I_284_0_GOLDEN_SHOVEL("golden_shovel", null, 284, 0, "items/golden_shovel.png"), - I_285_0_GOLDEN_PICKAXE("golden_pickaxe", null, 285, 0, "items/golden_pickaxe.png"), - I_286_0_GOLDEN_AXE("golden_axe", null, 286, 0, "items/golden_axe.png"), - I_287_0_STRING("string", null, 287, 0, "items/string.png"), - I_288_0_FEATHER("feather", null, 288, 0, "items/feather.png"), - I_289_0_GUNPOWDER("gunpowder", null, 289, 0, "items/gunpowder.png"), - I_290_0_WOODEN_HOE("wooden_hoe", null, 290, 0, "items/wooden_hoe.png"), - I_291_0_STONE_HOE("stone_hoe", null, 291, 0, "items/stone_hoe.png"), - I_292_0_IRON_HOE("iron_hoe", null, 292, 0, "items/iron_hoe.png"), - I_293_0_DIAMOND_HOE("diamond_hoe", null, 293, 0, "items/diamond_hoe.png"), - I_294_0_GOLDEN_HOE("golden_hoe", null, 294, 0, "items/golden_hoe.png"), - I_295_0_WHEAT_SEEDS("wheat_seeds", null, 295, 0, "items/wheat_seeds.png"), - I_296_0_WHEAT("wheat", null, 296, 0, "items/wheat.png"), - I_297_0_BREAD("bread", null, 297, 0, "items/bread.png"), - I_298_0_LEATHER_HELMET("leather_helmet", null, 298, 0, "items/leather_helmet.png"), - I_299_0_LEATHER_CHESTPLATE("leather_chestplate", null, 299, 0, "items/leather_chestplate.png"), - I_300_0_LEATHER_LEGGINGS("leather_leggings", null, 300, 0, "items/leather_leggings.png"), - I_301_0_LEATHER_BOOTS("leather_boots", null, 301, 0, "items/leather_boots.png"), - I_302_0_CHAINMAIL_HELMET("chainmail_helmet", null, 302, 0, "items/chainmail_helmet.png"), - I_303_0_CHAINMAIL_CHESTPLATE("chainmail_chestplate", null, 303, 0, "items/chainmail_chestplate.png"), - I_304_0_CHAINMAIL_LEGGINGS("chainmail_leggings", null, 304, 0, "items/chainmail_leggings.png"), - I_305_0_CHAINMAIL_BOOTS("chainmail_boots", null, 305, 0, "items/chainmail_boots.png"), - I_306_0_IRON_HELMET("iron_helmet", null, 306, 0, "items/iron_helmet.png"), - I_307_0_IRON_CHESTPLATE("iron_chestplate", null, 307, 0, "items/iron_chestplate.png"), - I_308_0_IRON_LEGGINGS("iron_leggings", null, 308, 0, "items/iron_leggings.png"), - I_309_0_IRON_BOOTS("iron_boots", null, 309, 0, "items/iron_boots.png"), - I_310_0_DIAMOND_HELMET("diamond_helmet", null, 310, 0, "items/diamond_helmet.png"), - I_311_0_DIAMOND_CHESTPLATE("diamond_chestplate", null, 311, 0, "items/diamond_chestplate.png"), - I_312_0_DIAMOND_LEGGINGS("diamond_leggings", null, 312, 0, "items/diamond_leggings.png"), - I_313_0_DIAMOND_BOOTS("diamond_boots", null, 313, 0, "items/diamond_boots.png"), - I_314_0_GOLDEN_HELMET("golden_helmet", null, 314, 0, "items/golden_helmet.png"), - I_315_0_GOLDEN_CHESTPLATE("golden_chestplate", null, 315, 0, "items/golden_chestplate.png"), - I_316_0_GOLDEN_LEGGINGS("golden_leggings", null, 316, 0, "items/golden_leggings.png"), - I_317_0_GOLDEN_BOOTS("golden_boots", null, 317, 0, "items/golden_boots.png"), - I_318_0_FLINT("flint", null, 318, 0, "items/flint.png"), - I_319_0_PORKCHOP("porkchop", null, 319, 0, "items/porkchop.png"), - I_320_0_COOKED_PORKCHOP("cooked_porkchop", null, 320, 0, "items/cooked_porkchop.png"), - I_321_0_PAINTING("painting", null, 321, 0, "items/painting.png"), - I_322_0_GOLDEN_APPLE("golden_apple", null, 322, 0, "items/golden_apple.png"), - I_323_0_SIGN("sign", null, 323, 0, "items/sign.png"), - I_324_0_WOODEN_DOOR("wooden_door", null, 324, 0, "items/wooden_door.png"), - I_325_0_BUCKET_BUCKET("bucket", "bucket", 325, 0, "items/bucket_bucket.png"), - I_325_1_BUCKET_MILK("bucket", "milk", 325, 1, "items/bucket_milk.png"), - I_325_8_BUCKET_BUCKET_WATER("bucket", "bucket_water", 325, 8, "items/bucket_bucket_water.png"), - I_325_10_BUCKET_BUCKET_LAVA("bucket", "bucket_lava", 325, 10, "items/bucket_bucket_lava.png"), - I_328_0_MINECART("minecart", null, 328, 0, "items/minecart.png"), - I_329_0_SADDLE("saddle", null, 329, 0, "items/saddle.png"), - I_330_0_IRON_DOOR("iron_door", null, 330, 0, "items/iron_door.png"), - I_331_0_REDSTONE("redstone", null, 331, 0, "items/redstone.png"), - I_332_0_SNOWBALL("snowball", null, 332, 0, "items/snowball.png"), - I_333_0_BOAT_OAK("boat", "oak", 333, 0, "items/boat_oak.png"), - I_333_1_BOAT_SPRUCE("boat", "spruce", 333, 1, "items/boat_spruce.png"), - I_333_2_BOAT_BIRCH("boat", "birch", 333, 2, "items/boat_birch.png"), - I_333_3_BOAT_JUNGLE("boat", "jungle", 333, 3, "items/boat_jungle.png"), - I_333_4_BOAT_ACACIA("boat", "acacia", 333, 4, "items/boat_acacia.png"), - I_333_5_BOAT_BIG_OAK("boat", "big_oak", 333, 5, "items/boat_big_oak.png"), - I_334_0_LEATHER("leather", null, 334, 0, "items/leather.png"), - I_336_0_BRICK("brick", null, 336, 0, "items/brick.png"), - I_337_0_CLAY_BALL("clay_ball", null, 337, 0, "items/clay_ball.png"), - I_338_0_REEDS("reeds", null, 338, 0, "items/reeds.png"), - I_339_0_PAPER("paper", null, 339, 0, "items/paper.png"), - I_340_0_BOOK("book", null, 340, 0, "items/book.png"), - I_341_0_SLIME_BALL("slime_ball", null, 341, 0, "items/slime_ball.png"), - I_342_0_CHEST_MINECART("chest_minecart", null, 342, 0, "items/chest_minecart.png"), - I_344_0_EGG("egg", null, 344, 0, "items/egg.png"), - I_345_0_COMPASS("compass", null, 345, 0, "items/compass.png"), - I_346_0_FISHING_ROD("fishing_rod", null, 346, 0, "items/fishing_rod.png"), - I_347_0_CLOCK("clock", null, 347, 0, "items/clock.png"), - I_348_0_GLOWSTONE_DUST("glowstone_dust", null, 348, 0, "items/glowstone_dust.png"), - I_349_0_FISH("fish", null, 349, 0, "items/fish.png"), - I_350_0_COOKED_FISH("cooked_fish", null, 350, 0, "items/cooked_fish.png"), - I_351_0_DYE_BLACKINKSAC("dye", "black", 351, 0, "items/dye_powder_black.png"), - I_351_1_DYE_RED("dye", "red", 351, 1, "items/dye_powder_red.png"), - I_351_2_DYE_GREEN("dye", "green", 351, 2, "items/dye_powder_green.png"), - I_351_3_DYE_BROWNCOCOABEANS("dye", "brown", 351, 3, "items/dye_powder_brown.png"), - I_351_4_DYE_BLUE("dye", "blue", 351, 4, "items/dye_powder_blue.png"), - I_351_5_DYE_PURPLE("dye", "purple", 351, 5, "items/dye_powder_purple.png"), - I_351_6_DYE_CYAN("dye", "cyan", 351, 6, "items/dye_powder_cyan.png"), - I_351_7_DYE_SILVER("dye", "silver", 351, 7, "items/dye_powder_silver.png"), - I_351_8_DYE_GRAY("dye", "gray", 351, 8, "items/dye_powder_gray.png"), - I_351_9_DYE_PINK("dye", "pink", 351, 9, "items/dye_powder_pink.png"), - I_351_10_DYE_LIME("dye", "lime", 351, 10, "items/dye_powder_lime.png"), - I_351_11_DYE_YELLOW("dye", "yellow", 351, 11, "items/dye_powder_yellow.png"), - I_351_12_DYE_LIGHT_BLUE("dye", "light_blue", 351, 12, "items/dye_powder_light_blue.png"), - I_351_13_DYE_MAGENTA("dye", "magenta", 351, 13, "items/dye_powder_magenta.png"), - I_351_14_DYE_ORANGE("dye", "orange", 351, 14, "items/dye_powder_orange.png"), - I_351_15_DYE_WHITEBONEMEAL("dye", "white", 351, 15, "items/dye_powder_white.png"), - I_352_0_BONE("bone", null, 352, 0, "items/bone.png"), - I_353_0_SUGAR("sugar", null, 353, 0, "items/sugar.png"), - I_354_0_CAKE("cake", null, 354, 0, "items/cake.png"), - I_355_0_BED("bed", null, 355, 0, "items/bed.png"), - I_356_0_REPEATER("repeater", null, 356, 0, "items/repeater.png"), - I_357_0_COOKIE("cookie", null, 357, 0, "items/cookie.png"), - I_358_0_MAP_FILLED("map_filled", null, 358, 0, "items/map_filled.png"), - I_359_0_SHEARS("shears", null, 359, 0, "items/shears.png"), - I_360_0_MELON("melon", null, 360, 0, "items/melon.png"), - I_361_0_PUMPKIN_SEEDS("pumpkin_seeds", null, 361, 0, "items/pumpkin_seeds.png"), - I_362_0_MELON_SEEDS("melon_seeds", null, 362, 0, "items/melon_seeds.png"), - I_363_0_BEEF("beef", null, 363, 0, "items/beef.png"), - I_364_0_COOKED_BEEF("cooked_beef", null, 364, 0, "items/cooked_beef.png"), - I_365_0_CHICKEN("chicken", null, 365, 0, "items/chicken.png"), - I_366_0_COOKED_CHICKEN("cooked_chicken", null, 366, 0, "items/cooked_chicken.png"), - I_367_0_ROTTEN_FLESH("rotten_flesh", null, 367, 0, "items/rotten_flesh.png"), - I_368_0_ENDER_PEARL("ender_pearl", null, 368, 0, "items/ender_pearl.png"), - I_369_0_BLAZE_ROD("blaze_rod", null, 369, 0, "items/blaze_rod.png"), - I_370_0_GHAST_TEAR("ghast_tear", null, 370, 0, "items/ghast_tear.png"), - I_371_0_GOLD_NUGGET("gold_nugget", null, 371, 0, "items/gold_nugget.png"), - I_372_0_NETHER_WART("nether_wart", null, 372, 0, "items/nether_wart.png"), - I_373_0_POTION("potion", null, 373, 0, "items/potion.png"), - I_374_0_GLASS_BOTTLE("glass_bottle", null, 374, 0, "items/glass_bottle.png"), - I_375_0_SPIDER_EYE("spider_eye", null, 375, 0, "items/spider_eye.png"), - I_376_0_FERMENTED_SPIDER_EYE("fermented_spider_eye", null, 376, 0, "items/fermented_spider_eye.png"), - I_377_0_BLAZE_POWDER("blaze_powder", null, 377, 0, "items/blaze_powder.png"), - I_378_0_MAGMA_CREAM("magma_cream", null, 378, 0, "items/magma_cream.png"), - I_379_0_BREWING_STAND("brewing_stand", null, 379, 0, "items/brewing_stand.png"), - I_380_0_CAULDRON("cauldron", null, 380, 0, "items/cauldron.png"), - I_381_0_ENDER_EYE("ender_eye", null, 381, 0, "items/ender_eye.png"), - I_382_0_SPECKLED_MELON("speckled_melon", null, 382, 0, "items/speckled_melon.png"), - I_383_0_SPAWN_EGG("spawn_egg", null, 383, 0, "items/spawn_egg.png"), - I_384_0_EXPERIENCE_BOTTLE("experience_bottle", null, 384, 0, "items/experience_bottle.png"), - I_385_0_FIREBALL("fireball", null, 385, 0, "items/fireball.png"), - I_388_0_EMERALD("emerald", null, 388, 0, "items/emerald.png"), - I_389_0_FRAME("frame", null, 389, 0, "items/frame.png"), - I_390_0_FLOWER_POT("flower_pot", null, 390, 0, "items/flower_pot.png"), - I_391_0_CARROT("carrot", null, 391, 0, "items/carrot.png"), - I_392_0_POTATO("potato", null, 392, 0, "items/potato.png"), - I_393_0_BAKED_POTATO("baked_potato", null, 393, 0, "items/baked_potato.png"), - I_394_0_POISONOUS_POTATO("poisonous_potato", null, 394, 0, "items/poisonous_potato.png"), - I_395_0_EMPTYMAP("emptyMap", null, 395, 0, "items/emptyMap.png"), - I_396_0_GOLDEN_CARROT("golden_carrot", null, 396, 0, "items/golden_carrot.png"), - I_397_0_SKULL_SKELETON("skull", "skeleton", 397, 0, "items/skull_skeleton.png"), - I_397_1_SKULL_WITHER("skull", "wither", 397, 1, "items/skull_wither.png"), - I_397_2_SKULL_ZOMBIE("skull", "zombie", 397, 2, "items/skull_zombie.png"), - I_397_3_SKULL_PLAYER("skull", "player", 397, 3, "items/skull_player.png"), - I_397_4_SKULL_CREEPER("skull", "creeper", 397, 4, "items/skull_creeper.png"), - I_397_5_SKULL_DRAGON("skull", "dragon", 397, 5, "items/skull_dragon.png"), - I_398_0_CARROTONASTICK("carrotOnAStick", null, 398, 0, "items/carrotOnAStick.png"), - I_399_0_NETHERSTAR("netherStar", null, 399, 0, "items/netherStar.png"), - I_400_0_PUMPKIN_PIE("pumpkin_pie", null, 400, 0, "items/pumpkin_pie.png"), - I_403_0_ENCHANTED_BOOK("enchanted_book", null, 403, 0, "items/enchanted_book.png"), - I_404_0_COMPARATOR("comparator", null, 404, 0, "items/comparator.png"), - I_405_0_NETHERBRICK("netherbrick", null, 405, 0, "items/netherbrick.png"), - I_406_0_QUARTZ("quartz", null, 406, 0, "items/quartz.png"), - I_407_0_TNT_MINECART("tnt_minecart", null, 407, 0, "items/tnt_minecart.png"), - I_408_0_HOPPER_MINECART("hopper_minecart", null, 408, 0, "items/hopper_minecart.png"), - I_409_0_PRISMARINE_SHARD("prismarine_shard", null, 409, 0, "items/prismarine_shard.png"), - I_410_0_HOPPER("hopper", null, 410, 0, "items/hopper.png"), - I_411_0_RABBIT("rabbit", null, 411, 0, "items/rabbit.png"), - I_412_0_COOKED_RABBIT("cooked_rabbit", null, 412, 0, "items/cooked_rabbit.png"), - I_413_0_RABBIT_STEW("rabbit_stew", null, 413, 0, "items/rabbit_stew.png"), - I_414_0_RABBIT_FOOT("rabbit_foot", null, 414, 0, "items/rabbit_foot.png"), - I_415_0_RABBIT_HIDE("rabbit_hide", null, 415, 0, "items/rabbit_hide.png"), - I_416_0_HORSEARMORLEATHER("horsearmorleather", null, 416, 0, "items/horsearmorleather.png"), - I_417_0_HORSEARMORIRON("horsearmoriron", null, 417, 0, "items/horsearmoriron.png"), - I_418_0_HORSEARMORGOLD("horsearmorgold", null, 418, 0, "items/horsearmorgold.png"), - I_419_0_HORSEARMORDIAMOND("horsearmordiamond", null, 419, 0, "items/horsearmordiamond.png"), - I_420_0_LEAD("lead", null, 420, 0, "items/lead.png"), - I_421_0_NAMETAG("nameTag", null, 421, 0, "items/nameTag.png"), - I_422_0_PRISMARINE_CRYSTALS("prismarine_crystals", null, 422, 0, "items/prismarine_crystals.png"), - I_423_0_MUTTONRAW("muttonRaw", null, 423, 0, "items/muttonRaw.png"), - I_424_0_MUTTONCOOKED("muttonCooked", null, 424, 0, "items/muttonCooked.png"), - I_426_0_END_CRYSTAL("end_crystal", null, 426, 0, "items/end_crystal.png"), - I_427_0_SPRUCE_DOOR("spruce_door", null, 427, 0, "items/spruce_door.png"), - I_428_0_BIRCH_DOOR("birch_door", null, 428, 0, "items/birch_door.png"), - I_429_0_JUNGLE_DOOR("jungle_door", null, 429, 0, "items/jungle_door.png"), - I_430_0_ACACIA_DOOR("acacia_door", null, 430, 0, "items/acacia_door.png"), - I_431_0_DARK_OAK_DOOR("dark_oak_door", null, 431, 0, "items/dark_oak_door.png"), - I_432_0_CHORUS_FRUIT("chorus_fruit", null, 432, 0, "items/chorus_fruit.png"), - I_433_0_CHORUS_FRUIT_POPPED("chorus_fruit_popped", null, 433, 0, "items/chorus_fruit_popped.png"), - I_437_0_DRAGON_BREATH("dragon_breath", null, 437, 0, "items/dragon_breath.png"), - I_438_0_SPLASH_POTION("splash_potion", null, 438, 0, "items/splash_potion.png"), - I_441_0_LINGERING_POTION("lingering_potion", null, 441, 0, "items/lingering_potion.png"), - I_444_0_ELYTRA("elytra", null, 444, 0, "items/elytra.png"), - I_457_0_BEETROOT("beetroot", null, 457, 0, "items/beetroot.png"), - I_458_0_BEETROOT_SEEDS("beetroot_seeds", null, 458, 0, "items/seeds_beetroot.png"), - I_459_0_BEETROOT_SOUP("beetroot_soup", null, 459, 0, "items/beetroot_soup.png"), - I_460_0_SALMON("salmon", null, 460, 0, "items/fish_salmon.png"), - I_461_0_CLOWNFISH("clownfish", null, 461, 0, "items/fish_clownfish.png"), - I_462_0_PUFFERFISH("pufferfish", null, 462, 0, "items/fish_pufferfish.png"), - I_463_0_COOKED_SALMON("cooked_salmon", null, 463, 0, "items/fish_salmon_cooked.png"), - I_466_0_APPLEENCHANTED("apple_enchanted", null, 466, 0, "items/apple_golden.png"), - I_454_0_BOARD_ONE_BY_ONE("board", "one_by_one", 454, 0, "items/chalkboard_small.png"), - I_454_1_BOARD_TWO_BY_ONE("board", "two_by_one", 454, 1, "items/chalkboard_medium.png"), - I_454_2_BOARD_THREE_BY_TWO("board", "three_by_two", 454, 2, "items/chalkboard_large.png"), - I_456_0_PORTFOLIO("portfolio", null, 456, 0, "items/portfolio.png"), - I_498_0_CAMERA("camera", null, 498, 0, "items/camera.png"); - + static { + blockMap = new SparseArray<>(); + SparseArray subMap; + for (Block b : Block.values()) { + subMap = blockMap.get(b.id); + if (subMap == null) { + subMap = new SparseArray<>(); + blockMap.put(b.id, subMap); + } + subMap.put(b.subId, b); + if (b.subId == 0) byDataName.put(b.str, b); + byDataName.put(b.str + "@" + b.subName, b); + } + } public final int id, subId; - - public final String name, subName, displayName, identifier; - + public final String str, subName, displayName, identifier; public final String texPath; - public final Color color; public final boolean hasBiomeShading; - - public Bitmap bitmap; Block(String name, String subName, int id, int subId, String texPath, int color, boolean hasBiomeShading) { this.id = id; this.subId = subId; - this.name = name; + this.str = name; this.subName = subName; this.displayName = name + " " + subName; this.texPath = texPath; @@ -822,59 +608,6 @@ public enum Block implements NamedBitmapProviderHandle, NamedBitmapProvider { this.identifier = "minecraft:" + subName; } - Block(String name, String subName, int id, int subId, String texPath) { - this.id = id; - this.subId = subId; - this.name = name; - this.subName = subName; - this.displayName = name + " " + subName; - this.texPath = texPath; - this.color = null; - this.hasBiomeShading = false; - this.identifier = "minecraft:" + subName; - } - - @Override - public Bitmap getBitmap() { - return this.bitmap; - } - - @NonNull - @Override - public NamedBitmapProvider getNamedBitmapProvider() { - return this; - } - - @NonNull - @Override - public String getBitmapDisplayName() { - return this.displayName; - } - - @NonNull - @Override - public String getBitmapDataName() { - return name + "@" + subName; - } - - private static final Map byDataName = new HashMap<>(); - private static final SparseArray> blockMap; - - static { - blockMap = new SparseArray<>(); - SparseArray subMap; - for (Block b : Block.values()) { - subMap = blockMap.get(b.id); - if (subMap == null) { - subMap = new SparseArray<>(); - blockMap.put(b.id, subMap); - } - subMap.put(b.subId, b); - if (b.subId == 0) byDataName.put(b.name, b); - byDataName.put(b.name + "@" + b.subName, b); - } - } - public static Block getByDataName(String dataName) { return byDataName.get(dataName); } @@ -888,7 +621,7 @@ public static void loadBitmaps(AssetManager assetManager) throws IOException { //TODO file-paths were generated from block names; some do not actually exist... //Log.w("File not found! "+b.texPath); } catch (Exception e) { - e.printStackTrace(); + Log.d(Block.class, e); } } } @@ -901,10 +634,40 @@ public static Block getBlock(int id, int meta) { else return subMap.get(meta); } + public static Block getBlockWithLegacyId(int id) { + if (id < 0) return null; + SparseArray subMap = blockMap.get(id); + if (subMap == null) return null; + return subMap.valueAt(0); + } + public static Block getBlock(int runtimeId) { int id = runtimeId >>> 8; int data = runtimeId & 0xf; return getBlock(id, data); } + @Override + public Bitmap getBitmap() { + return this.bitmap; + } + + @NonNull + @Override + public NamedBitmapProvider getNamedBitmapProvider() { + return this; + } + + @NonNull + @Override + public String getBitmapDisplayName() { + return this.displayName; + } + + @NonNull + @Override + public String getBitmapDataName() { + return str + "@" + subName; + } + } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/Item.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/Item.java new file mode 100644 index 00000000..4cc501d7 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/Item.java @@ -0,0 +1,342 @@ +package com.mithrilmania.blocktopograph.map; + +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.annotation.NonNull; +import android.util.SparseArray; + +import com.mithrilmania.blocktopograph.Log; +import com.mithrilmania.blocktopograph.util.Color; +import com.mithrilmania.blocktopograph.util.NamedBitmapProvider; +import com.mithrilmania.blocktopograph.util.NamedBitmapProviderHandle; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public enum Item implements NamedBitmapProviderHandle, NamedBitmapProvider { + + /* + * ============================== + * Blocks + * ============================== + */ + + I_256_0_IRON_SHOVEL("iron_shovel", null, 256, 0, "items/iron_shovel.png"), + I_257_0_IRON_PICKAXE("iron_pickaxe", null, 257, 0, "items/iron_pickaxe.png"), + I_258_0_IRON_AXE("iron_axe", null, 258, 0, "items/iron_axe.png"), + I_259_0_FLINT_AND_STEEL("flint_and_steel", null, 259, 0, "items/flint_and_steel.png"), + I_260_0_APPLE("apple", null, 260, 0, "items/apple.png"), + I_261_0_BOW("bow", null, 261, 0, "items/bow.png"), + I_262_0_ARROW("arrow", null, 262, 0, "items/arrow.png"), + I_263_0_COAL_COAL("coal", "coal", 263, 0, "items/coal_coal.png"), + I_263_1_COAL_CHARCOAL("coal", "charcoal", 263, 1, "items/coal_charcoal.png"), + I_264_0_DIAMOND("diamond", null, 264, 0, "items/diamond.png"), + I_265_0_IRON_INGOT("iron_ingot", null, 265, 0, "items/iron_ingot.png"), + I_266_0_GOLD_INGOT("gold_ingot", null, 266, 0, "items/gold_ingot.png"), + I_267_0_IRON_SWORD("iron_sword", null, 267, 0, "items/iron_sword.png"), + I_268_0_WOODEN_SWORD("wooden_sword", null, 268, 0, "items/wooden_sword.png"), + I_269_0_WOODEN_SHOVEL("wooden_shovel", null, 269, 0, "items/wooden_shovel.png"), + I_270_0_WOODEN_PICKAXE("wooden_pickaxe", null, 270, 0, "items/wooden_pickaxe.png"), + I_271_0_WOODEN_AXE("wooden_axe", null, 271, 0, "items/wooden_axe.png"), + I_272_0_STONE_SWORD("stone_sword", null, 272, 0, "items/stone_sword.png"), + I_273_0_STONE_SHOVEL("stone_shovel", null, 273, 0, "items/stone_shovel.png"), + I_274_0_STONE_PICKAXE("stone_pickaxe", null, 274, 0, "items/stone_pickaxe.png"), + I_275_0_STONE_AXE("stone_axe", null, 275, 0, "items/stone_axe.png"), + I_276_0_DIAMOND_SWORD("diamond_sword", null, 276, 0, "items/diamond_sword.png"), + I_277_0_DIAMOND_SHOVEL("diamond_shovel", null, 277, 0, "items/diamond_shovel.png"), + I_278_0_DIAMOND_PICKAXE("diamond_pickaxe", null, 278, 0, "items/diamond_pickaxe.png"), + I_279_0_DIAMOND_AXE("diamond_axe", null, 279, 0, "items/diamond_axe.png"), + I_280_0_STICK("stick", null, 280, 0, "items/stick.png"), + I_281_0_BOWL("bowl", null, 281, 0, "items/bowl.png"), + I_282_0_MUSHROOM_STEW("mushroom_stew", null, 282, 0, "items/mushroom_stew.png"), + I_283_0_GOLDEN_SWORD("golden_sword", null, 283, 0, "items/golden_sword.png"), + I_284_0_GOLDEN_SHOVEL("golden_shovel", null, 284, 0, "items/golden_shovel.png"), + I_285_0_GOLDEN_PICKAXE("golden_pickaxe", null, 285, 0, "items/golden_pickaxe.png"), + I_286_0_GOLDEN_AXE("golden_axe", null, 286, 0, "items/golden_axe.png"), + I_287_0_STRING("string", null, 287, 0, "items/string.png"), + I_288_0_FEATHER("feather", null, 288, 0, "items/feather.png"), + I_289_0_GUNPOWDER("gunpowder", null, 289, 0, "items/gunpowder.png"), + I_290_0_WOODEN_HOE("wooden_hoe", null, 290, 0, "items/wooden_hoe.png"), + I_291_0_STONE_HOE("stone_hoe", null, 291, 0, "items/stone_hoe.png"), + I_292_0_IRON_HOE("iron_hoe", null, 292, 0, "items/iron_hoe.png"), + I_293_0_DIAMOND_HOE("diamond_hoe", null, 293, 0, "items/diamond_hoe.png"), + I_294_0_GOLDEN_HOE("golden_hoe", null, 294, 0, "items/golden_hoe.png"), + I_295_0_WHEAT_SEEDS("wheat_seeds", null, 295, 0, "items/wheat_seeds.png"), + I_296_0_WHEAT("wheat", null, 296, 0, "items/wheat.png"), + I_297_0_BREAD("bread", null, 297, 0, "items/bread.png"), + I_298_0_LEATHER_HELMET("leather_helmet", null, 298, 0, "items/leather_helmet.png"), + I_299_0_LEATHER_CHESTPLATE("leather_chestplate", null, 299, 0, "items/leather_chestplate.png"), + I_300_0_LEATHER_LEGGINGS("leather_leggings", null, 300, 0, "items/leather_leggings.png"), + I_301_0_LEATHER_BOOTS("leather_boots", null, 301, 0, "items/leather_boots.png"), + I_302_0_CHAINMAIL_HELMET("chainmail_helmet", null, 302, 0, "items/chainmail_helmet.png"), + I_303_0_CHAINMAIL_CHESTPLATE("chainmail_chestplate", null, 303, 0, "items/chainmail_chestplate.png"), + I_304_0_CHAINMAIL_LEGGINGS("chainmail_leggings", null, 304, 0, "items/chainmail_leggings.png"), + I_305_0_CHAINMAIL_BOOTS("chainmail_boots", null, 305, 0, "items/chainmail_boots.png"), + I_306_0_IRON_HELMET("iron_helmet", null, 306, 0, "items/iron_helmet.png"), + I_307_0_IRON_CHESTPLATE("iron_chestplate", null, 307, 0, "items/iron_chestplate.png"), + I_308_0_IRON_LEGGINGS("iron_leggings", null, 308, 0, "items/iron_leggings.png"), + I_309_0_IRON_BOOTS("iron_boots", null, 309, 0, "items/iron_boots.png"), + I_310_0_DIAMOND_HELMET("diamond_helmet", null, 310, 0, "items/diamond_helmet.png"), + I_311_0_DIAMOND_CHESTPLATE("diamond_chestplate", null, 311, 0, "items/diamond_chestplate.png"), + I_312_0_DIAMOND_LEGGINGS("diamond_leggings", null, 312, 0, "items/diamond_leggings.png"), + I_313_0_DIAMOND_BOOTS("diamond_boots", null, 313, 0, "items/diamond_boots.png"), + I_314_0_GOLDEN_HELMET("golden_helmet", null, 314, 0, "items/golden_helmet.png"), + I_315_0_GOLDEN_CHESTPLATE("golden_chestplate", null, 315, 0, "items/golden_chestplate.png"), + I_316_0_GOLDEN_LEGGINGS("golden_leggings", null, 316, 0, "items/golden_leggings.png"), + I_317_0_GOLDEN_BOOTS("golden_boots", null, 317, 0, "items/golden_boots.png"), + I_318_0_FLINT("flint", null, 318, 0, "items/flint.png"), + I_319_0_PORKCHOP("porkchop", null, 319, 0, "items/porkchop.png"), + I_320_0_COOKED_PORKCHOP("cooked_porkchop", null, 320, 0, "items/cooked_porkchop.png"), + I_321_0_PAINTING("painting", null, 321, 0, "items/painting.png"), + I_322_0_GOLDEN_APPLE("golden_apple", null, 322, 0, "items/golden_apple.png"), + I_323_0_SIGN("sign", null, 323, 0, "items/sign.png"), + I_324_0_WOODEN_DOOR("wooden_door", null, 324, 0, "items/wooden_door.png"), + I_325_0_BUCKET_BUCKET("bucket", "bucket", 325, 0, "items/bucket_bucket.png"), + I_325_1_BUCKET_MILK("bucket", "milk", 325, 1, "items/bucket_milk.png"), + I_325_8_BUCKET_BUCKET_WATER("bucket", "bucket_water", 325, 8, "items/bucket_bucket_water.png"), + I_325_10_BUCKET_BUCKET_LAVA("bucket", "bucket_lava", 325, 10, "items/bucket_bucket_lava.png"), + I_328_0_MINECART("minecart", null, 328, 0, "items/minecart.png"), + I_329_0_SADDLE("saddle", null, 329, 0, "items/saddle.png"), + I_330_0_IRON_DOOR("iron_door", null, 330, 0, "items/iron_door.png"), + I_331_0_REDSTONE("redstone", null, 331, 0, "items/redstone.png"), + I_332_0_SNOWBALL("snowball", null, 332, 0, "items/snowball.png"), + I_333_0_BOAT_OAK("boat", "oak", 333, 0, "items/boat_oak.png"), + I_333_1_BOAT_SPRUCE("boat", "spruce", 333, 1, "items/boat_spruce.png"), + I_333_2_BOAT_BIRCH("boat", "birch", 333, 2, "items/boat_birch.png"), + I_333_3_BOAT_JUNGLE("boat", "jungle", 333, 3, "items/boat_jungle.png"), + I_333_4_BOAT_ACACIA("boat", "acacia", 333, 4, "items/boat_acacia.png"), + I_333_5_BOAT_BIG_OAK("boat", "big_oak", 333, 5, "items/boat_big_oak.png"), + I_334_0_LEATHER("leather", null, 334, 0, "items/leather.png"), + I_336_0_BRICK("brick", null, 336, 0, "items/brick.png"), + I_337_0_CLAY_BALL("clay_ball", null, 337, 0, "items/clay_ball.png"), + I_338_0_REEDS("reeds", null, 338, 0, "items/reeds.png"), + I_339_0_PAPER("paper", null, 339, 0, "items/paper.png"), + I_340_0_BOOK("book", null, 340, 0, "items/book.png"), + I_341_0_SLIME_BALL("slime_ball", null, 341, 0, "items/slime_ball.png"), + I_342_0_CHEST_MINECART("chest_minecart", null, 342, 0, "items/chest_minecart.png"), + I_344_0_EGG("egg", null, 344, 0, "items/egg.png"), + I_345_0_COMPASS("compass", null, 345, 0, "items/compass.png"), + I_346_0_FISHING_ROD("fishing_rod", null, 346, 0, "items/fishing_rod.png"), + I_347_0_CLOCK("clock", null, 347, 0, "items/clock.png"), + I_348_0_GLOWSTONE_DUST("glowstone_dust", null, 348, 0, "items/glowstone_dust.png"), + I_349_0_FISH("fish", null, 349, 0, "items/fish.png"), + I_350_0_COOKED_FISH("cooked_fish", null, 350, 0, "items/cooked_fish.png"), + I_351_0_DYE_BLACKINKSAC("dye", "black", 351, 0, "items/dye_powder_black.png"), + I_351_1_DYE_RED("dye", "red", 351, 1, "items/dye_powder_red.png"), + I_351_2_DYE_GREEN("dye", "green", 351, 2, "items/dye_powder_green.png"), + I_351_3_DYE_BROWNCOCOABEANS("dye", "brown", 351, 3, "items/dye_powder_brown.png"), + I_351_4_DYE_BLUE("dye", "blue", 351, 4, "items/dye_powder_blue.png"), + I_351_5_DYE_PURPLE("dye", "purple", 351, 5, "items/dye_powder_purple.png"), + I_351_6_DYE_CYAN("dye", "cyan", 351, 6, "items/dye_powder_cyan.png"), + I_351_7_DYE_SILVER("dye", "silver", 351, 7, "items/dye_powder_silver.png"), + I_351_8_DYE_GRAY("dye", "gray", 351, 8, "items/dye_powder_gray.png"), + I_351_9_DYE_PINK("dye", "pink", 351, 9, "items/dye_powder_pink.png"), + I_351_10_DYE_LIME("dye", "lime", 351, 10, "items/dye_powder_lime.png"), + I_351_11_DYE_YELLOW("dye", "yellow", 351, 11, "items/dye_powder_yellow.png"), + I_351_12_DYE_LIGHT_BLUE("dye", "light_blue", 351, 12, "items/dye_powder_light_blue.png"), + I_351_13_DYE_MAGENTA("dye", "magenta", 351, 13, "items/dye_powder_magenta.png"), + I_351_14_DYE_ORANGE("dye", "orange", 351, 14, "items/dye_powder_orange.png"), + I_351_15_DYE_WHITEBONEMEAL("dye", "white", 351, 15, "items/dye_powder_white.png"), + I_352_0_BONE("bone", null, 352, 0, "items/bone.png"), + I_353_0_SUGAR("sugar", null, 353, 0, "items/sugar.png"), + I_354_0_CAKE("cake", null, 354, 0, "items/cake.png"), + I_355_0_BED("bed", null, 355, 0, "items/bed.png"), + I_356_0_REPEATER("repeater", null, 356, 0, "items/repeater.png"), + I_357_0_COOKIE("cookie", null, 357, 0, "items/cookie.png"), + I_358_0_MAP_FILLED("map_filled", null, 358, 0, "items/map_filled.png"), + I_359_0_SHEARS("shears", null, 359, 0, "items/shears.png"), + I_360_0_MELON("melon", null, 360, 0, "items/melon.png"), + I_361_0_PUMPKIN_SEEDS("pumpkin_seeds", null, 361, 0, "items/pumpkin_seeds.png"), + I_362_0_MELON_SEEDS("melon_seeds", null, 362, 0, "items/melon_seeds.png"), + I_363_0_BEEF("beef", null, 363, 0, "items/beef.png"), + I_364_0_COOKED_BEEF("cooked_beef", null, 364, 0, "items/cooked_beef.png"), + I_365_0_CHICKEN("chicken", null, 365, 0, "items/chicken.png"), + I_366_0_COOKED_CHICKEN("cooked_chicken", null, 366, 0, "items/cooked_chicken.png"), + I_367_0_ROTTEN_FLESH("rotten_flesh", null, 367, 0, "items/rotten_flesh.png"), + I_368_0_ENDER_PEARL("ender_pearl", null, 368, 0, "items/ender_pearl.png"), + I_369_0_BLAZE_ROD("blaze_rod", null, 369, 0, "items/blaze_rod.png"), + I_370_0_GHAST_TEAR("ghast_tear", null, 370, 0, "items/ghast_tear.png"), + I_371_0_GOLD_NUGGET("gold_nugget", null, 371, 0, "items/gold_nugget.png"), + I_372_0_NETHER_WART("nether_wart", null, 372, 0, "items/nether_wart.png"), + I_373_0_POTION("potion", null, 373, 0, "items/potion.png"), + I_374_0_GLASS_BOTTLE("glass_bottle", null, 374, 0, "items/glass_bottle.png"), + I_375_0_SPIDER_EYE("spider_eye", null, 375, 0, "items/spider_eye.png"), + I_376_0_FERMENTED_SPIDER_EYE("fermented_spider_eye", null, 376, 0, "items/fermented_spider_eye.png"), + I_377_0_BLAZE_POWDER("blaze_powder", null, 377, 0, "items/blaze_powder.png"), + I_378_0_MAGMA_CREAM("magma_cream", null, 378, 0, "items/magma_cream.png"), + I_379_0_BREWING_STAND("brewing_stand", null, 379, 0, "items/brewing_stand.png"), + I_380_0_CAULDRON("cauldron", null, 380, 0, "items/cauldron.png"), + I_381_0_ENDER_EYE("ender_eye", null, 381, 0, "items/ender_eye.png"), + I_382_0_SPECKLED_MELON("speckled_melon", null, 382, 0, "items/speckled_melon.png"), + I_383_0_SPAWN_EGG("spawn_egg", null, 383, 0, "items/spawn_egg.png"), + I_384_0_EXPERIENCE_BOTTLE("experience_bottle", null, 384, 0, "items/experience_bottle.png"), + I_385_0_FIREBALL("fireball", null, 385, 0, "items/fireball.png"), + I_388_0_EMERALD("emerald", null, 388, 0, "items/emerald.png"), + I_389_0_FRAME("frame", null, 389, 0, "items/frame.png"), + I_390_0_FLOWER_POT("flower_pot", null, 390, 0, "items/flower_pot.png"), + I_391_0_CARROT("carrot", null, 391, 0, "items/carrot.png"), + I_392_0_POTATO("potato", null, 392, 0, "items/potato.png"), + I_393_0_BAKED_POTATO("baked_potato", null, 393, 0, "items/baked_potato.png"), + I_394_0_POISONOUS_POTATO("poisonous_potato", null, 394, 0, "items/poisonous_potato.png"), + I_395_0_EMPTYMAP("emptyMap", null, 395, 0, "items/emptyMap.png"), + I_396_0_GOLDEN_CARROT("golden_carrot", null, 396, 0, "items/golden_carrot.png"), + I_397_0_SKULL_SKELETON("skull", "skeleton", 397, 0, "items/skull_skeleton.png"), + I_397_1_SKULL_WITHER("skull", "wither", 397, 1, "items/skull_wither.png"), + I_397_2_SKULL_ZOMBIE("skull", "zombie", 397, 2, "items/skull_zombie.png"), + I_397_3_SKULL_PLAYER("skull", "player", 397, 3, "items/skull_player.png"), + I_397_4_SKULL_CREEPER("skull", "creeper", 397, 4, "items/skull_creeper.png"), + I_397_5_SKULL_DRAGON("skull", "dragon", 397, 5, "items/skull_dragon.png"), + I_398_0_CARROTONASTICK("carrotOnAStick", null, 398, 0, "items/carrotOnAStick.png"), + I_399_0_NETHERSTAR("netherStar", null, 399, 0, "items/netherStar.png"), + I_400_0_PUMPKIN_PIE("pumpkin_pie", null, 400, 0, "items/pumpkin_pie.png"), + I_403_0_ENCHANTED_BOOK("enchanted_book", null, 403, 0, "items/enchanted_book.png"), + I_404_0_COMPARATOR("comparator", null, 404, 0, "items/comparator.png"), + I_405_0_NETHERBRICK("netherbrick", null, 405, 0, "items/netherbrick.png"), + I_406_0_QUARTZ("quartz", null, 406, 0, "items/quartz.png"), + I_407_0_TNT_MINECART("tnt_minecart", null, 407, 0, "items/tnt_minecart.png"), + I_408_0_HOPPER_MINECART("hopper_minecart", null, 408, 0, "items/hopper_minecart.png"), + I_409_0_PRISMARINE_SHARD("prismarine_shard", null, 409, 0, "items/prismarine_shard.png"), + I_410_0_HOPPER("hopper", null, 410, 0, "items/hopper.png"), + I_411_0_RABBIT("rabbit", null, 411, 0, "items/rabbit.png"), + I_412_0_COOKED_RABBIT("cooked_rabbit", null, 412, 0, "items/cooked_rabbit.png"), + I_413_0_RABBIT_STEW("rabbit_stew", null, 413, 0, "items/rabbit_stew.png"), + I_414_0_RABBIT_FOOT("rabbit_foot", null, 414, 0, "items/rabbit_foot.png"), + I_415_0_RABBIT_HIDE("rabbit_hide", null, 415, 0, "items/rabbit_hide.png"), + I_416_0_HORSEARMORLEATHER("horsearmorleather", null, 416, 0, "items/horsearmorleather.png"), + I_417_0_HORSEARMORIRON("horsearmoriron", null, 417, 0, "items/horsearmoriron.png"), + I_418_0_HORSEARMORGOLD("horsearmorgold", null, 418, 0, "items/horsearmorgold.png"), + I_419_0_HORSEARMORDIAMOND("horsearmordiamond", null, 419, 0, "items/horsearmordiamond.png"), + I_420_0_LEAD("lead", null, 420, 0, "items/lead.png"), + I_421_0_NAMETAG("nameTag", null, 421, 0, "items/nameTag.png"), + I_422_0_PRISMARINE_CRYSTALS("prismarine_crystals", null, 422, 0, "items/prismarine_crystals.png"), + I_423_0_MUTTONRAW("muttonRaw", null, 423, 0, "items/muttonRaw.png"), + I_424_0_MUTTONCOOKED("muttonCooked", null, 424, 0, "items/muttonCooked.png"), + I_426_0_END_CRYSTAL("end_crystal", null, 426, 0, "items/end_crystal.png"), + I_427_0_SPRUCE_DOOR("spruce_door", null, 427, 0, "items/spruce_door.png"), + I_428_0_BIRCH_DOOR("birch_door", null, 428, 0, "items/birch_door.png"), + I_429_0_JUNGLE_DOOR("jungle_door", null, 429, 0, "items/jungle_door.png"), + I_430_0_ACACIA_DOOR("acacia_door", null, 430, 0, "items/acacia_door.png"), + I_431_0_DARK_OAK_DOOR("dark_oak_door", null, 431, 0, "items/dark_oak_door.png"), + I_432_0_CHORUS_FRUIT("chorus_fruit", null, 432, 0, "items/chorus_fruit.png"), + I_433_0_CHORUS_FRUIT_POPPED("chorus_fruit_popped", null, 433, 0, "items/chorus_fruit_popped.png"), + I_437_0_DRAGON_BREATH("dragon_breath", null, 437, 0, "items/dragon_breath.png"), + I_438_0_SPLASH_POTION("splash_potion", null, 438, 0, "items/splash_potion.png"), + I_441_0_LINGERING_POTION("lingering_potion", null, 441, 0, "items/lingering_potion.png"), + I_444_0_ELYTRA("elytra", null, 444, 0, "items/elytra.png"), + I_457_0_BEETROOT("beetroot", null, 457, 0, "items/beetroot.png"), + I_458_0_BEETROOT_SEEDS("beetroot_seeds", null, 458, 0, "items/seeds_beetroot.png"), + I_459_0_BEETROOT_SOUP("beetroot_soup", null, 459, 0, "items/beetroot_soup.png"), + I_460_0_SALMON("salmon", null, 460, 0, "items/fish_salmon.png"), + I_461_0_CLOWNFISH("clownfish", null, 461, 0, "items/fish_clownfish.png"), + I_462_0_PUFFERFISH("pufferfish", null, 462, 0, "items/fish_pufferfish.png"), + I_463_0_COOKED_SALMON("cooked_salmon", null, 463, 0, "items/fish_salmon_cooked.png"), + I_466_0_APPLEENCHANTED("apple_enchanted", null, 466, 0, "items/apple_golden.png"), + I_454_0_BOARD_ONE_BY_ONE("board", "one_by_one", 454, 0, "items/chalkboard_small.png"), + I_454_1_BOARD_TWO_BY_ONE("board", "two_by_one", 454, 1, "items/chalkboard_medium.png"), + I_454_2_BOARD_THREE_BY_TWO("board", "three_by_two", 454, 2, "items/chalkboard_large.png"), + I_456_0_PORTFOLIO("portfolio", null, 456, 0, "items/portfolio.png"), + I_498_0_CAMERA("camera", null, 498, 0, "items/camera.png"); + + private static final Map byDataName = new HashMap<>(); + private static final SparseArray> itemMap; + + static { + itemMap = new SparseArray<>(); + SparseArray subMap; + for (Item b : Item.values()) { + subMap = itemMap.get(b.id); + if (subMap == null) { + subMap = new SparseArray<>(); + itemMap.put(b.id, subMap); + } + subMap.put(b.subId, b); + if (b.subId == 0) byDataName.put(b.str, b); + byDataName.put(b.str + "@" + b.subName, b); + } + } + + public final int id, subId; + public final String str, subName, displayName, identifier; + public final String texPath; + public final Color color; + public final boolean hasBiomeShading; + public Bitmap bitmap; + + Item(String name, String subName, int id, int subId, String texPath) { + this.id = id; + this.subId = subId; + this.str = name; + this.subName = subName; + this.displayName = name + " " + subName; + this.texPath = texPath; + this.color = null; + this.hasBiomeShading = false; + this.identifier = "minecraft:" + subName; + } + + public static Item getByDataName(String dataName) { + return byDataName.get(dataName); + } + + public static void loadBitmaps(AssetManager assetManager) throws IOException { + for (Item b : Item.values()) { + if (b.bitmap == null && b.texPath != null) { + try { + b.bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeStream(assetManager.open(b.texPath)), 32, 32, false); + } catch (FileNotFoundException e) { + //TODO file-paths were generated from item names; some do not actually exist... + //Log.w("File not found! "+b.texPath); + } catch (Exception e) { + Log.d(Item.class, e); + } + } + } + } + + public static Item getItem(int id, int meta) { + if (id < 0) return null; + SparseArray subMap = itemMap.get(id); + if (subMap == null) return null; + else return subMap.get(meta); + } + + public static Item getItemWithLegacyId(int id) { + if (id < 0) return null; + SparseArray subMap = itemMap.get(id); + if (subMap == null) return null; + return subMap.valueAt(0); + } + + public static Item getItem(int runtimeId) { + int id = runtimeId >>> 8; + int data = runtimeId & 0xf; + return getItem(id, data); + } + + @Override + public Bitmap getBitmap() { + return this.bitmap; + } + + @NonNull + @Override + public NamedBitmapProvider getNamedBitmapProvider() { + return this; + } + + @NonNull + @Override + public String getBitmapDisplayName() { + return this.displayName; + } + + @NonNull + @Override + public String getBitmapDataName() { + return str + "@" + subName; + } + +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java index 9bef8ef1..ea7bab49 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MapFragment.java @@ -31,6 +31,7 @@ import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.R; import com.mithrilmania.blocktopograph.World; +import com.mithrilmania.blocktopograph.WorldActivity; import com.mithrilmania.blocktopograph.WorldActivityInterface; import com.mithrilmania.blocktopograph.WorldData; import com.mithrilmania.blocktopograph.chunk.Chunk; @@ -174,8 +175,7 @@ public DimensionVector3 getMultiPlayerPos(String dbKey) throws Exception dimension); } catch (Exception e) { - Log.e(e.getMessage()); - + Log.d(this, e); Exception e2 = new Exception("Could not find " + dbKey); e2.setStackTrace(e.getStackTrace()); throw e2; @@ -191,7 +191,7 @@ public DimensionVector3 getPlayerPos() throws Exception { final CompoundTag player = data != null ? (CompoundTag) DataConverter.read(data).get(0) - : (CompoundTag) worldProvider.getWorld().level.getChildTagByKey("Player"); + : (CompoundTag) worldProvider.getWorld().getLevel().getChildTagByKey("Player"); ListTag posVec = (ListTag) player.getChildTagByKey("Pos"); if (posVec == null || posVec.getValue() == null) @@ -212,19 +212,16 @@ public DimensionVector3 getPlayerPos() throws Exception { dimension); } catch (Exception e) { - Log.e(e.toString()); - - ///Meow - return new DimensionVector3<>(0f, 64f, 0f, Dimension.OVERWORLD); -// Exception e2 = new Exception("Could not find player."); -// e2.setStackTrace(e.getStackTrace()); -// throw e2; + Log.d(this, e); + Exception e2 = new Exception("Could not find player."); + e2.setStackTrace(e.getStackTrace()); + throw e2; } } public DimensionVector3 getSpawnPos() throws Exception { try { - CompoundTag level = this.worldProvider.get().getWorld().level; + CompoundTag level = this.worldProvider.get().getWorld().getLevel(); int spawnX = ((IntTag) level.getChildTagByKey("SpawnX")).getValue(); int spawnY = ((IntTag) level.getChildTagByKey("SpawnY")).getValue(); int spawnZ = ((IntTag) level.getChildTagByKey("SpawnZ")).getValue(); @@ -235,9 +232,8 @@ public DimensionVector3 getSpawnPos() throws Exception { } return new DimensionVector3<>(spawnX, spawnY, spawnZ, Dimension.OVERWORLD); } catch (Exception e) { - ///Meow - return new DimensionVector3<>(0, 64, 0, Dimension.OVERWORLD); - //throw new Exception("Could not find spawn"); + Log.d(this, e); + throw new Exception("Could not find spawn"); } } @@ -259,13 +255,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View rootView = inflater.inflate(R.layout.map_fragment, container, false); - RelativeLayout worldContainer = (RelativeLayout) rootView.findViewById(R.id.world_tileview_container); + RelativeLayout worldContainer = rootView.findViewById(R.id.world_tileview_container); assert worldContainer != null; /* GPS button: moves camera to player position */ - FloatingActionButton fabGPSPlayer = (FloatingActionButton) rootView.findViewById(R.id.fab_menu_gps_player); + FloatingActionButton fabGPSPlayer = rootView.findViewById(R.id.fab_menu_gps_player); assert fabGPSPlayer != null; fabGPSPlayer.setOnClickListener(new View.OnClickListener() { @Override @@ -301,7 +297,7 @@ public void onClick(View view) { /* GPS button: moves camera to spawn */ - FloatingActionButton fabGPSSpawn = (FloatingActionButton) rootView.findViewById(R.id.fab_menu_gps_spawn); + FloatingActionButton fabGPSSpawn = rootView.findViewById(R.id.fab_menu_gps_spawn); assert fabGPSSpawn != null; fabGPSSpawn.setOnClickListener(new View.OnClickListener() { @Override @@ -349,7 +345,7 @@ public void onClick(View view) { //show the toolbar if the fab menu is opened - FloatingActionMenu fabMenu = (FloatingActionMenu) rootView.findViewById(R.id.fab_menu); + FloatingActionMenu fabMenu = rootView.findViewById(R.id.fab_menu); fabMenu.setOnMenuToggleListener(new FloatingActionMenu.OnMenuToggleListener() { @Override public void onMenuToggle(boolean opened) { @@ -360,7 +356,7 @@ public void onMenuToggle(boolean opened) { }); - FloatingActionButton fabGPSMarker = (FloatingActionButton) rootView.findViewById(R.id.fab_menu_gps_marker); + FloatingActionButton fabGPSMarker = rootView.findViewById(R.id.fab_menu_gps_marker); assert fabGPSMarker != null; fabGPSMarker.setOnClickListener(new View.OnClickListener() { @Override @@ -429,7 +425,7 @@ public void onClick(DialogInterface dialog, int which) { /* GPS button: moves camera to player position */ - FloatingActionButton fabGPSMultiplayer = (FloatingActionButton) rootView.findViewById(R.id.fab_menu_gps_multiplayer); + FloatingActionButton fabGPSMultiplayer = rootView.findViewById(R.id.fab_menu_gps_multiplayer); assert fabGPSMultiplayer != null; fabGPSMultiplayer.setOnClickListener(new View.OnClickListener() { @Override @@ -456,7 +452,7 @@ public void onClick(final View view) { /* GPS button: moves camera to player position */ - FloatingActionButton fabGPSCoord = (FloatingActionButton) rootView.findViewById(R.id.fab_menu_gps_coord); + FloatingActionButton fabGPSCoord = rootView.findViewById(R.id.fab_menu_gps_coord); assert fabGPSCoord != null; fabGPSCoord.setOnClickListener(new View.OnClickListener() { @Override @@ -465,9 +461,9 @@ public void onClick(View view) { if (tileView == null) throw new Exception("No map available."); View xzForm = LayoutInflater.from(activity).inflate(R.layout.xz_coord_form, null); - final EditText xInput = (EditText) xzForm.findViewById(R.id.x_input); + final EditText xInput = xzForm.findViewById(R.id.x_input); xInput.setText("0"); - final EditText zInput = (EditText) xzForm.findViewById(R.id.z_input); + final EditText zInput = xzForm.findViewById(R.id.z_input); zInput.setText("0"); //wrap layout in alert @@ -569,7 +565,10 @@ public void onLongPress(MotionEvent event) { /* Create tile(=bitmap) provider */ - this.mChunkManager = new ChunkManager(worldProvider.get().getWorld().getWorldData()); + this.mChunkManager = new ChunkManager( + ((WorldActivity) activity) //Can't be null, otherwise returned. + .getWorld() //Can't be null, otherwise finished & returned. + .getWorldData()); //NonNull, no warning. this.minecraftTileProvider = new MCTileProvider(worldProvider.get(), mChunkManager); @@ -604,7 +603,6 @@ Create tile(=bitmap) provider this.tileView.addDetailLevel(0.25f, "0.25", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); this.tileView.addDetailLevel(0.5f, "0.5", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType); this.tileView.addDetailLevel(1f, "1", MCTileProvider.TILESIZE, MCTileProvider.TILESIZE, mapType);// 1/1=1 chunk per tile - } this.tileView.setScale(0.5f); @@ -617,7 +615,7 @@ Create tile(=bitmap) provider DimensionVector3 playerPos = getPlayerPos(); float x = playerPos.x, y = playerPos.y, z = playerPos.z; - Log.d("Placed player marker at: " + x + ";" + y + ";" + z + " [" + playerPos.dimension.name + "]"); + Log.d(this, "Placed player marker at: " + x + ";" + y + ";" + z + " [" + playerPos.dimension.name + "]"); localPlayerMarker = new AbstractMarker((int) x, (int) y, (int) z, playerPos.dimension, new CustomNamedBitmapProvider(Entity.PLAYER, "~local_player"), false); this.staticMarkers.add(localPlayerMarker); @@ -633,7 +631,7 @@ Create tile(=bitmap) provider } catch (Exception e) { e.printStackTrace(); - Log.d("Failed to place player marker. " + e.toString()); + Log.d(this, "Failed to place player marker. " + e.toString()); } @@ -666,13 +664,13 @@ Create tile(=bitmap) provider @Override public void onMarkerTap(View view, int tapX, int tapY) { if (!(view instanceof MarkerImageView)) { - Log.d("Markertaplistener found a marker that is not a MarkerImageView! " + view.toString()); + Log.d(this, "Markertaplistener found a marker that is not a MarkerImageView! " + view.toString()); return; } final AbstractMarker marker = ((MarkerImageView) view).getMarkerHook(); if (marker == null) { - Log.d("abstract marker is null! " + view.toString()); + Log.d(this, "abstract marker is null! " + view.toString()); return; } @@ -739,7 +737,7 @@ public void onClick(DialogInterface dialog, int which) { } else throw new Exception("Failed saving player"); } catch (Exception e) { - Log.w(e.toString()); + Log.d(this, e.toString()); Snackbar.make(tileView, R.string.failed_teleporting_player, Snackbar.LENGTH_LONG) @@ -944,7 +942,7 @@ public void onLongClick(final double worldX, final double worldZ) { final View container = activity.findViewById(R.id.world_content); if (container == null) { - Log.w("CANNOT FIND MAIN CONTAINER, WTF"); + Log.d(this, "CANNOT FIND MAIN CONTAINER, WTF"); return; } @@ -990,7 +988,7 @@ public void onClick(DialogInterface dialog, int which) { View yForm = LayoutInflater.from(activity).inflate(R.layout.y_coord_form, null); - final EditText yInput = (EditText) yForm.findViewById(R.id.y_input); + final EditText yInput = yForm.findViewById(R.id.y_input); yInput.setText(String.valueOf(playerY)); final float newX = (float) worldX; @@ -1147,8 +1145,6 @@ public void onClick(DialogInterface dialog, int which) { case ENTITY: case TILE_ENTITY: { - final World world = MapFragment.this.worldProvider.get().getWorld(); - ; final Chunk chunk = mChunkManager.getChunk(chunkXint, chunkZint, dim); if (!chunkDataNBT(chunk, chosen == LongClickOption.ENTITY)) { @@ -1207,8 +1203,7 @@ public void openMarkerFilter() { final Activity activity = this.getActivity(); - final List choices = new ArrayList<>(); - choices.addAll(markerFilter.values()); + final List choices = new ArrayList<>(markerFilter.values()); //sort on names, nice for the user. Collections.sort(choices, new Comparator() { @@ -1413,9 +1408,9 @@ public View getView(final int position, View v, @NonNull ViewGroup parent) { .inflate(R.layout.img_name_check_list_entry, parent, false); - ImageView img = (ImageView) v.findViewById(R.id.entry_img); - TextView text = (TextView) v.findViewById(R.id.entry_text); - final CheckBox check = (CheckBox) v.findViewById(R.id.entry_check); + ImageView img = v.findViewById(R.id.entry_img); + TextView text = v.findViewById(R.id.entry_text); + final CheckBox check = v.findViewById(R.id.entry_check); img.setImageBitmap(m.namedBitmap.getNamedBitmapProvider().getBitmap()); text.setText(m.namedBitmap.getNamedBitmapProvider().getBitmapDisplayName()); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java index 33c87a07..1db5144f 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerAsyncTask.java @@ -98,9 +98,7 @@ private void loadEntityMarkers(int chunkX, int chunkZ) { } } catch (Exception e) { - //TODO: e.getMessage can be null - //String msg=e.getMessage(); - //String log="MarkerAsyncTask.loadEntityMarkers)Log.w(); + Log.d(this, e); } } @@ -132,7 +130,7 @@ private void loadTileEntityMarkers(int chunkX, int chunkZ) { } } catch (Exception e) { - Log.w(e.getMessage()); + Log.d(this, e); } } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java index 9383241c..6617d603 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/MarkerManager.java @@ -134,7 +134,7 @@ private synchronized Set loadFromFile() { } } catch (Exception e) { //ok, probably a comment or something, just ignore - Log.d("Invalid line in marker file: " + line); + Log.d(this, "Invalid line in marker file: " + line); } } br.close(); @@ -165,7 +165,8 @@ public void run() { private synchronized void saveToFile() { try { - if (markerFile.createNewFile()) Log.d("Created " + this.markerFile.getAbsolutePath()); + if (markerFile.createNewFile()) + Log.d(this, "Created " + this.markerFile.getAbsolutePath()); //append to file PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(this.markerFile, false))); diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java index 4d5fb213..2395bcc8 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/map/renderer/SatelliteRenderer.java @@ -83,7 +83,7 @@ public static int getColumnColour(Chunk chunk, int x, int y, int z, int heightW, //TODO log null blocks to debug missing blocks if (block == null) { - Log.w("UNKNOWN block: id: " + id); + Log.d(SatelliteRenderer.class, "UNKNOWN block: id: " + id); continue; } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java index 0247662e..d33deb43 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/nbt/EditorFragment.java @@ -7,37 +7,39 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.text.Editable; -import android.text.Layout; import android.text.TextWatcher; - -import com.mithrilmania.blocktopograph.Log; - -import android.util.DisplayMetrics; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.WindowManager; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; +import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.R; -import com.mithrilmania.blocktopograph.WorldActivity; import com.mithrilmania.blocktopograph.WorldActivityInterface; import com.mithrilmania.blocktopograph.nbt.convert.NBTConstants; -import com.mithrilmania.blocktopograph.nbt.tags.*; -import com.unnamed.b.atv.holder.SimpleViewHolder; +import com.mithrilmania.blocktopograph.nbt.tags.ByteTag; +import com.mithrilmania.blocktopograph.nbt.tags.CompoundTag; +import com.mithrilmania.blocktopograph.nbt.tags.DoubleTag; +import com.mithrilmania.blocktopograph.nbt.tags.FloatTag; +import com.mithrilmania.blocktopograph.nbt.tags.IntTag; +import com.mithrilmania.blocktopograph.nbt.tags.ListTag; +import com.mithrilmania.blocktopograph.nbt.tags.LongTag; +import com.mithrilmania.blocktopograph.nbt.tags.ShortTag; +import com.mithrilmania.blocktopograph.nbt.tags.StringTag; +import com.mithrilmania.blocktopograph.nbt.tags.Tag; import com.unnamed.b.atv.model.TreeNode; import com.unnamed.b.atv.view.AndroidTreeView; @@ -59,7 +61,7 @@ public class EditorFragment extends Fragment { private EditableNBT nbt; - public void setEditableNBT(EditableNBT nbt) { + public void setNbt(@NonNull EditableNBT nbt) { this.nbt = nbt; } @@ -86,7 +88,7 @@ public View createNodeView(TreeNode node, EditableNBT value) { final LayoutInflater inflater = LayoutInflater.from(context); final View tagView = inflater.inflate(R.layout.tag_root_layout, null, false); - TextView tagName = (TextView) tagView.findViewById(R.id.tag_name); + TextView tagName = tagView.findViewById(R.id.tag_name); tagName.setText(value.getRootTitle()); return tagView; @@ -187,12 +189,12 @@ public View createNodeView(TreeNode node, final ChainTag chain) { } final View tagView = inflater.inflate(layoutID, null, false); - TextView tagName = (TextView) tagView.findViewById(R.id.tag_name); + TextView tagName = tagView.findViewById(R.id.tag_name); tagName.setText(tag.getName()); switch (layoutID) { case R.layout.tag_boolean_layout: { - final CheckBox checkBox = (CheckBox) tagView.findViewById(R.id.checkBox); + final CheckBox checkBox = tagView.findViewById(R.id.checkBox); final ByteTag byteTag = (ByteTag) tag; checkBox.setChecked(byteTag.getValue() == (byte) 1); checkBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { @@ -211,7 +213,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { break; } case R.layout.tag_byte_layout: { - final EditText editText = (EditText) tagView.findViewById(R.id.byteField); + final EditText editText = tagView.findViewById(R.id.byteField); final ByteTag byteTag = (ByteTag) tag; //parse the byte as an unsigned byte editText.setText("" + (((int) byteTag.getValue()) & 0xFF)); @@ -241,7 +243,7 @@ public void afterTextChanged(Editable s) { break; } case R.layout.tag_short_layout: { - final EditText editText = (EditText) tagView.findViewById(R.id.shortField); + final EditText editText = tagView.findViewById(R.id.shortField); final ShortTag shortTag = (ShortTag) tag; editText.setText(shortTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @@ -267,7 +269,7 @@ public void afterTextChanged(Editable s) { break; } case R.layout.tag_int_layout: { - final EditText editText = (EditText) tagView.findViewById(R.id.intField); + final EditText editText = tagView.findViewById(R.id.intField); final IntTag intTag = (IntTag) tag; editText.setText(intTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @@ -293,7 +295,7 @@ public void afterTextChanged(Editable s) { break; } case R.layout.tag_long_layout: { - final EditText editText = (EditText) tagView.findViewById(R.id.longField); + final EditText editText = tagView.findViewById(R.id.longField); final LongTag longTag = (LongTag) tag; editText.setText(longTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @@ -319,7 +321,7 @@ public void afterTextChanged(Editable s) { break; } case R.layout.tag_float_layout: { - final EditText editText = (EditText) tagView.findViewById(R.id.floatField); + final EditText editText = tagView.findViewById(R.id.floatField); final FloatTag floatTag = (FloatTag) tag; editText.setText(floatTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @@ -345,7 +347,7 @@ public void afterTextChanged(Editable s) { break; } case R.layout.tag_double_layout: { - final EditText editText = (EditText) tagView.findViewById(R.id.doubleField); + final EditText editText = tagView.findViewById(R.id.doubleField); final DoubleTag doubleTag = (DoubleTag) tag; editText.setText(doubleTag.getValue().toString()); editText.addTextChangedListener(new TextWatcher() { @@ -371,7 +373,7 @@ public void afterTextChanged(Editable s) { break; } case R.layout.tag_string_layout: { - final EditText editText = (EditText) tagView.findViewById(R.id.stringField); + final EditText editText = tagView.findViewById(R.id.stringField); final StringTag stringTag = (StringTag) tag; editText.setText(stringTag.getValue()); editText.addTextChangedListener(new TextWatcher() { @@ -486,10 +488,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, if (nbt == null) { - new Exception("No NBT data provided").printStackTrace(); - getActivity().finish(); - - return null; + Log.e(this, "No NBT data provided"); + if (getActivity() == null) return null; + //What are you doing! + TextView textView = new TextView(getActivity()); + textView.setText("Cannot load data. Close me please."); + return textView; } final View rootView = inflater.inflate(R.layout.nbt_editor, container, false); @@ -512,7 +516,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, root.addChild(new TreeNode(new ChainTag(null, tag)).setViewHolder(new NBTNodeHolder(nbt, activity))); } - FrameLayout frame = (FrameLayout) rootView.findViewById(R.id.nbt_editor_frame); + FrameLayout frame = rootView.findViewById(R.id.nbt_editor_frame); final AndroidTreeView tree = new AndroidTreeView(getActivity(), superRoot); tree.setUse2dScroll(true); @@ -525,7 +529,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, @Override public boolean onLongClick(final TreeNode node, final Object value) { - Log.d("NBT editor: Long click!"); + Log.d(this, "NBT editor: Long click!"); //root tag has nbt as value @@ -605,7 +609,7 @@ public void onClick(DialogInterface dialog, int whichButton) { //or alert is cancelled alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - Log.d("NBT tag creation cancelled"); + Log.d(this, "NBT tag creation cancelled"); } }); @@ -642,7 +646,7 @@ public void onClick(DialogInterface dialog, int whichButton) { for (TreeNode child : children) { tree.removeNode(child); Object childValue = child.getValue(); - if (childValue != null && childValue instanceof ChainTag) + if (childValue instanceof ChainTag) nbt.removeRootTag(((ChainTag) childValue).self); } nbt.setModified(); @@ -653,7 +657,7 @@ public void onClick(DialogInterface dialog, int whichButton) { //or alert is cancelled alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - Log.d("NBT tag creation cancelled"); + Log.d(this, "NBT tag creation cancelled"); } }); @@ -662,7 +666,7 @@ public void onClick(DialogInterface dialog, int whichButton) { break; } default: { - Log.d("User clicked unknown NBTEditOption! " + option.name()); + Log.d(this, "User clicked unknown NBTEditOption! " + option.name()); } } } catch (Exception e) { @@ -840,8 +844,7 @@ public void onClick(DialogInterface dialog, int whichButton) { Editable newNameEditable = edittext.getText(); String newName = (newNameEditable == null || newNameEditable.toString().equals("")) ? null : newNameEditable.toString(); - if (parent != null - && parent instanceof CompoundTag + if (parent instanceof CompoundTag && checkKeyCollision(newName, ((CompoundTag) parent).getValue())) { showMsg(R.string.error_parent_already_contains_child_with_same_key); return; @@ -859,7 +862,7 @@ && checkKeyCollision(newName, ((CompoundTag) parent).getValue())) { alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - Log.d("Cancelled rename NBT tag"); + Log.d(this, "Cancelled rename NBT tag"); } }); @@ -944,7 +947,7 @@ public void onClick(DialogInterface dialog, int whichButton) { //or alert is cancelled alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - Log.d("NBT tag creation cancelled"); + Log.d(this, "NBT tag creation cancelled"); } }); @@ -960,7 +963,7 @@ public void onClick(DialogInterface dialog, int whichButton) { } } default: { - Log.d("User clicked unknown NBTEditOption! " + editOption.name()); + Log.d(this, "User clicked unknown NBTEditOption! " + editOption.name()); } } @@ -983,7 +986,7 @@ public void onClick(DialogInterface dialog, int whichButton) { // save functionality // ================================ - FloatingActionButton fabSaveNBT = (FloatingActionButton) rootView.findViewById(R.id.fab_save_nbt); + FloatingActionButton fabSaveNBT = rootView.findViewById(R.id.fab_save_nbt); assert fabSaveNBT != null; fabSaveNBT.setOnClickListener(new View.OnClickListener() { @Override diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/util/UiUtil.java b/app/src/main/java/com/mithrilmania/blocktopograph/util/UiUtil.java new file mode 100644 index 00000000..0ae1f0c4 --- /dev/null +++ b/app/src/main/java/com/mithrilmania/blocktopograph/util/UiUtil.java @@ -0,0 +1,52 @@ +package com.mithrilmania.blocktopograph.util; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.support.annotation.NonNull; +import android.support.design.widget.Snackbar; +import android.support.v4.graphics.ColorUtils; +import android.support.v7.app.AlertDialog; +import android.view.View; +import android.widget.Toast; + +import com.mithrilmania.blocktopograph.R; +import com.mithrilmania.blocktopograph.map.Block; + +public final class UiUtil { + + public static void toastError(@NonNull Context context) { + Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show(); + } + + public static void toast(@NonNull Context context, @NonNull String text) { + Toast.makeText(context, text, Toast.LENGTH_SHORT).show(); + } + + public static void snackError(@NonNull View view) { + Snackbar.make(view, R.string.error_general, Snackbar.LENGTH_SHORT).show(); + } + + public static void snack(@NonNull View view, @NonNull String text) { + Snackbar.make(view, text, Snackbar.LENGTH_SHORT).show(); + } + + @NonNull + public static AlertDialog buildWaitDialog(@NonNull Context context) { + AlertDialog dialog = new AlertDialog.Builder(context) + .setView(R.layout.general_wait) + .setCancelable(false) + .create(); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + + public static void blendBlockColor(View view, Block block) { + Drawable drawable = view.getBackground(); + if (!(drawable instanceof GradientDrawable)) return; + GradientDrawable gradientDrawable = (GradientDrawable) drawable; + Color color = block.color; + int res = ColorUtils.blendARGB(color.asARGB(), 0x7f7f7f7f, 0.5f); + gradientDrawable.setColor(res); + } +} diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemDetailFragment.java b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemDetailFragment.java index 0df821b8..3c7cabcd 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemDetailFragment.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemDetailFragment.java @@ -86,7 +86,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, View rootView = inflater.inflate(R.layout.worlditem_detail, container, false); try { - if (world != null && world.level != null){ + if (world != null && world.getLevel() != null){ TextView worldName = (TextView) rootView.findViewById(R.id.detail_world_name); worldName.setText(world.getWorldDisplayName()); TextView worldSize = (TextView) rootView.findViewById(R.id.detail_world_size); @@ -164,8 +164,8 @@ public void onClick(View view) { buf.newLine(); buf.write("------------------------"); buf.newLine(); - for(int i =0; i < value.length; i += 256){ - buf.write(WorldData.bytesToHex(value, i, Math.min(i + 256, value.length))); + for(int d =0; d < value.length; d += 256){ + buf.write(WorldData.bytesToHex(value, d, Math.min(d + 256, value.length))); buf.newLine(); } diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java index 30dec8dd..29a34081 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldItemListActivity.java @@ -3,7 +3,6 @@ import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -11,15 +10,16 @@ import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.text.Editable; -import android.text.Html; +import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.Menu; @@ -29,39 +29,64 @@ import android.widget.EditText; import android.widget.TextView; -import com.mithrilmania.blocktopograph.BuildConfig; +import com.mithrilmania.blocktopograph.CreateWorldActivity; import com.mithrilmania.blocktopograph.Log; -import com.mithrilmania.blocktopograph.MenuHelper; import com.mithrilmania.blocktopograph.R; import com.mithrilmania.blocktopograph.World; -import com.mithrilmania.blocktopograph.WorldActivity; import com.mithrilmania.blocktopograph.util.io.IOUtil; import java.io.File; import java.io.FileFilter; +import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -public class WorldItemListActivity extends AppCompatActivity implements MenuHelper.MenuContext { +public class WorldItemListActivity extends AppCompatActivity { + // Storage Permissions + private static final int REQUEST_EXTERNAL_STORAGE = 4242; + private static final String[] PERMISSIONS_STORAGE = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + public static final int REQUEST_CODE_CREATE_WORLD = 2012; /** - * Whether or not the activity is in two-pane mode, i.e. running on a tablet - * device. + * Whether or not the activity is in two-pane mode, d.e. running on a tablet. */ private boolean mTwoPane; - private WorldItemRecyclerViewAdapter worldItemAdapter; + /** + * Checks if the app has permission to write to device storage + *

+ * If the app does not has permission then the user will be prompted to grant permissions + *

+ */ + public static boolean verifyStoragePermissions(Activity activity) { + // Check if we have write permission + int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + ActivityCompat.requestPermissions( + activity, + PERMISSIONS_STORAGE, + REQUEST_EXTERNAL_STORAGE + ); + return false; + } else return true; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + //Fabric.with(this, new Crashlytics()); setContentView(R.layout.activity_worldlist); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - assert toolbar != null; toolbar.setTitle(getTitle()); setSupportActionBar(toolbar); @@ -73,37 +98,95 @@ protected void onCreate(Bundle savedInstanceState) { mTwoPane = true; } - - FloatingActionButton fabRefreshWorlds = (FloatingActionButton) findViewById(R.id.fab_refresh_worlds); - assert fabRefreshWorlds != null; - fabRefreshWorlds.setOnClickListener(new View.OnClickListener() { + FloatingActionButton fabChooseWorldFile = findViewById(R.id.fab_create); + fabChooseWorldFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { + onClickCreateWorld(); + } + }); - if (worldItemAdapter != null) { - if (worldItemAdapter.reloadWorldList()) { - Snackbar.make(view, R.string.reloaded_world_list, Snackbar.LENGTH_SHORT) - .setAction("Action", null).show(); - } else { - Snackbar.make(view, R.string.could_not_find_worlds, Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - } - } else { - Snackbar.make(view, R.string.no_read_write_access, Snackbar.LENGTH_LONG) - .setAction("Action", null).show(); - } + RecyclerView recyclerView = findViewById(R.id.worlditem_list); + worldItemAdapter = new WorldItemRecyclerViewAdapter(); + recyclerView.setAdapter(this.worldItemAdapter); + + if (verifyStoragePermissions(this)) { + //directly open the world list if we already have access + worldItemAdapter.enable(); + } + + + } + private void onClickCreateWorld() { + if (worldItemAdapter.isDisabled()) { + Snackbar.make(getWindow().getDecorView(), R.string.no_read_write_access, Snackbar.LENGTH_SHORT).show(); + return; + } + startActivityForResult(new Intent(this, CreateWorldActivity.class), REQUEST_CODE_CREATE_WORLD); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (resultCode == RESULT_OK) { + switch (requestCode) { + case REQUEST_CODE_CREATE_WORLD: + worldItemAdapter.loadWorldList(); + return; } - }); + } + super.onActivityResult(requestCode, resultCode, data); + } + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String permissions[], @NonNull int[] grantResults) { + switch (requestCode) { + case REQUEST_EXTERNAL_STORAGE: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - FloatingActionButton fabChooseWorldFile = (FloatingActionButton) findViewById(R.id.fab_choose_worldfile); - assert fabChooseWorldFile != null; - fabChooseWorldFile.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View view) { + // permission was granted, yay! + this.worldItemAdapter.enable(); + + } else { + + // permission denied, boo! Disable the + AlertDialog.Builder builder = new AlertDialog.Builder(this); + TextView msg = new TextView(this); + float dpi = this.getResources().getDisplayMetrics().density; + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); + msg.setMaxLines(20); + msg.setMovementMethod(LinkMovementMethod.getInstance()); + msg.setText(R.string.no_sdcard_access); + builder.setView(msg) + .setTitle(R.string.action_help) + .setCancelable(true) + .setNeutralButton(android.R.string.ok, null) + .show(); + } + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.world, menu); + return true; + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + //some text pop-up dialogs, some with simple HTML tags. + switch (item.getItemId()) { + case R.id.action_open: { + if (worldItemAdapter.isDisabled()) { + Snackbar.make(getWindow().getDecorView(), R.string.no_read_write_access, Snackbar.LENGTH_SHORT).show(); + return true; + } final EditText pathText = new EditText(WorldItemListActivity.this); pathText.setHint(R.string.storage_path_here); @@ -149,7 +232,7 @@ public void onClick(DialogInterface dialog, int whichButton) { } else { try { - World world = new World(worldFolder); + World world = new World(worldFolder, null); if (mTwoPane) { Bundle arguments = new Bundle(); @@ -167,7 +250,7 @@ public void onClick(DialogInterface dialog, int whichButton) { } } catch (Exception e) { - Snackbar.make(view, R.string.error_opening_world, Snackbar.LENGTH_SHORT) + Snackbar.make(getWindow().getDecorView(), R.string.error_opening_world, Snackbar.LENGTH_SHORT) .setAction("Action", null).show(); } } @@ -183,177 +266,148 @@ public void onClick(DialogInterface dialog, int whichButton) { alert.show(); //TODO: browse for custom located world file, not everybody understands filesystem paths + //Then it also cannot find a world. --rbq2012. + return true; } - }); - - - if (verifyStoragePermissions(this)) { - //directly open the world list if we already have access - initWorldList(); - } - - - } - - public void initWorldList() { - - View recyclerView = findViewById(R.id.worlditem_list); - assert recyclerView != null; - boolean hasWorlds = setupRecyclerView((RecyclerView) recyclerView); - if (!hasWorlds) { - AlertDialog dia = new AlertDialog.Builder(this) - .setTitle(R.string.err_noworld_1) - .setView(R.layout.dialog_noworlds) - .create(); - dia.show(); - //Snackbar.make(recyclerView, R.string.could_not_find_worlds, Snackbar.LENGTH_SHORT) - // .setAction("Action", null).show(); - } - - } - - - @Override - public void onRequestPermissionsResult(int requestCode, - @NonNull String permissions[], @NonNull int[] grantResults) { - switch (requestCode) { - case REQUEST_EXTERNAL_STORAGE: { - // If request is cancelled, the result arrays are empty. - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - - // permission was granted, yay! - initWorldList(); - - } else { - - // permission denied, boo! Disable the - AlertDialog.Builder builder = new AlertDialog.Builder(this); - TextView msg = new TextView(this); - float dpi = this.getResources().getDisplayMetrics().density; - msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); - msg.setMaxLines(20); - msg.setMovementMethod(LinkMovementMethod.getInstance()); - msg.setText(R.string.no_sdcard_access); - builder.setView(msg) - .setTitle(R.string.action_help) - .setCancelable(true) - .setNeutralButton(android.R.string.ok, null) - .show(); - } + case R.id.action_about: { + + android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(this); + TextView msg = new TextView(this); + msg.setEllipsize(TextUtils.TruncateAt.MARQUEE); + float dpi = getResources().getDisplayMetrics().density; + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); + msg.setMaxLines(20); + msg.setMovementMethod(LinkMovementMethod.getInstance()); + msg.setText(R.string.app_about); + builder.setView(msg) + .setTitle(R.string.action_about) + .setCancelable(true) + .setNeutralButton(android.R.string.ok, null) + .show(); + + return true; + } + case R.id.action_help: { + android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(this); + TextView msg = new TextView(this); + float dpi = getResources().getDisplayMetrics().density; + msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); + msg.setMaxLines(20); + msg.setMovementMethod(LinkMovementMethod.getInstance()); + msg.setText(R.string.app_help); + builder.setView(msg) + .setTitle(R.string.action_help) + .setCancelable(true) + .setNeutralButton(android.R.string.ok, null) + .show(); + + return true; + } +// case R.id.action_changelog: { +// AlertDialog.Builder builder = new AlertDialog.Builder(ctx); +// TextView msg = new TextView(ctx); +// float dpi = ctx.getResources().getDisplayMetrics().density; +// msg.setPadding((int) (19 * dpi), (int) (5 * dpi), (int) (14 * dpi), (int) (5 * dpi)); +// msg.setMaxLines(20); +// msg.setMovementMethod(LinkMovementMethod.getInstance()); +// String content = String.format(ctx.getResources().getString(R.string.app_changelog), BuildConfig.VERSION_NAME); +// //noinspection deprecation +// msg.setText(Html.fromHtml(content)); +// builder.setView(msg) +// .setTitle(R.string.action_changelog) +// .setCancelable(true) +// .setNeutralButton(android.R.string.ok, null) +// .show(); +// +// return true; +// } + default: { + return false; } } } - - // Storage Permissions - private static final int REQUEST_EXTERNAL_STORAGE = 4242; - private static String[] PERMISSIONS_STORAGE = { - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - }; - - /** - * Checks if the app has permission to write to device storage - *

- * If the app does not has permission then the user will be prompted to grant permissions - */ - public static boolean verifyStoragePermissions(Activity activity) { - // Check if we have write permission - int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); - - if (permission != PackageManager.PERMISSION_GRANTED) { - // We don't have permission so prompt the user - ActivityCompat.requestPermissions( - activity, - PERMISSIONS_STORAGE, - REQUEST_EXTERNAL_STORAGE - ); - return false; - } else return true; - } - - - //returns true if the list of worlds is not empty - private boolean setupRecyclerView(@NonNull RecyclerView recyclerView) { - this.worldItemAdapter = new WorldItemRecyclerViewAdapter(); - boolean hasWorlds = this.worldItemAdapter.reloadWorldList(); - recyclerView.setAdapter(this.worldItemAdapter); - return hasWorlds; - } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.world, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return MenuHelper.onOptionsItemSelected(this, item); - } - - @Override - public Context getContext() { - return this; - } - - @Override - public boolean propagateOnOptionsItemSelected(MenuItem item) { - return super.onOptionsItemSelected(item); + protected void onResume() { + super.onResume(); + worldItemAdapter.loadWorldList(); } public class WorldItemRecyclerViewAdapter extends RecyclerView.Adapter { - private final List mValues; + private final List mWorlds; - private final File savesFolder; + private boolean disabled; WorldItemRecyclerViewAdapter() { - mValues = new ArrayList<>(); + mWorlds = new ArrayList<>(16); + disabled = true; + } - String path = Environment.getExternalStorageDirectory().toString() + "/games/com.mojang/minecraftWorlds/"; - Log.d("minecraftWorlds path: " + path); + void enable() { + disabled = false; + } - this.savesFolder = new File(path); + public boolean isDisabled() { + return disabled; } //returns true if it has loaded a new list of worlds, false otherwise - boolean reloadWorldList() { - mValues.clear(); - File[] saves = savesFolder.exists() ? savesFolder.listFiles(new FileFilter() { + void loadWorldList() { + if (disabled) return; + mWorlds.clear(); + List saveFolders; + List marks; + saveFolders = new ArrayList<>(4); + marks = new ArrayList<>(4); + + File sd = Environment.getExternalStorageDirectory(); + + saveFolders.add(new File(sd, "games/com.mojang/minecraftWorlds")); + marks.add(null); + + File[] datas = new File(sd, "Android/data").listFiles(new FilenameFilter() { @Override - public boolean accept(File pathname) { - return new File(pathname + "/level.dat").exists(); + public boolean accept(File file, String s) { + return s.startsWith("com.netease"); } - }) : null; - - if (saves != null) { - Log.d("Number of minecraft worlds: " + saves.length); + }); - for (File save : saves) { - //debug if we see all worlds - Log.d("FileName: " + save.getName()); + if (datas != null) for (File f : datas) { + File ff = new File(f, "files/minecraftWorlds"); + if (ff.exists()) { + saveFolders.add(ff); + marks.add(getString(R.string.world_mark_neteas)); + } + } + for (int i = 0, saveFoldersSize = saveFolders.size(); i < saveFoldersSize; i++) { + File dir = saveFolders.get(i); + File[] files = dir.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + if (!file.isDirectory()) return false; + return new File(file, "level.dat").exists(); + } + }); + if (files != null) for (File f : files) { try { - mValues.add(new World(save)); + mWorlds.add(new World(f, marks.get(i))); } catch (World.WorldLoadException e) { - e.printStackTrace(); - Log.d("Skipping world while reloading world list: " - + save.getName() + ", loading failed somehow, check stack trace"); + Log.d(this, e); } } } - Collections.sort(mValues, new Comparator() { + Collections.sort(mWorlds, new Comparator() { @Override public int compare(World a, World b) { try { long tA = WorldListUtil.getLastPlayedTimestamp(a); long tB = WorldListUtil.getLastPlayedTimestamp(b); - return tA > tB ? -1 : (tA == tB ? 0 : 1); + return Long.compare(tB, tA); } catch (Exception e) { + Log.d(this, e); return 0; } } @@ -362,13 +416,20 @@ public int compare(World a, World b) { //load data into view this.notifyDataSetChanged(); - return mValues.size() > 0; + if (mWorlds.size() == 0) { + AlertDialog dia = new AlertDialog.Builder(WorldItemListActivity.this) + .setTitle(R.string.err_noworld_1) + .setView(R.layout.dialog_noworlds) + .create(); + dia.show(); + } } + @NonNull @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.worlditem_list_content, parent, false); return new ViewHolder(view); @@ -377,13 +438,14 @@ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @SuppressLint("SetTextI18n") @Override - public void onBindViewHolder(final ViewHolder holder, int position) { - holder.mWorld = mValues.get(position); + public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { + holder.mWorld = mWorlds.get(position); holder.mWorldNameView.setText(holder.mWorld.getWorldDisplayName()); holder.mWorldSize.setText(IOUtil.getFileSizeInText(holder.mWorld.worldFolder)); holder.mWorldGamemode.setText(WorldListUtil.getWorldGamemodeText(WorldItemListActivity.this, holder.mWorld)); holder.mWorldLastPlayed.setText(WorldListUtil.getLastPlayedText(WorldItemListActivity.this, holder.mWorld)); - holder.mWorldPath.setText(holder.mWorld.levelFile.getAbsolutePath()); + holder.mWorldPath.setText(holder.mWorld.worldFolder.getName()); + holder.mWorldMark.setText(holder.mWorld.mark); holder.mView.setOnClickListener(new View.OnClickListener() { @@ -410,22 +472,28 @@ public void onClick(View v) { @Override public int getItemCount() { - return mValues.size(); + return mWorlds.size(); } class ViewHolder extends RecyclerView.ViewHolder { final View mView; - final TextView mWorldNameView, mWorldSize, mWorldGamemode, mWorldLastPlayed, mWorldPath; + final TextView mWorldNameView; + final TextView mWorldMark; + final TextView mWorldSize; + final TextView mWorldGamemode; + final TextView mWorldLastPlayed; + final TextView mWorldPath; World mWorld; ViewHolder(View view) { super(view); mView = view; - mWorldNameView = (TextView) view.findViewById(R.id.world_name); - mWorldSize = (TextView) view.findViewById(R.id.world_size); - mWorldGamemode = (TextView) view.findViewById(R.id.world_gamemode); - mWorldLastPlayed = (TextView) view.findViewById(R.id.world_last_played); - mWorldPath = (TextView) view.findViewById(R.id.world_path); + mWorldNameView = view.findViewById(R.id.world_name); + mWorldMark = view.findViewById(R.id.world_mark); + mWorldSize = view.findViewById(R.id.world_size); + mWorldGamemode = view.findViewById(R.id.world_gamemode); + mWorldLastPlayed = view.findViewById(R.id.world_last_played); + mWorldPath = view.findViewById(R.id.world_path); } @Override diff --git a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldListUtil.java b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldListUtil.java index 523f88dc..db2efc5b 100644 --- a/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldListUtil.java +++ b/app/src/main/java/com/mithrilmania/blocktopograph/worldlist/WorldListUtil.java @@ -3,6 +3,7 @@ import android.content.Context; +import com.mithrilmania.blocktopograph.Log; import com.mithrilmania.blocktopograph.R; import com.mithrilmania.blocktopograph.World; @@ -10,41 +11,46 @@ public class WorldListUtil { - public static long getLastPlayedTimestamp(World world){ + public static long getLastPlayedTimestamp(World world) { try { - return (long) world.level.getChildTagByKey("LastPlayed").getValue(); - } catch (Exception e){ + return (long) world.getLevel().getChildTagByKey("LastPlayed").getValue(); + } catch (Exception e) { + Log.d(WorldListUtil.class, e); return 0; } } - public static String getLastPlayedText(Context context, World world){ + public static String getLastPlayedText(Context context, World world) { long lastPlayed = getLastPlayedTimestamp(world); - if(lastPlayed == 0) return "?"; + if (lastPlayed == 0) return "?"; DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context); return dateFormat.format(lastPlayed * 1000); } - public static String getWorldGamemodeText(Context context, World world){ + public static String getWorldGamemodeText(Context context, World world) { String gameMode; try { - int gameType = (int) world.level.getChildTagByKey("GameType").getValue(); - switch (gameType){ - case 0: gameMode = context.getString(R.string.gamemode_survival); + int gameType = (int) world.getLevel().getChildTagByKey("GameType").getValue(); + switch (gameType) { + case 0: + gameMode = context.getString(R.string.gamemode_survival); break; - case 1: gameMode = context.getString(R.string.gamemode_creative); + case 1: + gameMode = context.getString(R.string.gamemode_creative); break; - case 2: gameMode = context.getString(R.string.gamemode_adventure); + case 2: + gameMode = context.getString(R.string.gamemode_adventure); break; default: gameMode = "?"; } - } catch (Exception e){ - gameMode = "?"; + } catch (Exception e) { + Log.d(WorldListUtil.class, e); + gameMode = "??"; } return gameMode; } - - + + } diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 00000000..834ff462 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_black.xml b/app/src/main/res/drawable/ic_add_black.xml new file mode 100644 index 00000000..dd959826 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_black.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_help_black.xml b/app/src/main/res/drawable/ic_help_black.xml new file mode 100644 index 00000000..b0af2467 --- /dev/null +++ b/app/src/main/res/drawable/ic_help_black.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/rect_border_round_corner.xml b/app/src/main/res/drawable/rect_border_round_corner.xml new file mode 100644 index 00000000..421c7ce3 --- /dev/null +++ b/app/src/main/res/drawable/rect_border_round_corner.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create_world.xml b/app/src/main/res/layout/activity_create_world.xml new file mode 100644 index 00000000..844e9ef6 --- /dev/null +++ b/app/src/main/res/layout/activity_create_world.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_worldlist.xml b/app/src/main/res/layout/activity_worldlist.xml index 649416cf..ffc04697 100644 --- a/app/src/main/res/layout/activity_worldlist.xml +++ b/app/src/main/res/layout/activity_worldlist.xml @@ -1,6 +1,5 @@ - + android:orientation="vertical" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + android:layout_height="wrap_content" /> + android:layout_gravity="bottom" + android:orientation="vertical"> - + android:layout_marginBottom="10dp" /--> + android:focusable="false" + android:src="@drawable/ic_add" /> diff --git a/app/src/main/res/layout/common_dialog_activity_posibtn_bar.xml b/app/src/main/res/layout/common_dialog_activity_posibtn_bar.xml new file mode 100644 index 00000000..d3dc4654 --- /dev/null +++ b/app/src/main/res/layout/common_dialog_activity_posibtn_bar.xml @@ -0,0 +1,15 @@ + + + +