Refactored, removed paste.ee uploading, merged JSON output from multiple files.
This commit is contained in:
parent
700c30f79f
commit
a8098084e1
|
@ -7,12 +7,3 @@ git clone git@github.com:andrewlalis/MaterialsExtractor.git
|
|||
cd MaterialsExtractor
|
||||
./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.
|
||||
```
|
||||
|
|
2
build.sh
2
build.sh
|
@ -4,4 +4,4 @@
|
|||
cp target/materials-extractor-*-jar-with-dependencies.jar ./materials-extractor.jar
|
||||
|
||||
echo "Built project as materials-extractor.jar"
|
||||
echo "Run: java -jar materials-extractor.jar <schematic-file.nbt> [paste.ee token]"
|
||||
echo "Run: java -jar materials-extractor.jar <schematic-file.nbt>"
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>nl.andrewlalis</groupId>
|
||||
<artifactId>materials-extractor</artifactId>
|
||||
<version>0.0.2</version>
|
||||
<version>1.0.0</version>
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package nl.andrewlalis.materials_extractor;
|
||||
|
||||
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 net.querz.nbt.io.NBTUtil;
|
||||
import net.querz.nbt.io.NamedTag;
|
||||
|
@ -8,49 +10,95 @@ import net.querz.nbt.tag.CompoundTag;
|
|||
import net.querz.nbt.tag.ListTag;
|
||||
|
||||
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.Path;
|
||||
import java.util.*;
|
||||
import java.security.MessageDigest;
|
||||
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 {
|
||||
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();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 1) {
|
||||
System.out.println("Missing required NBT file as 1st argument to this program.");
|
||||
System.out.println("Missing at least one NBT file as an argument to the program.");
|
||||
System.exit(1);
|
||||
}
|
||||
Path nbtFilePath = Path.of(args[0]);
|
||||
if (Files.notExists(nbtFilePath) || !Files.isReadable(nbtFilePath) || !nbtFilePath.getFileName().toString().endsWith(".nbt")) {
|
||||
System.out.println("Invalid or missing file: " + args[0]);
|
||||
for (String arg : args) {
|
||||
if (!validateInputFile(arg)) {
|
||||
System.out.println("Invalid or missing file: " + arg);
|
||||
System.exit(1);
|
||||
}
|
||||
final var counts = getItemList(nbtFilePath);
|
||||
|
||||
ObjectNode obj = MAPPER.createObjectNode();
|
||||
counts.keySet().stream().sorted().forEachOrdered(key -> obj.put(key, counts.get(key)));
|
||||
String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
|
||||
if (args.length >= 2) {
|
||||
// Use 2nd arg as api key for paste.ee.
|
||||
upload(json, args[1]);
|
||||
} else {
|
||||
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:");
|
||||
}
|
||||
var conversions = parseConversions();
|
||||
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) {
|
||||
Path filePath = Path.of(file);
|
||||
return Files.exists(filePath) &&
|
||||
Files.isReadable(filePath) &&
|
||||
filePath.getFileName().toString().toLowerCase().endsWith(".nbt");
|
||||
}
|
||||
|
||||
private static Map<String, Integer> getItemList(Path filePath) throws IOException {
|
||||
private static Map<String, String> parseConversions() throws IOException {
|
||||
try (var in = MaterialsExtractor.class.getResourceAsStream("/conversions.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 {
|
||||
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());
|
||||
CompoundTag compoundTag = (CompoundTag) root.getTag();
|
||||
ListTag<CompoundTag> paletteEntries = compoundTag.getListTag("palette").asCompoundTagList();
|
||||
|
@ -64,7 +112,7 @@ public class MaterialsExtractor {
|
|||
Map<String, Integer> counts = new HashMap<>();
|
||||
for (var entry : blockEntries) {
|
||||
String name = palette[entry.getInt("state")];
|
||||
String converted = CONVERSIONS.getOrDefault(name, name);
|
||||
String converted = conversions.getOrDefault(name, name);
|
||||
if (converted != null) {
|
||||
counts.put(converted, 1 + counts.computeIfAbsent(converted, s -> 0));
|
||||
}
|
||||
|
@ -72,31 +120,9 @@ public class MaterialsExtractor {
|
|||
return counts;
|
||||
}
|
||||
|
||||
private static void upload(String content, String apiToken) throws Exception {
|
||||
HttpClient httpClient = HttpClient.newHttpClient();
|
||||
|
||||
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);
|
||||
private static ObjectNode countsToJson(Map<String, Integer> counts) {
|
||||
ObjectNode obj = MAPPER.createObjectNode();
|
||||
counts.keySet().stream().sorted().forEachOrdered(key -> obj.put(key, counts.get(key)));
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"minecraft:redstone_wall_torch": "minecraft:redstone_torch",
|
||||
"minecraft:piston_head": null,
|
||||
"minecraft:redstone_wire": "minecraft:redstone"
|
||||
}
|
Loading…
Reference in New Issue