Compare commits

..

No commits in common. "main" and "v0.0.2" have entirely different histories.
main ... v0.0.2

5 changed files with 66 additions and 88 deletions

View File

@ -7,3 +7,12 @@ git clone git@github.com:andrewlalis/MaterialsExtractor.git
cd MaterialsExtractor cd MaterialsExtractor
./build.sh ./build.sh
``` ```
Usage information:
```
java -jar materials-extractor.jar <schematic-file.nbt> [paste.ee token]
Where
First argument (required) is the path to the .nbt schematic file to read.
Second argument (optional) is an api token to https://paste.ee, to
automatically upload item-list for usage.
```

View File

@ -4,4 +4,4 @@
cp target/materials-extractor-*-jar-with-dependencies.jar ./materials-extractor.jar cp target/materials-extractor-*-jar-with-dependencies.jar ./materials-extractor.jar
echo "Built project as materials-extractor.jar" echo "Built project as materials-extractor.jar"
echo "Run: java -jar materials-extractor.jar <schematic-file.nbt>" echo "Run: java -jar materials-extractor.jar <schematic-file.nbt> [paste.ee token]"

View File

@ -6,7 +6,7 @@
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<artifactId>materials-extractor</artifactId> <artifactId>materials-extractor</artifactId>
<version>1.0.0</version> <version>0.0.2</version>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>

View File

