Skip to content

Commit

Permalink
Add support for Minecraft 1.18
Browse files Browse the repository at this point in the history
  • Loading branch information
Meeples10 committed Dec 5, 2021
1 parent d2cc94d commit dc6bc7a
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 11 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Several command line arguments may be used to modify the behavior of the program
- `table`: Generates a simple HTML table with the collected data.
- `version-select`: Use this argument if you want to analyze a world that was not generated with the latest version of Minecraft. Shows a popup on launch that allows the version in which the region files were generated to be selected. Selecting a version that does not match the version in which the regions were generated may result in unexpected behavior.
Alternatively, to explicitly specify a version from the command line and skip the popup, use the argument `version-select=<version>` where `<version>` is one of the following:
- `ANVIL_118` for 1.18
- `ANVIL_2021` for 1.16 to 1.17
- `ANVIL_2018` for 1.13 to 1.15
- `ANVIL_2012` for 1.2 to 1.12
Expand All @@ -37,7 +38,7 @@ Alternatively, to explicitly specify a version from the command line and skip th

### Version compatibility

MCResourceAnalyzer 1.1.1 can analyze worlds generated with any version of Minecraft: Java Edition between Indev 0.31 20100122 and 1.17.
MCResourceAnalyzer 1.1.4 can analyze worlds generated with any version of Minecraft: Java Edition between Indev 0.31 20100122 and 1.18.

Note that Indev worlds with the `Long` and `Deep` world shapes are not supported.

Expand All @@ -46,6 +47,3 @@ Note that Indev worlds with the `Long` and `Deep` world shapes are not supported
Contributions to this project are more than welcome. If you create your own fork, my only request is that you include the following URL somewhere in your fork: https://github.com/Meeples10/MCResourceAnalyzer

