Actually fail CI if generation fails (#30)

* Actually fail unit tests if generation fails

* Do not fail fast

* Fix 1.8.9 spam

* Fix 1.8.9 generation

* Fix 1.8.9 generation

* Fix entity generation on 4 versions

* Fix projectile thrown package

* Disable 1.17 recipe generator
This commit is contained in:
Alex
2024-10-27 15:07:25 +01:00
committed by GitHub
parent 91251bcf06
commit b8e0215b2a
12 changed files with 173 additions and 143 deletions
+1
View File
@@ -10,6 +10,7 @@ on:
jobs:
build:
strategy:
fail-fast: false
matrix:
version: [
"1.7",
@@ -2,6 +2,7 @@ package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.FieldHelper;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
@@ -17,6 +18,7 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;
public class EntitiesDataGenerator implements IDataGenerator {
@@ -42,20 +44,17 @@ public class EntitiesDataGenerator implements IDataGenerator {
}
private static String getCategoryFrom(@NotNull EntityType<?> entityType) {
if (entityType == EntityType.PLAYER) return "other"; // fail early for player entities
Entity entity = entityType.create(DGU.getWorld());
if (entity == null)
throw new IllegalStateException("Entity was null after trying to create a: " + DGU.translateText(entityType.getTranslationKey()));
entity.remove();
return switch (entity.getClass().getPackageName()) {
ParameterizedType entityTypeClass = (ParameterizedType) FieldHelper.findStaticFieldWithValue(EntityType.class, entityType).getGenericType();
Class<?> entityClass = (Class<?>) entityTypeClass.getActualTypeArguments()[0];
return switch (entityClass.getPackageName()) {
case "net.minecraft.entity.decoration", "net.minecraft.entity.decoration.painting" -> "Immobile";
case "net.minecraft.entity.boss", "net.minecraft.entity.mob", "net.minecraft.entity.boss.dragon" ->
"Hostile mobs";
case "net.minecraft.entity.projectile", "net.minecraft.entity.thrown" -> "Projectiles";
case "net.minecraft.entity.passive" -> "Passive mobs";
case "net.minecraft.entity.vehicle" -> "Vehicles";
case "net.minecraft.entity" -> "other";
default -> throw new IllegalStateException("Unexpected entity type: " + entity.getClass().getPackageName());
case "net.minecraft.entity.player", "net.minecraft.entity" -> "other";
default -> throw new IllegalStateException("Unexpected entity type: " + entityClass.getPackageName());
};
}
@@ -2,6 +2,7 @@ package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.FieldHelper;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
@@ -17,6 +18,7 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;
public class EntitiesDataGenerator implements IDataGenerator {
@@ -42,21 +44,17 @@ public class EntitiesDataGenerator implements IDataGenerator {
}
private static String getCategoryFrom(@NotNull EntityType<?> entityType) {
if (entityType == EntityType.PLAYER) return "other"; // fail early for player entities
Entity entity = entityType.create(DGU.getWorld());
if (entity == null)
throw new IllegalStateException("Entity was null after trying to create a: " + DGU.translateText(entityType.getTranslationKey()));
entity.remove();
return switch (entity.getClass().getPackageName()) {
ParameterizedType entityTypeClass = (ParameterizedType) FieldHelper.findStaticFieldWithValue(EntityType.class, entityType).getGenericType();
Class<?> entityClass = (Class<?>) entityTypeClass.getActualTypeArguments()[0];
return switch (entityClass.getPackageName()) {
case "net.minecraft.entity.decoration", "net.minecraft.entity.decoration.painting" -> "Immobile";
case "net.minecraft.entity.boss", "net.minecraft.entity.mob", "net.minecraft.entity.boss.dragon" ->
"Hostile mobs";
case "net.minecraft.entity.projectile", "net.minecraft.entity.projectile.thrown",
"net.minecraft.entity.thrown" -> "Projectiles";
case "net.minecraft.entity.projectile", "net.minecraft.entity.thrown" -> "Projectiles";
case "net.minecraft.entity.passive" -> "Passive mobs";
case "net.minecraft.entity.vehicle" -> "Vehicles";
case "net.minecraft.entity" -> "other";
default -> throw new IllegalStateException("Unexpected entity type: " + entity.getClass().getPackageName());
case "net.minecraft.entity.player", "net.minecraft.entity" -> "other";
default -> throw new IllegalStateException("Unexpected entity type: " + entityClass.getPackageName());
};
}
@@ -2,6 +2,7 @@ package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.FieldHelper;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
@@ -18,6 +19,8 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.ParameterizedType;
public class EntitiesDataGenerator implements IDataGenerator {
public static JsonObject generateEntity(Registry<EntityType<?>> entityRegistry, EntityType<?> entityType) {
JsonObject entityDesc = new JsonObject();
@@ -42,20 +45,17 @@ public class EntitiesDataGenerator implements IDataGenerator {
}
private static String getCategoryFrom(@NotNull EntityType<?> entityType) {
if (entityType == EntityType.PLAYER) return "other"; // fail early for player entities
Entity entity = entityType.create(DGU.getWorld());
if (entity == null)
throw new IllegalStateException("Entity was null after trying to create a: " + DGU.translateText(entityType.getTranslationKey()));
entity.remove();
return switch (entity.getClass().getPackageName()) {
ParameterizedType entityTypeClass = (ParameterizedType) FieldHelper.findStaticFieldWithValue(EntityType.class, entityType).getGenericType();
Class<?> entityClass = (Class<?>) entityTypeClass.getActualTypeArguments()[0];
return switch (entityClass.getPackageName()) {
case "net.minecraft.entity.decoration", "net.minecraft.entity.decoration.painting" -> "Immobile";
case "net.minecraft.entity.boss", "net.minecraft.entity.mob", "net.minecraft.entity.boss.dragon" ->
"Hostile mobs";
case "net.minecraft.entity.projectile", "net.minecraft.entity.projectile.thrown" -> "Projectiles";
case "net.minecraft.entity.passive" -> "Passive mobs";
case "net.minecraft.entity.vehicle" -> "Vehicles";
case "net.minecraft.entity" -> "other";
default -> throw new IllegalStateException("Unexpected entity type: " + entity.getClass().getPackageName());
case "net.minecraft.entity.player", "net.minecraft.entity" -> "other";
default -> throw new IllegalStateException("Unexpected entity type: " + entityClass.getPackageName());
};
}
@@ -2,6 +2,7 @@ package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.FieldHelper;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
@@ -17,6 +18,8 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import java.lang.reflect.ParameterizedType;
public class EntitiesDataGenerator implements IDataGenerator {
public static JsonObject generateEntity(Registry<EntityType<?>> entityRegistry, EntityType<?> entityType) {
JsonObject entityDesc = new JsonObject();
@@ -45,20 +48,17 @@ public class EntitiesDataGenerator implements IDataGenerator {
}
private static String getCategoryFrom(EntityType<?> entityType) {
if (entityType == EntityType.PLAYER) return "UNKNOWN"; // fail early for player entities
Entity entity = EntityType.createInstanceFromId(Registry.ENTITY_TYPE.getRawId(entityType), DGU.getWorld());
if (entity == null)
throw new IllegalStateException("Entity was null after trying to create a: " + DGU.translateText(entityType.getTranslationKey()));
entity.discard();
return switch (entity.getClass().getPackageName()) {
ParameterizedType entityTypeClass = (ParameterizedType) FieldHelper.findStaticFieldWithValue(EntityType.class, entityType).getGenericType();
Class<?> entityClass = (Class<?>) entityTypeClass.getActualTypeArguments()[0];
return switch (entityClass.getPackageName()) {
case "net.minecraft.entity.decoration", "net.minecraft.entity.decoration.painting" -> "Immobile";
case "net.minecraft.entity.boss", "net.minecraft.entity.mob", "net.minecraft.entity.boss.dragon" ->
"Hostile mobs";
case "net.minecraft.entity.projectile", "net.minecraft.entity.projectile.thrown" -> "Projectiles";
case "net.minecraft.entity.passive" -> "Passive mobs";
case "net.minecraft.entity.vehicle" -> "Vehicles";
case "net.minecraft.entity" -> "UNKNOWN";
default -> throw new IllegalStateException("Unexpected entity type: " + entity.getClass().getPackageName());
case "net.minecraft.entity.player", "net.minecraft.entity" -> "other";
default -> throw new IllegalStateException("Unexpected entity type: " + entityClass.getPackageName());
};
}
@@ -1,23 +1,10 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import com.google.gson.JsonNull;
import net.minecraft.item.Item;
import net.minecraft.recipe.Ingredient;
import net.minecraft.recipe.Recipe;
import net.minecraft.recipe.ShapedRecipe;
import net.minecraft.recipe.ShapelessRecipe;
import net.minecraft.util.registry.Registry;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class RecipeDataGenerator implements IDataGenerator {
private static int getRawIdFor(Item item) {
@@ -31,102 +18,108 @@ public class RecipeDataGenerator implements IDataGenerator {
@Override
public JsonElement generateDataJson() {
JsonObject finalObj = new JsonObject();
Multimap<Integer, JsonObject> recipes = ArrayListMultimap.create();
for (Recipe<?> recipe : Objects.requireNonNull(DGU.getWorld()).getRecipeManager().values()) {
if (recipe instanceof ShapedRecipe sr) {
var ingredients = sr.getIngredients();
List<Integer> ingr = new ArrayList<>();
for (int i = 0; i < 9; i++) {
if (i >= ingredients.size()) {
ingr.add(-1);
continue;
}
var stacks = ingredients.get(i);
// var matching = stacks.getMatchingStacks();
// if (matching.length == 0) {
// JsonObject finalObj = new JsonObject();
// Multimap<Integer, JsonObject> recipes = ArrayListMultimap.create();
// for (Recipe<?> recipe : Objects.requireNonNull(DGU.getWorld()).getRecipeManager().values()) {
// if (recipe instanceof ShapedRecipe sr) {
// var ingredients = sr.getIngredients();
// List<Integer> ingr = new ArrayList<>();
// for (int i = 0; i < 9; i++) {
// if (i >= ingredients.size()) {
// ingr.add(-1);
// } else {
// ingr.add(getRawIdFor(matching[0].getItem()));
// continue;
// }
}
Lists.reverse(ingr);
JsonArray inShape = new JsonArray();
var iter = ingr.iterator();
for (int y = 0; y < 3; y++) {
var jsonRow = new JsonArray();
int one = iter.next();
int two = iter.next();
int three = iter.next();
if (y > 0 && one == -1 && two == -1 && three == -1) continue;
jsonRow.add(one);
jsonRow.add(two);
jsonRow.add(three);
inShape.add(jsonRow);
}
JsonObject finalRecipe = new JsonObject();
finalRecipe.add("inShape", inShape);
var resultObject = new JsonObject();
resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem()));
resultObject.addProperty("count", sr.getOutput().getCount());
finalRecipe.add("result", resultObject);
String id = ((Integer) getRawIdFor(sr.getOutput().getItem())).toString();
if (!finalObj.has(id)) {
finalObj.add(id, new JsonArray());
}
finalObj.get(id).getAsJsonArray().add(finalRecipe);
// var input = new JsonArray();
// var ingredients = sr.getIngredients().stream().toList();
// for (int y = 0; y < sr.getHeight(); y++) {
// var arr = new JsonArray();
// for (int x = 0; x < sr.getWidth(); x++) {
// if ((y*3)+x >= ingredients.size()) {
// arr.add(JsonNull.INSTANCE);
// continue;
// }
// var ingredient = ingredients.get((y*3)+x).getMatchingStacks(); // FIXME: fix when there are more than one matching stack
// if (ingredient.length == 0) {
// arr.add(JsonNull.INSTANCE);
// } else {
// arr.add(getRawIdFor(ingredient[0].getItem()));
// }
// }
// input.add(arr);
// var stacks = ingredients.get(i);
//// var matching = stacks.getMatchingStacks();
//// if (matching.length == 0) {
//// ingr.add(-1);
//// } else {
//// ingr.add(getRawIdFor(matching[0].getItem()));
//// }
// }
// var rootRecipeObject = new JsonObject();
// rootRecipeObject.add("inShape", input);
// Lists.reverse(ingr);
//
// JsonArray inShape = new JsonArray();
//
// var iter = ingr.iterator();
// for (int y = 0; y < 3; y++) {
// var jsonRow = new JsonArray();
// int one = iter.next();
// int two = iter.next();
// int three = iter.next();
// if (y > 0 && one == -1 && two == -1 && three == -1) continue;
// jsonRow.add(one);
// jsonRow.add(two);
// jsonRow.add(three);
// inShape.add(jsonRow);
// }
//
// JsonObject finalRecipe = new JsonObject();
// finalRecipe.add("inShape", inShape);
//
// var resultObject = new JsonObject();
// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem()));
// resultObject.addProperty("count", sr.getOutput().getCount());
// finalRecipe.add("result", resultObject);
//
// String id = ((Integer) getRawIdFor(sr.getOutput().getItem())).toString();
//
// if (!finalObj.has(id)) {
// finalObj.add(id, new JsonArray());
// }
// finalObj.get(id).getAsJsonArray().add(finalRecipe);
//// var input = new JsonArray();
//// var ingredients = sr.getIngredients().stream().toList();
//// for (int y = 0; y < sr.getHeight(); y++) {
//// var arr = new JsonArray();
//// for (int x = 0; x < sr.getWidth(); x++) {
//// if ((y*3)+x >= ingredients.size()) {
//// arr.add(JsonNull.INSTANCE);
//// continue;
//// }
//// var ingredient = ingredients.get((y*3)+x).getMatchingStacks(); // FIXME: fix when there are more than one matching stack
//// if (ingredient.length == 0) {
//// arr.add(JsonNull.INSTANCE);
//// } else {
//// arr.add(getRawIdFor(ingredient[0].getItem()));
//// }
//// }
//// input.add(arr);
//// }
//// var rootRecipeObject = new JsonObject();
//// rootRecipeObject.add("inShape", input);
//// var resultObject = new JsonObject();
//// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem()));
//// resultObject.addProperty("count", sr.getOutput().getCount());
//// rootRecipeObject.add("result", resultObject);
//// recipes.put(getRawIdFor(sr.getOutput().getItem()), rootRecipeObject);
// } else if (recipe instanceof ShapelessRecipe sl) {
// var ingredients = new JsonArray();
// for (Ingredient ingredient : sl.getIngredients()) {
// if (ingredient.isEmpty()) continue;
//// ingredients.add(getRawIdFor(ingredient.getMatchingStacks()[0].getItem()));
// }
// var rootRecipeObject = new JsonObject();
// rootRecipeObject.add("ingredients", ingredients);
// var resultObject = new JsonObject();
// resultObject.addProperty("id", getRawIdFor(sl.getOutput().getItem()));
// resultObject.addProperty("count", sl.getOutput().getCount());
// rootRecipeObject.add("result", resultObject);
// recipes.put(getRawIdFor(sr.getOutput().getItem()), rootRecipeObject);
} else if (recipe instanceof ShapelessRecipe sl) {
var ingredients = new JsonArray();
for (Ingredient ingredient : sl.getIngredients()) {
if (ingredient.isEmpty()) continue;
// ingredients.add(getRawIdFor(ingredient.getMatchingStacks()[0].getItem()));
}
var rootRecipeObject = new JsonObject();
rootRecipeObject.add("ingredients", ingredients);
var resultObject = new JsonObject();
resultObject.addProperty("id", getRawIdFor(sl.getOutput().getItem()));
resultObject.addProperty("count", sl.getOutput().getCount());
rootRecipeObject.add("result", resultObject);
recipes.put(getRawIdFor(sl.getOutput().getItem()), rootRecipeObject);
}
}
recipes.forEach((a, b) -> {
if (!finalObj.has(a.toString())) {
finalObj.add(a.toString(), new JsonArray());
}
finalObj.get(a.toString()).getAsJsonArray().add(b);
});
return finalObj;
// recipes.put(getRawIdFor(sl.getOutput().getItem()), rootRecipeObject);
// }
// }
// recipes.forEach((a, b) -> {
// if (!finalObj.has(a.toString())) {
// finalObj.add(a.toString(), new JsonArray());
// }
// finalObj.get(a.toString()).getAsJsonArray().add(b);
// });
// return finalObj;
return JsonNull.INSTANCE;
}
@Override
public boolean isEnabled() {
return false; // TODO: Implement this
}
}
@@ -65,6 +65,8 @@ public class BlockCollisionShapesDataGenerator implements IDataGenerator {
for (BlockState state : block.getStateManager().getBlockStates().reverse()) {
List<Box> boxes = new ArrayList<>();
try {
// Fix needed for StairsBlock because it requires the state of the block to be set
DGU.getWorld().setBlockState(BlockPos.ORIGIN, state);
block.appendCollisionBoxes(DGU.getWorld(), BlockPos.ORIGIN, state, ENTITY_BOX, boxes, null);
} catch (Exception e) {
e.printStackTrace();
@@ -120,6 +120,8 @@ public class BlocksDataGenerator implements IDataGenerator {
}
private static String boundingBox(Block block, BlockState state) {
// Fix needed for StairsBlock because it requires the state of the block to be set
DGU.getWorld().setBlockState(BlockPos.ORIGIN, state);
if (block.getCollisionBox(DGU.getWorld(), BlockPos.ORIGIN, state) == null) {
return "empty";
}
@@ -5,6 +5,7 @@ import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.mixin.EntityTypeAccessor;
import dev.u9g.minecraftdatagenerator.util.DGU;
import dev.u9g.minecraftdatagenerator.util.Registries;
import net.minecraft.block.Blocks;
import net.minecraft.entity.*;
import net.minecraft.entity.mob.AmbientEntity;
import net.minecraft.entity.mob.HostileEntity;
@@ -14,6 +15,7 @@ import net.minecraft.entity.passive.AnimalEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.projectile.FishingBobberEntity;
import net.minecraft.entity.projectile.Projectile;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -31,6 +33,10 @@ public class EntitiesDataGenerator implements IDataGenerator {
entityDesc.addProperty("id", id);
entityDesc.addProperty("internalId", id);
entityDesc.addProperty("name", Objects.requireNonNull(registryKey).getPath());
if (entity instanceof ItemEntity itemEntity) {
// Same as 1.9.4
itemEntity.setItemStack(new ItemStack(Blocks.STONE));
}
String displayName = entity != null ? entity.getTranslationKey() : null;
if (displayName != null && !displayName.startsWith("entity.")) {
entityDesc.addProperty("displayName", displayName);
+12 -4
View File
@@ -1,6 +1,6 @@
# minecraft-data-generator
This tool generates minecraft-data files based on a running client using a fabric mod.
This tool generates minecraft-data files based on a running a server on client classpath using a fabric mod.
The supported versions can be read in the directory structure.
Every version has its own directory.
@@ -26,13 +26,21 @@ You can then find the minecraft-data in the `<version>/run/minecraft-data` direc
## Adding a new version
To add a new version, you need to create a new directory in the root directory with the version name.
Then you need to add the version to the list in the `settings.gradle.kts` file.
Then you need to add the version to the list in the `settings.gradle` file.
Then also add it to the `.github/workflows/build.yml` file.
Then copy the code of the most recently released version into the new directory.
Then you need to change the values in the `gradle.properties` file.
Then copy the code of the most recently released version into the new module directory.
Then you need to change the values in the `build.gradle` file.
At last, you need to fix all code issues that are caused by the new version.
if there are any new curse, treasure, or non-tradeable, or non-discoverable enchantments in the new version, you need to add them manually to the `EnchantmentsDataGenerator` class.
For that, use an IDE like IntelliJ IDEA to fix the issues.
Once everything compiles, you can commit the changes, push them to your fork and create a pull request.
Once your PR was accepted and merged, the new version will be available in the next release.
## Technical info
By configuring Unimined (the mod build tool used by this project) to run the server, the server will be started with the client classpath.
The Minecraft client always contains a copy of the whole server code.
This is so because the integrated server is always bundled with clients and Mojang has always additionally bundled the dedicated server code within the client code.
This way we can access client information and classes while running a modded server environment.
The `common` module shares common mod logic to allow us to deduplicate similar code across versions.
@@ -0,0 +1,21 @@
package dev.u9g.minecraftdatagenerator;
import java.lang.reflect.Field;
public class FieldHelper {
public static Field findStaticFieldWithValue(Class<?> clazz, Object value) {
for (Field field : clazz.getDeclaredFields()) {
try {
field.setAccessible(true);
if (field.get(null).equals(value)) {
return field;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
throw new IllegalArgumentException("No field with value " + value + " found in " + clazz);
}
}
@@ -22,6 +22,6 @@ public class MinecraftDataGenerator {
} else {
MinecraftDataGenerator.LOGGER.severe("Data generation failed!");
}
Runtime.getRuntime().halt(0);
Runtime.getRuntime().halt(success ? 0 : 1);
}
}