@ -1,8 +1,6 @@
package nl.andrewlalis.materials_extractor; package nl.andrewlalis.materials_extractor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import net.querz.nbt.io.NBTUtil; import net.querz.nbt.io.NBTUtil;
import net.querz.nbt.io.NamedTag; import net.querz.nbt.io.NamedTag;
@ -10,95 +8,49 @@ import net.querz.nbt.tag.CompoundTag;
import net.querz.nbt.tag.ListTag; import net.querz.nbt.tag.ListTag;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.MessageDigest; import java.util.*;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MaterialsExtractor { public class MaterialsExtractor {
private static final Map<String, String> CONVERSIONS = new HashMap<>();
static {
CONVERSIONS.put("minecraft:redstone_wall_torch", "minecraft:redstone_torch");
CONVERSIONS.put("minecraft:piston_head", null);
CONVERSIONS.put("minecraft:redstone_wire", "minecraft:redstone");
}
private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
if (args.length < 1) { if (args.length < 1) {
System.out.println("Missing at least one NBT file as an argument to the program."); System.out.println("Missing required NBT file as 1st argument to this program.");
System.exit(1); System.exit(1);
} }
for (String arg : args) { Path nbtFilePath = Path.of(args[0]);
if (!validateInputFile(arg)) { if (Files.notExists(nbtFilePath) || !Files.isReadable(nbtFilePath) || !nbtFilePath.getFileName().toString().endsWith(".nbt")) {
System.out.println("Invalid or missing file: " + arg); System.out.println("Invalid or missing file: " + args[0]);
System.exit(1); System.exit(1);
}
} }
var conversions = parseConversions(); final var counts = getItemList(nbtFilePath);
ArrayNode array = MAPPER.createArrayNode();
for (String arg : args) {
array.add(extractFromFile(arg, conversions));
}
ObjectMapper outputMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
String json = outputMapper.writeValueAsString(array);
System.out.println(json);
}
private static boolean validateInputFile(String file) { ObjectNode obj = MAPPER.createObjectNode();
Path filePath = Path.of(file); counts.keySet().stream().sorted().forEachOrdered(key -> obj.put(key, counts.get(key)));
return Files.exists(filePath) && String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
Files.isReadable(filePath) && if (args.length >= 2) {
filePath.getFileName().toString().toLowerCase().endsWith(".nbt"); // Use 2nd arg as api key for paste.ee.
} upload(json, args[1]);
} else {
private static Map<String, String> parseConversions() throws IOException { System.out.println("Copy and paste the following JSON to a pastebin service, and copy the \"raw\" download URL when running the item-extractor program on your in-game PC:");
try (var in = MaterialsExtractor.class.getResourceAsStream("/conversions.json")) { System.out.println(json);
if (in == null) throw new IOException("Couldn't find resource.");
ObjectNode obj = MAPPER.readValue(in, ObjectNode.class);
Map<String, String> conversions = new HashMap<>();
for (var entry : obj.properties()) {
if (entry.getValue().isNull()) {
conversions.put(entry.getKey(), null);
} else {
conversions.put(entry.getKey(), entry.getValue().asText());
}
}
return Collections.unmodifiableMap(conversions);
} }
} }
private static ObjectNode extractFromFile(String file, Map<String, String> conversions) throws IOException { private static Map<String, Integer> getItemList(Path filePath) throws IOException {
var filePath = Path.of(file);
var counts = getItemList(filePath, conversions);
var obj = countsToJson(counts);
obj.put("__NAME__", filePath.getFileName().toString());
obj.put("__TIMESTAMP__", LocalDateTime.now(ZoneOffset.UTC).toString());
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hash = md.digest(Files.readAllBytes(filePath));
StringBuilder sb = new StringBuilder(hash.length * 2);
for (byte b : hash) sb.append(String.format("%02x", b));
obj.put("__SHA1_HASH__", sb.toString());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return obj;
}
/**
* Gets a mapping of items to their required count, for a Create-mod
* NBT schematic file. The NBT is structured such that it contains a
* <code>palette</code> list of compound tags, and a <code>blocks</code>
* list of compound tags. We first parse an array of all item names from the
* palette, then look through the blocks to count up the instances of each.
* @param filePath The file path to read.
* @param conversions A mapping for item names to convert to others (or null)
* in order to avoid weird minecraft items like piston
* heads.
* @return A mapping of item names to their count.
* @throws IOException If a read error occurs.
*/
private static Map<String, Integer> getItemList(Path filePath, Map<String, String> conversions) throws IOException {
NamedTag root = NBTUtil.read(filePath.toFile()); NamedTag root = NBTUtil.read(filePath.toFile());
CompoundTag compoundTag = (CompoundTag) root.getTag(); CompoundTag compoundTag = (CompoundTag) root.getTag();
ListTag<CompoundTag> paletteEntries = compoundTag.getListTag("palette").asCompoundTagList(); ListTag<CompoundTag> paletteEntries = compoundTag.getListTag("palette").asCompoundTagList();
@ -112,7 +64,7 @@ public class MaterialsExtractor {
Map<String, Integer> counts = new HashMap<>(); Map<String, Integer> counts = new HashMap<>();
for (var entry : blockEntries) { for (var entry : blockEntries) {
String name = palette[entry.getInt("state")]; String name = palette[entry.getInt("state")];
String converted = conversions.getOrDefault(name, name); String converted = CONVERSIONS.getOrDefault(name, name);
if (converted != null) { if (converted != null) {
counts.put(converted, 1 + counts.computeIfAbsent(converted, s -> 0)); counts.put(converted, 1 + counts.computeIfAbsent(converted, s -> 0));
} }
@ -120,9 +72,31 @@ public class MaterialsExtractor {
return counts; return counts;
} }
private static ObjectNode countsToJson(Map<String, Integer> counts) { private static void upload(String content, String apiToken) throws Exception {
ObjectNode obj = MAPPER.createObjectNode(); HttpClient httpClient = HttpClient.newHttpClient();
counts.keySet().stream().sorted().forEachOrdered(key -> obj.put(key, counts.get(key)));
return obj; ObjectNode body = MAPPER.createObjectNode();
body.put("encrypted", false);
body.put("description", "Schematic item-list");
ObjectNode section = MAPPER.createObjectNode();
section.put("name", "item-list.json");
section.put("syntax", "json");
section.put("contents", content);
body.set("sections", MAPPER.createArrayNode().add(section));
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.paste.ee/v1/pastes"))
.POST(HttpRequest.BodyPublishers.ofString(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(body)))
.header("X-Auth-Token", apiToken)
.header("Content-Type", "application/json")
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
System.err.println("Error: " + response.statusCode());
return;
}
ObjectNode responseBody = MAPPER.readValue(response.body(), ObjectNode.class);
String pasteId = responseBody.get("id").asText();
System.out.println("https://paste.ee/d/" + pasteId);
} }
} }

View File

@ -1,5 +0,0 @@
{
"minecraft:redstone_wall_torch": "minecraft:redstone_torch",
"minecraft:piston_head": null,
"minecraft:redstone_wire": "minecraft:redstone"
}