If you notice that the program fails to analyze a particular world, please [create an issue](https://github.com/Meeples10/MCResourceAnalyzer/issues/new?title=Error%20when%20analyzing%20world&body=Minecraft%20version:%20%0A%0AProgram%20output:%0A%60%60%60%0A[paste%20output%20here]%0A%60%60%60%0A%0AOther%20details:%20%0A%0A%3C%21--%20If%20possible,%20please%20attach%20the%20region%20file%20that%20caused%20the%20program%20to%20fail%20--%3E) with the version of Minecraft with which the world was generated, the output of the program, and, if possible, the region file that caused the program to fail.


[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/donate?business=ZXKMCY4HP34BG&no_recurring=0&item_name=Developing+Software&currency_code=USD)
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>io.github.meeples10.mcresourceanalyzer</groupId>
<artifactId>mc-resource-analyzer</artifactId>
<version>1.1.3</version>
<version>1.1.4</version>
<packaging>jar</packaging>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@ int getMaximumY() {
}

public enum Version {
ANVIL_2021("Anvil (1.16 to 1.17)", RegionAnalyzerAnvil2021.class, true), ANVIL_2018("Anvil (1.13 to 1.15)",
RegionAnalyzerAnvil2018.class,
true), ANVIL_2012("Anvil (1.2 to 1.12)", RegionAnalyzerAnvil2012.class, true), MCREGION(
"McRegion (Beta 1.3 to 1.1)", RegionAnalyzerMCRegion.class,
true), ALPHA("Alpha (Infdev 20100327 to Beta 1.2)", RegionAnalyzerAlpha.class, true), INDEV(
"Indev (Indev 0.31 20100122 to Infdev 20100325)", RegionAnalyzerIndev.class, false);
ANVIL_118("Anvil (1.18)", RegionAnalyzerAnvil118.class, true), ANVIL_2021("Anvil (1.16 to 1.17)",
RegionAnalyzerAnvil2021.class, true), ANVIL_2018("Anvil (1.13 to 1.15)", RegionAnalyzerAnvil2018.class,
true), ANVIL_2012("Anvil (1.2 to 1.12)", RegionAnalyzerAnvil2012.class, true), MCREGION(
"McRegion (Beta 1.3 to 1.1)", RegionAnalyzerMCRegion.class,
true), ALPHA("Alpha (Infdev 20100327 to Beta 1.2)", RegionAnalyzerAlpha.class,
true), INDEV("Indev (Indev 0.31 20100122 to Infdev 20100325)",
RegionAnalyzerIndev.class, false);

private final String versionName;
private final Class<? extends RegionAnalyzer> analyzerClass;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.github.meeples10.mcresourceanalyzer;

import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagLongArray;

public class RegionAnalyzerAnvil118 extends RegionAnalyzer {

@Override
public void analyze(File regionDir) {
if(!regionDir.exists()) {
System.out.println("Error: No region directory found at " + regionDir.getAbsolutePath());
System.exit(1);
}
int totalRegions = regionDir.listFiles().length;
if(totalRegions == 0) {
System.out.println("Error: Region directory is empty");
System.exit(1);
}
System.out.println(totalRegions + " regions found");
int rnum = 1;
for(File f : regionDir.listFiles()) {
long startTime = System.currentTimeMillis();
String name = Main.formatRegionName(regionDir, f);
RegionFile r = new RegionFile(f);
System.out.print("Scanning region " + name + " [" + rnum + "/" + totalRegions + "] (modified "
+ Main.DATE_FORMAT.format(new Date(r.lastModified())) + ")... ");
for(int x = 0; x < 32; x++) {
for(int z = 0; z < 32; z++) {
if(r.hasChunk(x, z)) {
try {
processRegion(r, name, x, z);
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
System.out.println(
"Done (" + String.format("%.2f", (double) (System.currentTimeMillis() - startTime) / 1000) + "s)");
rnum++;
}
long duration = System.currentTimeMillis() - getStartTime();
System.out.println(("Completed analysis in " + Main.millisToHMS(duration) + " (" + chunkCount + " chunks)"));
long totalBlocks = 0L;
for(String key : blockCounter.keySet()) {
totalBlocks += blockCounter.get(key);
}
System.out.println("--------------------------------\n" + blockCounter.size() + " unique blocks\n" + totalBlocks
+ " blocks total\n--------------------------------");

System.out.print("Sorting data... ");
heightCounter = heightCounter.entrySet().stream().sorted(Map.Entry.comparingByKey(new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
return Long.compare(blockCounter.get(arg1), blockCounter.get(arg0));
}
})).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
System.out.println("Done");

double totalExcludingAir = (double) (totalBlocks
- (blockCounter.containsKey("minecraft:air") ? blockCounter.get("minecraft:air") : 0)
- (blockCounter.containsKey("minecraft:cave_air") ? blockCounter.get("minecraft:cave_air") : 0));
System.out.print("Generating CSV... ");
String data = "";
if(Main.saveStatistics) {
data += "chunk-count=" + chunkCount + ",unique-blocks=" + blockCounter.size() + ",total-blocks="
+ totalBlocks + ",duration-millis=" + duration + ",duration-readable=" + Main.millisToHMS(duration)
+ "\n";
}
data += "id,";
int minY = getMinimumY();
int maxY = getMaximumY();
for(int i = minY; i <= maxY; i++) {
data += i + ",";
}
data += "total,percent_of_total,percent_excluding_air\n";
int digits = String.valueOf(blockCounter.size()).length();
String completionFormat = "[%0" + digits + "d/%0" + digits + "d]";
int keyIndex = 0;
for(String key : heightCounter.keySet()) {
keyIndex += 1;
System.out.print("\rGenerating CSV... " + String.format(completionFormat, keyIndex, blockCounter.size()));
data += key + ",";
for(int i = minY; i <= maxY; i++) {
if(!heightCounter.get(key).containsKey(i)) {
data += "0,";
} else {
data += heightCounter.get(key).get(i) + ",";
}
}
data += blockCounter.get(key) + ","
+ Main.DECIMAL_FORMAT.format(((double) blockCounter.get(key) / (double) totalBlocks) * 100.0d);
if(key.equals("minecraft:air") || key.equals("minecraft:cave_air")) {
data += ",N/A";
} else {
data += "," + Main.DECIMAL_FORMAT.format(((double) blockCounter.get(key) / totalExcludingAir) * 100.0d);
}
data += "\n";
}
try {
File out = new File(Main.getOutputPrefix() + ".csv");
Main.writeStringToFile(out, data);
System.out.println("\nData written to " + out.getAbsolutePath());
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
if(Main.generateTable) {
try {
File out = new File(Main.getOutputPrefix() + "_table.html");
Main.writeStringToFile(out, generateTable((double) totalBlocks, totalExcludingAir));
System.out.println("\nTable written to " + out.getAbsolutePath());
} catch(IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}

private void processRegion(RegionFile r, String name, int x, int z) throws Exception {
DataInputStream chunkDataInputStream = r.getChunkDataInputStream(x, z);
if(chunkDataInputStream == null) {
// Skip malformed chunks
return;
}
NBTTagList sections = CompressedStreamTools.read(r.getChunkDataInputStream(x, z)).getTagList("sections", 10);
analyzeChunk(sections);
chunkCount++;
}

private void analyzeChunk(NBTTagList sections) {
int i = 0;
for(; i < sections.tagCount(); i++) {
NBTTagCompound tag = sections.getCompoundTagAt(i);

NBTTagLongArray blockStatesTag = ((NBTTagLongArray) tag.getCompoundTag("block_states").getTag("data"));
long[] blockStates = blockStatesTag == null ? new long[0] : blockStatesTag.get();
NBTTagList palette = tag.getCompoundTag("block_states").getTagList("palette", 10);
if(palette.hasNoTags()) continue;
int bitLength = Main.bitLength(palette.tagCount() - 1);
bitLength = bitLength < 4 ? 4 : bitLength;
int[] blocks = Main.unstream(bitLength, 64, true, blockStates);
if(blocks.length == 0) continue; // skip empty sections
int sectionY = tag.getByte("Y");

for(int y = 0; y < 16; y++) {
for(int x = 0; x < 16; x++) {
for(int z = 0; z < 16; z++) {
int actualY = sectionY * 16 + y;
int blockIndex = y * 16 * 16 + z * 16 + x;
String block = palette.getStringTagAt(blocks[blockIndex]);
int startIndex = block.indexOf("Name:\"");
String blockName = block.substring(startIndex + 6, block.indexOf('"', startIndex + 6));
if(blockCounter.containsKey(blockName)) {
blockCounter.put(blockName, blockCounter.get(blockName) + 1L);
} else {
blockCounter.put(blockName, 1L);
}
if(!heightCounter.containsKey(blockName)) {
heightCounter.put(blockName, new HashMap<Integer, Long>());
}
if(heightCounter.get(blockName).containsKey(actualY)) {
heightCounter.get(blockName).put(actualY, heightCounter.get(blockName).get(actualY) + 1L);
} else {
heightCounter.get(blockName).put(actualY, 1L);
}
}
}
}
}
airHack(i, "minecraft:air");
}
}

0 comments on commit dc6bc7a

Please sign in to comment.