Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Andrew Lalis | a8098084e1 | |
Andrew Lalis | 700c30f79f |
|
@ -20,16 +20,17 @@ jobs:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
|
- name: Extract Tag Version
|
||||||
|
id: get_version
|
||||||
|
run: echo "tag_version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
id: build
|
id: build
|
||||||
run: |
|
run: |
|
||||||
mvn -B package --file pom.xml
|
mvn -B package --file pom.xml
|
||||||
asset=$(find target/ -type f -name "*jar-with-dependencies.jar")
|
asset=$(find target/ -type f -name "*jar-with-dependencies.jar")
|
||||||
echo "asset_path=$asset" >> $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: Extract Tag Version
|
|
||||||
id: get_version
|
|
||||||
run: echo "tag_version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Logs
|
- name: Logs
|
||||||
run: |
|
run: |
|
||||||
|
@ -42,13 +43,3 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: MaterialsExtractor ${{steps.get_version.outputs.tag_version}}
|
name: MaterialsExtractor ${{steps.get_version.outputs.tag_version}}
|
||||||
files: ${{steps.build.outputs.asset_path}}
|
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
|
|
||||||
|
|
|
@ -7,12 +7,3 @@ 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.
|
|
||||||
```
|
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -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> [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>
|
<groupId>nl.andrewlalis</groupId>
|
||||||
<artifactId>materials-extractor</artifactId>
|
<artifactId>materials-extractor</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>1.0.0</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>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
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;
|
||||||
|
@ -8,49 +10,95 @@ 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.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 {
|
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 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);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
Path nbtFilePath = Path.of(args[0]);
|
for (String arg : args) {
|
||||||
if (Files.notExists(nbtFilePath) || !Files.isReadable(nbtFilePath) || !nbtFilePath.getFileName().toString().endsWith(".nbt")) {
|
if (!validateInputFile(arg)) {
|
||||||
System.out.println("Invalid or missing file: " + args[0]);
|
System.out.println("Invalid or missing file: " + arg);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
final var counts = getItemList(nbtFilePath);
|
}
|
||||||
|
var conversions = parseConversions();
|
||||||
ObjectNode obj = MAPPER.createObjectNode();
|
ArrayNode array = MAPPER.createArrayNode();
|
||||||
counts.keySet().stream().sorted().forEachOrdered(key -> obj.put(key, counts.get(key)));
|
for (String arg : args) {
|
||||||
String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
|
array.add(extractFromFile(arg, conversions));
|
||||||
if (args.length >= 2) {
|
}
|
||||||
// Use 2nd arg as api key for paste.ee.
|
ObjectMapper outputMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
upload(json, args[1]);
|
String json = outputMapper.writeValueAsString(array);
|
||||||
} 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);
|
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());
|
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();
|
||||||
|
@ -64,7 +112,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));
|
||||||
}
|
}
|
||||||
|
@ -72,31 +120,9 @@ public class MaterialsExtractor {
|
||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void upload(String content, String apiToken) throws Exception {
|
private static ObjectNode countsToJson(Map<String, Integer> counts) {
|
||||||
HttpClient httpClient = HttpClient.newHttpClient();
|
ObjectNode obj = MAPPER.createObjectNode();
|
||||||
|
counts.keySet().stream().sorted().forEachOrdered(key -> obj.put(key, counts.get(key)));
|
||||||
ObjectNode body = MAPPER.createObjectNode();
|
return obj;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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