diff --git a/extractor/src/main/java/dev/_00a/valence_extractor/DummyPlayerEntity.java b/extractor/src/main/java/dev/_00a/valence_extractor/DummyPlayerEntity.java index 153273c..47b4c91 100644 --- a/extractor/src/main/java/dev/_00a/valence_extractor/DummyPlayerEntity.java +++ b/extractor/src/main/java/dev/_00a/valence_extractor/DummyPlayerEntity.java @@ -13,7 +13,7 @@ public class DummyPlayerEntity extends PlayerEntity { public static final DummyPlayerEntity INSTANCE; static { - INSTANCE = Util.magicallyInstantiate(DummyPlayerEntity.class); + INSTANCE = Main.magicallyInstantiate(DummyPlayerEntity.class); try { var dataTrackerField = Entity.class.getDeclaredField("dataTracker"); diff --git a/extractor/src/main/java/dev/_00a/valence_extractor/DummyWorld.java b/extractor/src/main/java/dev/_00a/valence_extractor/DummyWorld.java index 3a9c7fb..d7f643f 100644 --- a/extractor/src/main/java/dev/_00a/valence_extractor/DummyWorld.java +++ b/extractor/src/main/java/dev/_00a/valence_extractor/DummyWorld.java @@ -38,7 +38,7 @@ public class DummyWorld extends World { public static final DummyWorld INSTANCE; static { - INSTANCE = Util.magicallyInstantiate(DummyWorld.class); + INSTANCE = Main.magicallyInstantiate(DummyWorld.class); try { var randomField = World.class.getDeclaredField("random"); diff --git a/extractor/src/main/java/dev/_00a/valence_extractor/Main.java b/extractor/src/main/java/dev/_00a/valence_extractor/Main.java new file mode 100644 index 0000000..d798af6 --- /dev/null +++ b/extractor/src/main/java/dev/_00a/valence_extractor/Main.java @@ -0,0 +1,78 @@ +package dev._00a.valence_extractor; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import dev._00a.valence_extractor.extractors.Blocks; +import dev._00a.valence_extractor.extractors.Entities; +import dev._00a.valence_extractor.extractors.EntityStatuses; +import net.fabricmc.api.ModInitializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sun.reflect.ReflectionFactory; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class Main implements ModInitializer { + public static final String MOD_ID = "valence_extractor"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + /** + * Magically creates an instance of a concrete class without calling its constructor. + */ + public static T magicallyInstantiate(Class clazz) { + var rf = ReflectionFactory.getReflectionFactory(); + try { + var objCon = Object.class.getDeclaredConstructor(); + var con = rf.newConstructorForSerialization(clazz, objCon); + return clazz.cast(con.newInstance()); + } catch (Throwable e) { + throw new IllegalArgumentException("Failed to magically instantiate " + clazz.getName(), e); + } + } + + @Override + public void onInitialize() { + LOGGER.info("Starting extractors..."); + + var extractors = new Extractor[]{new Blocks(), new Entities(), new EntityStatuses(),}; + + Path outputDirectory; + try { + outputDirectory = Files.createDirectories(Paths.get("valence_extractor_output")); + } catch (IOException e) { + LOGGER.info("Failed to create output directory.", e); + return; + } + + var gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().serializeNulls().create(); + + for (var ext : extractors) { + try { + var out = outputDirectory.resolve(ext.fileName()); + var fileWriter = new FileWriter(out.toFile(), StandardCharsets.UTF_8); + gson.toJson(ext.extract(), fileWriter); + fileWriter.close(); + LOGGER.info("Wrote " + out.toAbsolutePath()); + } catch (Exception e) { + LOGGER.error("Extractor for \"" + ext.fileName() + "\" failed.", e); + } + } + + LOGGER.info("Done."); + System.exit(0); + } + + public interface Extractor { + String fileName(); + + JsonElement extract() throws Exception; + } + + public record Pair(T left, U right) { + } +} diff --git a/extractor/src/main/java/dev/_00a/valence_extractor/Util.java b/extractor/src/main/java/dev/_00a/valence_extractor/Util.java deleted file mode 100644 index 173ae32..0000000 --- a/extractor/src/main/java/dev/_00a/valence_extractor/Util.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev._00a.valence_extractor; - -import sun.reflect.ReflectionFactory; - -public class Util { - /** - * Magically creates an instance of a concrete class without calling its constructor. - */ - public static T magicallyInstantiate(Class clazz) { - var rf = ReflectionFactory.getReflectionFactory(); - try { - var objCon = Object.class.getDeclaredConstructor(); - var con = rf.newConstructorForSerialization(clazz, objCon); - return clazz.cast(con.newInstance()); - } catch (Throwable e) { - throw new IllegalArgumentException("Failed to magically instantiate " + clazz.getName(), e); - } - } -} diff --git a/extractor/src/main/java/dev/_00a/valence_extractor/extractors/Blocks.java b/extractor/src/main/java/dev/_00a/valence_extractor/extractors/Blocks.java new file mode 100644 index 0000000..0202a33 --- /dev/null +++ b/extractor/src/main/java/dev/_00a/valence_extractor/extractors/Blocks.java @@ -0,0 +1,81 @@ +package dev._00a.valence_extractor.extractors; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import dev._00a.valence_extractor.Main; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.EmptyBlockView; + +public class Blocks implements Main.Extractor { + public Blocks() { + } + + @Override + public String fileName() { + return "blocks.json"; + } + + @Override + public JsonElement extract() { + var blocksJson = new JsonArray(); + var stateIdCounter = 0; + + for (var block : Registry.BLOCK) { + var blockJson = new JsonObject(); +// blockJson.addProperty("id", Registry.BLOCK.getRawId(block)); + blockJson.addProperty("translation_key", block.getTranslationKey()); +// blockJson.addProperty("min_state_id", stateIdCounter); +// blockJson.addProperty("max_state_id", stateIdCounter + block.getStateManager().getStates().size() - 1); + + var propsJson = new JsonArray(); + for (var prop : block.getStateManager().getProperties()) { + var propJson = new JsonObject(); + + propJson.addProperty("name", prop.getName()); + + var valuesJson = new JsonArray(); + for (var value : prop.getValues()) { + valuesJson.add(value.toString()); + } + propJson.add("values", valuesJson); + + propsJson.add(propJson); + } + blockJson.add("properties", propsJson); + + var statesJson = new JsonArray(); + for (var state : block.getStateManager().getStates()) { + var stateJson = new JsonObject(); + var id = stateIdCounter++; + stateJson.addProperty("id", id); + stateJson.addProperty("luminance", state.getLuminance()); + stateJson.addProperty("opaque", state.isOpaque()); + + if (block.getDefaultState().equals(state)) { + blockJson.addProperty("default_state_id", id); + } + + var collisionShapesJson = new JsonArray(); + for (var box : state.getCollisionShape(EmptyBlockView.INSTANCE, BlockPos.ORIGIN).getBoundingBoxes()) { + var boxJson = new JsonObject(); + boxJson.addProperty("min_x", box.minX); + boxJson.addProperty("min_y", box.minY); + boxJson.addProperty("min_z", box.minZ); + boxJson.addProperty("max_x", box.maxX); + boxJson.addProperty("max_y", box.maxY); + boxJson.addProperty("max_z", box.maxZ); + collisionShapesJson.add(boxJson); + } + stateJson.add("collision_shapes", collisionShapesJson); + + statesJson.add(stateJson); + } + blockJson.add("states", statesJson); + + blocksJson.add(blockJson); + } + return blocksJson; + } +} diff --git a/extractor/src/main/java/dev/_00a/valence_extractor/Extractor.java b/extractor/src/main/java/dev/_00a/valence_extractor/extractors/Entities.java similarity index 52% rename from extractor/src/main/java/dev/_00a/valence_extractor/Extractor.java rename to extractor/src/main/java/dev/_00a/valence_extractor/extractors/Entities.java index 0b829bc..0b83b83 100644 --- a/extractor/src/main/java/dev/_00a/valence_extractor/Extractor.java +++ b/extractor/src/main/java/dev/_00a/valence_extractor/extractors/Entities.java @@ -1,9 +1,11 @@ -package dev._00a.valence_extractor; +package dev._00a.valence_extractor.extractors; import com.google.gson.*; -import net.fabricmc.api.ModInitializer; +import dev._00a.valence_extractor.DummyPlayerEntity; +import dev._00a.valence_extractor.DummyWorld; +import dev._00a.valence_extractor.Main; +import dev._00a.valence_extractor.Main.Pair; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityStatuses; import net.minecraft.entity.EntityType; import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.data.TrackedData; @@ -19,67 +21,56 @@ import net.minecraft.util.math.GlobalPos; import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.RegistryEntry; import net.minecraft.village.VillagerData; -import net.minecraft.world.EmptyBlockView; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.io.FileWriter; -import java.io.IOException; import java.lang.reflect.ParameterizedType; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashSet; import java.util.Locale; import java.util.Optional; import java.util.OptionalInt; -public class Extractor implements ModInitializer { - public static final String MOD_ID = "valence_extractor"; - public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); - private Gson gson; - private Path outputDirectory; +public class Entities implements Main.Extractor { + public Entities() { + } - private static TD2JResult trackedDataToJson(TrackedData data, DataTracker tracker) { + private static Pair trackedDataToJson(TrackedData data, DataTracker tracker) { final var handler = data.getType(); final var val = tracker.get(data); if (handler == TrackedDataHandlerRegistry.BYTE) { - return new TD2JResult("byte", new JsonPrimitive((Byte) val)); + return new Pair<>("byte", new JsonPrimitive((Byte) val)); } else if (handler == TrackedDataHandlerRegistry.INTEGER) { - return new TD2JResult("integer", new JsonPrimitive((Integer) val)); + return new Pair<>("integer", new JsonPrimitive((Integer) val)); } else if (handler == TrackedDataHandlerRegistry.FLOAT) { - return new TD2JResult("float", new JsonPrimitive((Float) val)); + return new Pair<>("float", new JsonPrimitive((Float) val)); } else if (handler == TrackedDataHandlerRegistry.STRING) { - return new TD2JResult("string", new JsonPrimitive((String) val)); + return new Pair<>("string", new JsonPrimitive((String) val)); } else if (handler == TrackedDataHandlerRegistry.TEXT_COMPONENT) { // TODO: return text as json element. - return new TD2JResult("text_component", new JsonPrimitive(((Text) val).getString())); + return new Pair<>("text_component", new JsonPrimitive(((Text) val).getString())); } else if (handler == TrackedDataHandlerRegistry.OPTIONAL_TEXT_COMPONENT) { var res = ((Optional) val).map(o -> (JsonElement) new JsonPrimitive(((Text) o).getString())).orElse(JsonNull.INSTANCE); - return new TD2JResult("optional_text_component", res); + return new Pair<>("optional_text_component", res); } else if (handler == TrackedDataHandlerRegistry.ITEM_STACK) { // TODO - return new TD2JResult("item_stack", new JsonPrimitive(((ItemStack) val).toString())); + return new Pair<>("item_stack", new JsonPrimitive(((ItemStack) val).toString())); } else if (handler == TrackedDataHandlerRegistry.BOOLEAN) { - return new TD2JResult("boolean", new JsonPrimitive((Boolean) val)); + return new Pair<>("boolean", new JsonPrimitive((Boolean) val)); } else if (handler == TrackedDataHandlerRegistry.ROTATION) { var json = new JsonObject(); var ea = (EulerAngle) val; json.addProperty("pitch", ea.getPitch()); json.addProperty("yaw", ea.getYaw()); json.addProperty("roll", ea.getRoll()); - return new TD2JResult("rotation", json); + return new Pair<>("rotation", json); } else if (handler == TrackedDataHandlerRegistry.BLOCK_POS) { var bp = (BlockPos) val; var json = new JsonObject(); json.addProperty("x", bp.getX()); json.addProperty("y", bp.getY()); json.addProperty("z", bp.getZ()); - return new TD2JResult("block_pos", json); + return new Pair<>("block_pos", json); } else if (handler == TrackedDataHandlerRegistry.OPTIONAL_BLOCK_POS) { - return new TD2JResult("optional_block_pos", ((Optional) val).map(o -> { + return new Pair<>("optional_block_pos", ((Optional) val).map(o -> { var bp = (BlockPos) o; var json = new JsonObject(); json.addProperty("x", bp.getX()); @@ -88,37 +79,37 @@ public class Extractor implements ModInitializer { return (JsonElement) json; }).orElse(JsonNull.INSTANCE)); } else if (handler == TrackedDataHandlerRegistry.FACING) { - return new TD2JResult("facing", new JsonPrimitive(val.toString())); + return new Pair<>("facing", new JsonPrimitive(val.toString())); } else if (handler == TrackedDataHandlerRegistry.OPTIONAL_UUID) { var res = ((Optional) val).map(o -> (JsonElement) new JsonPrimitive(o.toString())).orElse(JsonNull.INSTANCE); - return new TD2JResult("optional_uuid", res); + return new Pair<>("optional_uuid", res); } else if (handler == TrackedDataHandlerRegistry.OPTIONAL_BLOCK_STATE) { // TODO: get raw block state ID. var res = ((Optional) val).map(o -> (JsonElement) new JsonPrimitive(o.toString())).orElse(JsonNull.INSTANCE); - return new TD2JResult("optional_block_state", res); + return new Pair<>("optional_block_state", res); } else if (handler == TrackedDataHandlerRegistry.NBT_COMPOUND) { // TODO: base64 binary representation or SNBT? - return new TD2JResult("nbt_compound", new JsonPrimitive(val.toString())); + return new Pair<>("nbt_compound", new JsonPrimitive(val.toString())); } else if (handler == TrackedDataHandlerRegistry.PARTICLE) { - return new TD2JResult("particle", new JsonPrimitive(((ParticleEffect) val).asString())); + return new Pair<>("particle", new JsonPrimitive(((ParticleEffect) val).asString())); } else if (handler == TrackedDataHandlerRegistry.VILLAGER_DATA) { var vd = (VillagerData) val; var json = new JsonObject(); json.addProperty("type", vd.getType().toString()); json.addProperty("profession", vd.getProfession().toString()); json.addProperty("level", vd.getLevel()); - return new TD2JResult("villager_data", json); + return new Pair<>("villager_data", json); } else if (handler == TrackedDataHandlerRegistry.OPTIONAL_INT) { var opt = (OptionalInt) val; - return new TD2JResult("optional_int", opt.isPresent() ? new JsonPrimitive(opt.getAsInt()) : JsonNull.INSTANCE); + return new Pair<>("optional_int", opt.isPresent() ? new JsonPrimitive(opt.getAsInt()) : JsonNull.INSTANCE); } else if (handler == TrackedDataHandlerRegistry.ENTITY_POSE) { - return new TD2JResult("entity_pose", new JsonPrimitive(val.toString())); + return new Pair<>("entity_pose", new JsonPrimitive(val.toString())); } else if (handler == TrackedDataHandlerRegistry.CAT_VARIANT) { - return new TD2JResult("cat_variant", new JsonPrimitive(Registry.CAT_VARIANT.getId((CatVariant) val).getPath())); + return new Pair<>("cat_variant", new JsonPrimitive(Registry.CAT_VARIANT.getId((CatVariant) val).getPath())); } else if (handler == TrackedDataHandlerRegistry.FROG_VARIANT) { - return new TD2JResult("frog_variant", new JsonPrimitive(Registry.FROG_VARIANT.getId((FrogVariant) val).getPath())); + return new Pair<>("frog_variant", new JsonPrimitive(Registry.FROG_VARIANT.getId((FrogVariant) val).getPath())); } else if (handler == TrackedDataHandlerRegistry.OPTIONAL_GLOBAL_POS) { - return new TD2JResult("optional_global_pos", ((Optional) val).map(o -> { + return new Pair<>("optional_global_pos", ((Optional) val).map(o -> { var gp = (GlobalPos) o; var json = new JsonObject(); json.addProperty("dimension", gp.getDimension().getValue().toString()); @@ -133,96 +124,20 @@ public class Extractor implements ModInitializer { }).orElse(JsonNull.INSTANCE)); } else if (handler == TrackedDataHandlerRegistry.PAINTING_VARIANT) { var variant = ((RegistryEntry) val).getKey().map(k -> k.getValue().getPath()).orElse(""); - return new TD2JResult("painting_variant", new JsonPrimitive(variant)); + return new Pair<>("painting_variant", new JsonPrimitive(variant)); } else { throw new IllegalArgumentException("Unexpected tracked data type " + handler); } } @Override - public void onInitialize() { - LOGGER.info("Starting extractor..."); - - try { - outputDirectory = Files.createDirectories(Paths.get("valence_extractor_output")); - gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().serializeNulls().create(); - - extractBlocks(); - extractEntities(); - extractEntityStatuses(); - } catch (Throwable e) { - LOGGER.error("Extraction failed", e); - System.exit(1); - } - - LOGGER.info("Extractor finished successfully."); - System.exit(0); - } - - private void extractBlocks() throws IOException { - var blocksJson = new JsonArray(); - var stateIdCounter = 0; - - for (var block : Registry.BLOCK) { - var blockJson = new JsonObject(); -// blockJson.addProperty("id", Registry.BLOCK.getRawId(block)); - blockJson.addProperty("translation_key", block.getTranslationKey()); -// blockJson.addProperty("min_state_id", stateIdCounter); -// blockJson.addProperty("max_state_id", stateIdCounter + block.getStateManager().getStates().size() - 1); - - var propsJson = new JsonArray(); - for (var prop : block.getStateManager().getProperties()) { - var propJson = new JsonObject(); - - propJson.addProperty("name", prop.getName()); - - var valuesJson = new JsonArray(); - for (var value : prop.getValues()) { - valuesJson.add(value.toString()); - } - propJson.add("values", valuesJson); - - propsJson.add(propJson); - } - blockJson.add("properties", propsJson); - - var statesJson = new JsonArray(); - for (var state : block.getStateManager().getStates()) { - var stateJson = new JsonObject(); - var id = stateIdCounter++; - stateJson.addProperty("id", id); - stateJson.addProperty("luminance", state.getLuminance()); - stateJson.addProperty("opaque", state.isOpaque()); - - if (block.getDefaultState().equals(state)) { - blockJson.addProperty("default_state_id", id); - } - - var collisionShapesJson = new JsonArray(); - for (var box : state.getCollisionShape(EmptyBlockView.INSTANCE, BlockPos.ORIGIN).getBoundingBoxes()) { - var boxJson = new JsonObject(); - boxJson.addProperty("min_x", box.minX); - boxJson.addProperty("min_y", box.minY); - boxJson.addProperty("min_z", box.minZ); - boxJson.addProperty("max_x", box.maxX); - boxJson.addProperty("max_y", box.maxY); - boxJson.addProperty("max_z", box.maxZ); - collisionShapesJson.add(boxJson); - } - stateJson.add("collision_shapes", collisionShapesJson); - - statesJson.add(stateJson); - } - blockJson.add("states", statesJson); - - blocksJson.add(blockJson); - } - - writeJsonFile("blocks.json", blocksJson); + public String fileName() { + return "entities.json"; } + @Override @SuppressWarnings("unchecked") - private void extractEntities() throws IOException, IllegalAccessException, NoSuchFieldException { + public JsonElement extract() throws IllegalAccessException, NoSuchFieldException { final var entitiesJson = new JsonArray(); final var entityClasses = new HashSet>(); @@ -258,9 +173,9 @@ public class Extractor implements ModInitializer { fieldJson.addProperty("index", data.getId()); var dataTracker = (DataTracker) dataTrackerField.get(entityInstance); - var res = Extractor.trackedDataToJson(data, dataTracker); - fieldJson.addProperty("type", res.type_name); - fieldJson.add("default_value", res.data); + var res = Entities.trackedDataToJson(data, dataTracker); + fieldJson.addProperty("type", res.left()); + fieldJson.add("default_value", res.right()); fieldsJson.add(fieldJson); } @@ -282,34 +197,6 @@ public class Extractor implements ModInitializer { } } - writeJsonFile("entities.json", entitiesJson); - } - - private void extractEntityStatuses() throws IllegalAccessException, IOException { - var statusesJson = new JsonObject(); - - for (var field : EntityStatuses.class.getDeclaredFields()) { - if (field.canAccess(null) && field.get(null) instanceof Byte code) { - if (field.getName().equals("field_30030")) { - // TODO: temp - statusesJson.addProperty("stop_attack", code); - } else { - statusesJson.addProperty(field.getName().toLowerCase(Locale.ROOT), code); - } - } - } - - writeJsonFile("entity_statuses.json", statusesJson); - } - - private void writeJsonFile(String fileName, JsonElement element) throws IOException { - var out = outputDirectory.resolve(fileName); - var fileWriter = new FileWriter(out.toFile(), StandardCharsets.UTF_8); - gson.toJson(element, fileWriter); - fileWriter.close(); - LOGGER.info("Wrote " + out.toAbsolutePath()); - } - - private record TD2JResult(String type_name, JsonElement data) { + return entitiesJson; } } diff --git a/extractor/src/main/java/dev/_00a/valence_extractor/extractors/EntityStatuses.java b/extractor/src/main/java/dev/_00a/valence_extractor/extractors/EntityStatuses.java new file mode 100644 index 0000000..6373dc5 --- /dev/null +++ b/extractor/src/main/java/dev/_00a/valence_extractor/extractors/EntityStatuses.java @@ -0,0 +1,32 @@ +package dev._00a.valence_extractor.extractors; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import dev._00a.valence_extractor.Main; + +import java.util.Locale; + +public class EntityStatuses implements Main.Extractor { + @Override + public String fileName() { + return "entity_statuses.json"; + } + + @Override + public JsonElement extract() throws Exception { + var statusesJson = new JsonObject(); + + for (var field : net.minecraft.entity.EntityStatuses.class.getDeclaredFields()) { + if (field.canAccess(null) && field.get(null) instanceof Byte code) { + if (field.getName().equals("field_30030")) { + // TODO: temp + statusesJson.addProperty("stop_attack", code); + } else { + statusesJson.addProperty(field.getName().toLowerCase(Locale.ROOT), code); + } + } + } + + return statusesJson; + } +} diff --git a/extractor/src/main/resources/fabric.mod.json b/extractor/src/main/resources/fabric.mod.json index cf1e291..8acb97e 100644 --- a/extractor/src/main/resources/fabric.mod.json +++ b/extractor/src/main/resources/fabric.mod.json @@ -14,7 +14,7 @@ "environment": "*", "entrypoints": { "main": [ - "dev._00a.valence_extractor.Extractor" + "dev._00a.valence_extractor.Main" ] }, "mixins": [],