Compare commits

...

2 Commits
v0.0.1 ... main

6 changed files with 94 additions and 81 deletions

View File

@ -20,16 +20,17 @@ jobs:
java-version: "17"
distribution: "temurin"
- name: Extract Tag Version
id: get_version
run: echo "tag_version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
- name: Build with Maven
id: build
run: |
mvn -B package --file pom.xml
asset=$(find target/ -type f -name "*jar-with-dependencies.jar")
echo "asset_path=$asset" >> $GITHUB_OUTPUT
- name: Extract Tag Version
id: get_version
run: echo "tag_version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
cp $asset materials-extractor-${{steps.get_version.outputs.tag_version}}.jar
echo "asset_path=materials-extractor-${{steps.get_version.outputs.tag_version}}.jar" >> $GITHUB_OUTPUT
- name: Logs
run: |
@ -42,13 +43,3 @@ jobs:
with:
name: MaterialsExtractor ${{steps.get_version.outputs.tag_version}}
files: ${{steps.build.outputs.asset_path}}
# - name: Upload Asset
# uses: actions/upload-release-asset@v1
# env:
# GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
# with:
# upload_url: ${{steps.create_release.outputs.upload_url}}
# asset_name: materials-extractor-${{steps.get_version.outputs.tag_version}}.jar
# asset_path: ${{steps.build.outputs.asset_path}}
# asset_content_type: application/java-archive

View File

@ -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.
```

View File

@ -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>"

View File

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

View File

@ -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]);
System.exit(1);
for (String arg : args) {
if (!validateInputFile(arg)) {
System.out.println("Invalid or missing file: " + arg);
System.exit(1);
}
}
final var counts = getItemList(nbtFilePath);
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);
}
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:");
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, 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 Map<String, Integer> getItemList(Path filePath) throws IOException {
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;
}
}

View File

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