🎈 Add version 1.21.8 (#53)

* Add version 1.21.8

* send Change branch from 'main' to 'master'

* 🎈 Fix empty ingredients in 1.21.8 recipe generation by using display API correctly (#54)

* Initial plan

* Fix empty ingredients in 1.21.8 by using sl.getIngredients() instead of sl.display()

Co-authored-by: extremeheat <13713600+extremeheat@users.noreply.github.com>

* Update README.md

* Fix 1.21.8 shapeless recipe ingredients by using display API correctly

- Changed from trying to access sl.ingredients field (package-private) to using sl.display() properly
- Extract ingredients from ShapelessCraftingRecipeDisplay using SlotDisplay.resolveForFirstStack()
- Use SlotDisplayContext.fromLevel() to provide proper context for resolving items
- Successfully generates 295 shapeless recipes with proper ingredient arrays instead of empty arrays
- Tested with full data generation run - recipes.json now contains populated ingredients

Co-authored-by: extremeheat <13713600+extremeheat@users.noreply.github.com>

* Update build.yml

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: extremeheat <13713600+extremeheat@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>

* Rename AGENTS.md to copilot-instructions.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: extremeheat <13713600+extremeheat@users.noreply.github.com>
This commit is contained in:
rom1504bot
2025-09-07 08:33:48 +02:00
committed by GitHub
co-authored by github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> extremeheat Copilot extremeheat
parent 4931a8dadc
commit e359900c40
29 changed files with 1780 additions and 4 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ module.exports = async ([mergedArtifactURL], helpers) => {
owner: 'PrismarineJS', owner: 'PrismarineJS',
repo: 'minecraft-data', repo: 'minecraft-data',
workflow: 'handle-mcpc-generator.yml', workflow: 'handle-mcpc-generator.yml',
branch: 'main', branch: 'master',
inputs: { inputs: {
versions: JSON.stringify(require('../../versions.json')), versions: JSON.stringify(require('../../versions.json')),
mergedArtifactURL, mergedArtifactURL,
+2
View File
@@ -104,6 +104,8 @@ jobs:
path: artifacts/* path: artifacts/*
if-no-files-found: error if-no-files-found: error
- run: cat "$GITHUB_EVENT_PATH"
- name: Sending artifacts to minecraft-data - name: Sending artifacts to minecraft-data
# Check if PR starts with marker # Check if PR starts with marker
if: startsWith(github.event.pull_request.title, env.MARKER) if: startsWith(github.event.pull_request.title, env.MARKER)
+4 -2
View File
@@ -1,7 +1,8 @@
# minecraft-data-generator # minecraft-data-generator
This tool generates minecraft-data files based on a running a server on client classpath 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. The supported versions are enumerated in versions.json.
Every version has its own directory. Every version has its own directory.
## Usage ## Usage
@@ -25,7 +26,8 @@ You can then find the minecraft-data in the `mc/<version>/run/minecraft-data` di
## Adding a new version ## Adding a new version
Generally, our automated PR system will automatically create a new PR for a new version, so you just have to clone the auto opened PR and continue work on it. Generally, our automated PR system will automatically create a new PR for a new version,
so you just have to clone the auto opened PR and continue work on it.
### Manual setup ### Manual setup
+25
View File
@@ -0,0 +1,25 @@
import xyz.wagyourtail.unimined.internal.minecraft.MinecraftProvider
plugins {
id 'dg-mc-conventions'
}
unimined.minecraft {
version "1.21.8"
mappings {
mojmap()
devFallbackNamespace "mojmap"
}
customPatcher(new CustomOfficialFabricMinecraftTransformer(project, delegate as MinecraftProvider)) {
it.loader libs.versions.fabric.loader.get()
}
runs.config("server") {
javaVersion = JavaVersion.VERSION_21
}
defaultRemapJar = true
}
@@ -0,0 +1,38 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.RangedAttribute;
import java.util.Objects;
public class AttributesDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "attributes";
}
@Override
public JsonElement generateDataJson() {
JsonArray arr = new JsonArray();
var registry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ATTRIBUTE);
for (Attribute attribute : registry) {
JsonObject obj = new JsonObject();
String name = Objects.requireNonNull(registry.getKey(attribute)).getPath();
while(name.contains("_")) {
name = name.replaceFirst("_[a-z]", String.valueOf(Character.toUpperCase(name.charAt(name.indexOf("_") + 1))));
}
obj.addProperty("name", name);
obj.addProperty("resource", Objects.requireNonNull(registry.getKey(attribute)).toString());
obj.addProperty("min", ((RangedAttribute) attribute).getMinValue());
obj.addProperty("max", ((RangedAttribute) attribute).getMaxValue());
obj.addProperty("default", attribute.getDefaultValue());
arr.add(obj);
}
return arr;
}
}
@@ -0,0 +1,115 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BiomeTags;
import net.minecraft.world.level.biome.Biome;
public class BiomesDataGenerator implements IDataGenerator {
private static String guessBiomeDimensionFromCategory(Biome biome) {
var biomeRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.BIOME);
if (biomeRegistry.wrapAsHolder(biome).is(BiomeTags.IS_NETHER)) {
return "nether";
} else if (biomeRegistry.wrapAsHolder(biome).is(BiomeTags.IS_END)) {
return "end";
} else {
return "overworld";
}
}
private static String guessCategoryBasedOnName(String name, String dimension) {
if (dimension.equals("nether")) {
return "nether";
} else if (dimension.equals("end")) {
return "the_end";
}
if (name.contains("end")) {
System.out.println();
}
if (name.contains("hills")) {
return "extreme_hills";
} else if (name.contains("ocean")) {
return "ocean";
} else if (name.contains("plains")) {
return "plains";
} else if (name.contains("ice") || name.contains("frozen")) {
return "ice";
} else if (name.contains("jungle")) {
return "jungle";
} else if (name.contains("desert")) {
return "desert";
} else if (name.contains("forest") || name.contains("grove")) {
return "forest";
} else if (name.contains("taiga")) {
return "taiga";
} else if (name.contains("swamp")) {
return "swamp";
} else if (name.contains("river")) {
return "river";
} else if (name.equals("the_end")) {
return "the_end";
} else if (name.contains("mushroom")) {
return "mushroom";
} else if (name.contains("beach") || name.equals("stony_shore")) {
return "beach";
} else if (name.contains("savanna")) {
return "savanna";
} else if (name.contains("badlands")) {
return "mesa";
} else if (name.contains("peaks") || name.equals("snowy_slopes") || name.equals("meadow")) {
return "mountain";
} else if (name.equals("the_void")) {
return "none";
} else if (name.contains("cave") || name.equals("deep_dark")) {
return "underground";
} else {
System.out.println("Unable to find biome category for biome with name: '" + name + "'");
return "none";
}
}
public static JsonObject generateBiomeInfo(Registry<Biome> registry, Biome biome) {
JsonObject biomeDesc = new JsonObject();
ResourceLocation registryKey = registry.getKey(biome);
String localizationKey = String.format("biome.%s.%s", registryKey.getNamespace(), registryKey.getPath());
String name = registryKey.getPath();
biomeDesc.addProperty("id", registry.getId(biome));
biomeDesc.addProperty("name", name);
String dimension = guessBiomeDimensionFromCategory(biome);
biomeDesc.addProperty("category", guessCategoryBasedOnName(name, dimension));
biomeDesc.addProperty("temperature", biome.getBaseTemperature());
//biomeDesc.addProperty("precipitation", biome.getPrecipitation().getName());// - removed in 1.19.4
biomeDesc.addProperty("has_precipitation", biome.hasPrecipitation());
//biomeDesc.addProperty("depth", biome.getDepth()); - Doesn't exist anymore in minecraft source
biomeDesc.addProperty("dimension", dimension);
biomeDesc.addProperty("displayName", DGU.translateText(localizationKey));
biomeDesc.addProperty("color", biome.getSkyColor());
//biomeDesc.addProperty("rainfall", biome.getDownfall());// - removed in 1.19.4
return biomeDesc;
}
@Override
public String getDataName() {
return "biomes";
}
@Override
public JsonArray generateDataJson() {
JsonArray biomesArray = new JsonArray();
RegistryAccess registryManager = DGU.getWorld().registryAccess();
Registry<Biome> biomeRegistry = registryManager.lookupOrThrow(Registries.BIOME);
biomeRegistry.stream()
.map(biome -> generateBiomeInfo(biomeRegistry, biome))
.forEach(biomesArray::add);
return biomesArray;
}
}
@@ -0,0 +1,110 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.VoxelShape;
import java.util.*;
public class BlockCollisionShapesDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "blockCollisionShapes";
}
@Override
public JsonObject generateDataJson() {
Registry<Block> blockRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.BLOCK);
BlockShapesCache blockShapesCache = new BlockShapesCache();
blockRegistry.forEach(blockShapesCache::processBlock);
JsonObject resultObject = new JsonObject();
resultObject.add("blocks", blockShapesCache.dumpBlockShapeIndices(blockRegistry));
resultObject.add("shapes", blockShapesCache.dumpShapesObject());
return resultObject;
}
private static class BlockShapesCache {
public final Map<VoxelShape, Integer> uniqueBlockShapes = new LinkedHashMap<>();
public final Map<Block, List<Integer>> blockCollisionShapes = new LinkedHashMap<>();
private int lastCollisionShapeId = 0;
public void processBlock(Block block) {
List<BlockState> blockStates = block.getStateDefinition().getPossibleStates();
List<Integer> blockCollisionShapes = new ArrayList<>();
for (BlockState blockState : blockStates) {
VoxelShape blockShape = blockState.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
Integer blockShapeIndex = uniqueBlockShapes.get(blockShape);
if (blockShapeIndex == null) {
blockShapeIndex = lastCollisionShapeId++;
uniqueBlockShapes.put(blockShape, blockShapeIndex);
}
blockCollisionShapes.add(blockShapeIndex);
}
this.blockCollisionShapes.put(block, blockCollisionShapes);
}
public JsonObject dumpBlockShapeIndices(Registry<Block> blockRegistry) {
JsonObject resultObject = new JsonObject();
for (var entry : blockCollisionShapes.entrySet()) {
List<Integer> blockCollisions = entry.getValue();
long distinctShapesCount = blockCollisions.stream().distinct().count();
JsonElement blockCollision;
if (distinctShapesCount == 1L) {
blockCollision = new JsonPrimitive(blockCollisions.getFirst());
} else {
blockCollision = new JsonArray();
for (int collisionId : blockCollisions) {
((JsonArray) blockCollision).add(collisionId);
}
}
ResourceLocation registryKey = blockRegistry.getKey(entry.getKey());
resultObject.add(registryKey.getPath(), blockCollision);
}
return resultObject;
}
public JsonObject dumpShapesObject() {
JsonObject shapesObject = new JsonObject();
for (var entry : uniqueBlockShapes.entrySet()) {
JsonArray boxesArray = new JsonArray();
entry.getKey().forAllBoxes((x1, y1, z1, x2, y2, z2) -> {
JsonArray oneBoxJsonArray = new JsonArray();
oneBoxJsonArray.add(x1);
oneBoxJsonArray.add(y1);
oneBoxJsonArray.add(z1);
oneBoxJsonArray.add(x2);
oneBoxJsonArray.add(y2);
oneBoxJsonArray.add(z2);
boxesArray.add(oneBoxJsonArray);
});
shapesObject.add(Integer.toString(entry.getValue()), boxesArray);
}
return shapesObject;
}
}
}
@@ -0,0 +1,201 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.common.base.CaseFormat;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraft.world.level.block.AirBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class BlocksDataGenerator implements IDataGenerator {
private static final Logger logger = LoggerFactory.getLogger(BlocksDataGenerator.class);
private static List<Item> getItemsEffectiveForBlock(BlockState blockState) {
return DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM).stream()
.filter(item -> item.getDefaultInstance().isCorrectToolForDrops(blockState))
.collect(Collectors.toList());
}
private static void populateDropsIfPossible(BlockState blockState, Item firstToolItem, List<ItemStack> outDrops) {
MinecraftServer minecraftServer = DGU.getCurrentlyRunningServer();
if (minecraftServer != null) {
//If we have local world context, we can actually evaluate loot tables and determine actual data
ServerLevel serverWorld = minecraftServer.overworld();
LootParams.Builder lootContextParameterSet = new LootParams.Builder(serverWorld)
.withParameter(LootContextParams.BLOCK_STATE, blockState)
.withParameter(LootContextParams.ORIGIN, Vec3.ZERO)
.withParameter(LootContextParams.TOOL, firstToolItem.getDefaultInstance());
outDrops.addAll(blockState.getDrops(lootContextParameterSet));
} else {
//If we're lacking world context to correctly determine drops, assume that default drop is ItemBlock stack in quantity of 1
Item itemBlock = blockState.getBlock().asItem();
if (itemBlock != Items.AIR) {
outDrops.add(itemBlock.getDefaultInstance());
}
}
}
private static String getPropertyTypeName(Property<?> property) {
//Explicitly handle default minecraft properties
if (property instanceof BooleanProperty) {
return "bool";
}
if (property instanceof IntegerProperty) {
return "int";
}
if (property instanceof EnumProperty) {
return "enum";
}
//Use simple class name as fallback, this code will give something like
//example_type for ExampleTypeProperty class name
String rawPropertyName = property.getClass().getSimpleName().replace("Property", "");
return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, rawPropertyName);
}
private static <T extends Comparable<T>> JsonObject generateStateProperty(Property<T> property) {
JsonObject propertyObject = new JsonObject();
Collection<T> propertyValues = property.getPossibleValues();
propertyObject.addProperty("name", property.getName());
propertyObject.addProperty("type", getPropertyTypeName(property));
propertyObject.addProperty("num_values", propertyValues.size());
//Do not add values for vanilla boolean properties, they are known by default
if (!(property instanceof BooleanProperty)) {
JsonArray propertyValuesArray = new JsonArray();
for (T propertyValue : propertyValues) {
propertyValuesArray.add(property.getName(propertyValue));
}
propertyObject.add("values", propertyValuesArray);
}
return propertyObject;
}
private static String findMatchingBlockMaterial(BlockState blockState, List<MaterialsDataGenerator.MaterialInfo> materials) {
List<MaterialsDataGenerator.MaterialInfo> matchingMaterials = materials.stream()
.filter(material -> material.getPredicate().test(blockState))
.collect(Collectors.toList());
if (matchingMaterials.size() > 1) {
var firstMaterial = matchingMaterials.getFirst();
var otherMaterials = matchingMaterials.subList(1, matchingMaterials.size());
if (!otherMaterials.stream().allMatch(firstMaterial::includesMaterial)) {
logger.error("Block {} matches multiple materials: {}", blockState.getBlock(), matchingMaterials);
}
}
if (matchingMaterials.isEmpty()) {
return "default";
}
return matchingMaterials.getFirst().getMaterialName();
}
public static JsonObject generateBlock(List<MaterialsDataGenerator.MaterialInfo> materials, Block block) {
JsonObject blockDesc = new JsonObject();
Registry<Block> blockRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.BLOCK);
List<BlockState> blockStates = block.getStateDefinition().getPossibleStates();
BlockState defaultState = block.defaultBlockState();
ResourceLocation registryKey = blockRegistry.getKey(block);
String localizationKey = block.getDescriptionId();
List<Item> effectiveTools = getItemsEffectiveForBlock(defaultState);
blockDesc.addProperty("id", blockRegistry.getId(block));
blockDesc.addProperty("name", registryKey.getPath());
blockDesc.addProperty("displayName", DGU.translateText(localizationKey));
blockDesc.addProperty("hardness", block.defaultDestroyTime());
blockDesc.addProperty("resistance", block.getExplosionResistance());
blockDesc.addProperty("stackSize", block.asItem().getDefaultMaxStackSize());
blockDesc.addProperty("diggable", block.defaultDestroyTime() != -1.0f && !(block instanceof AirBlock));
// JsonObject effTools = new JsonObject();
// effectiveTools.forEach(item -> effTools.addProperty(
// String.valueOf(Registry.ITEM.getRawId(item)), // key
// item.getMiningSpeedMultiplier(item.getDefaultStack(), defaultState) // value
// ));
// blockDesc.add("effectiveTools", effTools);
blockDesc.addProperty("material", findMatchingBlockMaterial(defaultState, materials));
blockDesc.addProperty("transparent", !defaultState.canOcclude());
blockDesc.addProperty("emitLight", defaultState.getLightEmission());
blockDesc.addProperty("filterLight", defaultState.getLightBlock());
blockDesc.addProperty("defaultState", Block.getId(defaultState));
blockDesc.addProperty("minStateId", Block.getId(blockStates.getFirst()));
blockDesc.addProperty("maxStateId", Block.getId(blockStates.getLast()));
JsonArray stateProperties = new JsonArray();
for (Property<?> property : block.getStateDefinition().getProperties()) {
stateProperties.add(generateStateProperty(property));
}
blockDesc.add("states", stateProperties);
//Only add harvest tools if tool is required for harvesting this block
if (defaultState.requiresCorrectToolForDrops()) {
Registry<Item> itemRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM);
JsonObject effectiveToolsObject = new JsonObject();
for (Item effectiveItem : effectiveTools) {
effectiveToolsObject.addProperty(Integer.toString(itemRegistry.getId(effectiveItem)), true);
}
blockDesc.add("harvestTools", effectiveToolsObject);
}
List<ItemStack> actualBlockDrops = new ArrayList<>();
populateDropsIfPossible(defaultState, effectiveTools.isEmpty() ? Items.AIR : effectiveTools.getFirst(), actualBlockDrops);
JsonArray dropsArray = new JsonArray();
Registry<Item> itemRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM);
for (ItemStack dropStack : actualBlockDrops) {
dropsArray.add(itemRegistry.getId(dropStack.getItem()));
}
blockDesc.add("drops", dropsArray);
VoxelShape blockCollisionShape = defaultState.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
blockDesc.addProperty("boundingBox", blockCollisionShape.isEmpty() ? "empty" : "block");
return blockDesc;
}
@Override
public String getDataName() {
return "blocks";
}
@Override
public JsonArray generateDataJson() {
JsonArray resultBlocksArray = new JsonArray();
List<MaterialsDataGenerator.MaterialInfo> availableMaterials = MaterialsDataGenerator.getGlobalMaterialInfo();
Registry<Block> blockRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.BLOCK);
blockRegistry.forEach(block -> resultBlocksArray.add(generateBlock(availableMaterials, block)));
return resultBlocksArray;
}
}
@@ -0,0 +1,46 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffects;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.stream.Collectors;
public class EffectsDataGenerator implements IDataGenerator {
public static JsonObject generateEffect(Registry<MobEffect> registry, MobEffect mobEffect) {
JsonObject effectDesc = new JsonObject();
ResourceLocation registryKey = registry.getKey(mobEffect);
effectDesc.addProperty("id", registry.getId(mobEffect));
if (mobEffect == MobEffects.UNLUCK.value()) {
effectDesc.addProperty("name", "BadLuck");
effectDesc.addProperty("displayName", "Bad Luck");
} else {
effectDesc.addProperty("name", Arrays.stream(registryKey.getPath().split("_")).map(StringUtils::capitalize).collect(Collectors.joining()));
effectDesc.addProperty("displayName", DGU.translateText(mobEffect.getDescriptionId()));
}
effectDesc.addProperty("type", mobEffect.isBeneficial() ? "good" : "bad");
return effectDesc;
}
@Override
public String getDataName() {
return "effects";
}
@Override
public JsonArray generateDataJson() {
JsonArray resultsArray = new JsonArray();
Registry<MobEffect> mobEffectRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.MOB_EFFECT);
mobEffectRegistry.forEach(effect -> resultsArray.add(generateEffect(mobEffectRegistry, effect)));
return resultsArray;
}
}
@@ -0,0 +1,105 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.enchantment.Enchantment;
import java.util.List;
public class EnchantmentsDataGenerator implements IDataGenerator {
public static String getEnchantmentTargetName(HolderSet<Item> target) {
TagKey<Item> tagKey = target.unwrapKey().orElseThrow();
return tagKey.location().getPath().split("/")[1];
}
private static boolean isEnchantmentInTag(Enchantment enchantment, String tag) {
return DGU.getWorld()
.registryAccess()
.lookupOrThrow(Registries.ENCHANTMENT)
.getOrThrow(TagKey.create(Registries.ENCHANTMENT, ResourceLocation.parse(tag)))
.stream()
.anyMatch(enchantmentRegistryEntry -> enchantmentRegistryEntry.value() == enchantment);
}
//Equation enchantment costs follow is a * level + b, so we can easily retrieve a and b by passing zero level
private static JsonObject generateEnchantmentMinPowerCoefficients(Enchantment enchantment) {
int b = enchantment.getMinLevel();
int a = enchantment.getMaxLevel() - b;
JsonObject resultObject = new JsonObject();
resultObject.addProperty("a", a);
resultObject.addProperty("b", b);
return resultObject;
}
private static JsonObject generateEnchantmentMaxPowerCoefficients(Enchantment enchantment) {
int b = enchantment.getMinLevel();
int a = enchantment.getMaxLevel() - b;
JsonObject resultObject = new JsonObject();
resultObject.addProperty("a", a);
resultObject.addProperty("b", b);
return resultObject;
}
public static JsonObject generateEnchantment(Registry<Enchantment> registry, Enchantment enchantment) {
JsonObject enchantmentDesc = new JsonObject();
ResourceLocation registryKey = registry.getKey(enchantment);
enchantmentDesc.addProperty("id", registry.getId(enchantment));
enchantmentDesc.addProperty("name", registryKey.getPath());
enchantmentDesc.addProperty("displayName", enchantment.description().getString());
enchantmentDesc.addProperty("maxLevel", enchantment.getMaxLevel());
enchantmentDesc.add("minCost", generateEnchantmentMinPowerCoefficients(enchantment));
enchantmentDesc.add("maxCost", generateEnchantmentMaxPowerCoefficients(enchantment));
enchantmentDesc.addProperty("treasureOnly", isEnchantmentInTag(enchantment, "treasure"));
enchantmentDesc.addProperty("curse", isEnchantmentInTag(enchantment, "curse"));
List<Enchantment> incompatibleEnchantments = registry.stream()
.filter(other -> {
Holder<Enchantment> enchantmentEntry = registry.wrapAsHolder(enchantment);
Holder<Enchantment> otherEntry = registry.wrapAsHolder(other);
return !Enchantment.areCompatible(enchantmentEntry, otherEntry);
})
.filter(other -> other != enchantment)
.toList();
JsonArray excludes = new JsonArray();
for (Enchantment excludedEnchantment : incompatibleEnchantments) {
ResourceLocation otherKey = registry.getKey(excludedEnchantment);
excludes.add(otherKey.getPath());
}
enchantmentDesc.add("exclude", excludes);
enchantmentDesc.addProperty("category", getEnchantmentTargetName(enchantment.definition().supportedItems()));
enchantmentDesc.addProperty("weight", enchantment.definition().weight());
enchantmentDesc.addProperty("tradeable", isEnchantmentInTag(enchantment, "tradeable"));
enchantmentDesc.addProperty("discoverable", isEnchantmentInTag(enchantment, "on_random_loot"));
return enchantmentDesc;
}
@Override
public String getDataName() {
return "enchantments";
}
@Override
public JsonArray generateDataJson() {
JsonArray resultsArray = new JsonArray();
Registry<Enchantment> enchantmentRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ENCHANTMENT);
enchantmentRegistry.stream()
.forEach(enchantment -> resultsArray.add(generateEnchantment(enchantmentRegistry, enchantment)));
return resultsArray;
}
}
@@ -0,0 +1,136 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ambient.AmbientCreature;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.WaterAnimal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.projectile.Projectile;
public class EntitiesDataGenerator implements IDataGenerator {
public static JsonObject generateEntity(Registry<EntityType<?>> entityRegistry, EntityType<?> entityType) {
JsonObject entityDesc = new JsonObject();
ResourceLocation registryKey = entityRegistry.getKey(entityType);
int entityRawId = entityRegistry.getId(entityType);
entityDesc.addProperty("id", entityRawId);
entityDesc.addProperty("internalId", entityRawId);
entityDesc.addProperty("name", registryKey.getPath());
entityDesc.addProperty("displayName", DGU.translateText(entityType.getDescriptionId()));
entityDesc.addProperty("width", entityType.getDimensions().width());
entityDesc.addProperty("height", entityType.getDimensions().height());
String entityTypeString = "UNKNOWN";
MinecraftServer minecraftServer = DGU.getCurrentlyRunningServer();
if (minecraftServer != null) {
Entity entityObject = entityType.create(minecraftServer.overworld(), EntitySpawnReason.NATURAL);
entityTypeString = entityObject != null ? getEntityTypeForClass(entityObject.getClass()) : "unknown";
}
if (entityType == EntityType.PLAYER) {
entityTypeString = "player";
}
entityDesc.addProperty("type", entityTypeString);
entityDesc.addProperty("category", getCategoryFrom(entityType));
return entityDesc;
}
private static String getCategoryFrom(EntityType<?> entityType) {
if (entityType == EntityType.PLAYER) return "UNKNOWN";
Entity entity = entityType.create(DGU.getWorld(), EntitySpawnReason.NATURAL);
if (entity == null)
throw new Error("Entity was null after trying to create a: " + DGU.translateText(entityType.getDescriptionId()));
entity.discard();
String packageName = entity.getClass().getPackageName();
// Use a more flexible approach to handle sub-packages
if (packageName.equals("net.minecraft.world.entity.decoration") ||
packageName.startsWith("net.minecraft.world.entity.decoration.")) {
return "Immobile";
} else if (packageName.equals("net.minecraft.world.entity.boss") ||
packageName.equals("net.minecraft.world.entity.monster") ||
packageName.startsWith("net.minecraft.world.entity.boss.") ||
packageName.startsWith("net.minecraft.world.entity.monster.")) {
return "Hostile mobs";
} else if (packageName.equals("net.minecraft.world.entity.projectile") ||
packageName.startsWith("net.minecraft.world.entity.projectile.")) {
return "Projectiles";
} else if (packageName.equals("net.minecraft.world.entity.animal") ||
packageName.startsWith("net.minecraft.world.entity.animal.")) {
return "Passive mobs";
} else if (packageName.equals("net.minecraft.world.entity.vehicle") ||
packageName.startsWith("net.minecraft.world.entity.vehicle.")) {
return "Vehicles";
} else if (packageName.equals("net.minecraft.world.entity")) {
return "UNKNOWN";
} else {
// Instead of throwing an error, return UNKNOWN for unexpected packages
return "UNKNOWN";
}
}
//Honestly, both "type" and "category" fields in the schema and examples do not contain any useful information
//Since category is optional, I will just leave it out, and for type I will assume general entity classification
//by the Entity class hierarchy (which has some weirdness too by the way)
private static String getEntityTypeForClass(Class<? extends Entity> entityClass) {
//Top-level classifications
if (WaterAnimal.class.isAssignableFrom(entityClass)) {
return "water_creature";
}
if (Animal.class.isAssignableFrom(entityClass)) {
return "animal";
}
if (Monster.class.isAssignableFrom(entityClass)) {
return "hostile";
}
if (AmbientCreature.class.isAssignableFrom(entityClass)) {
return "ambient";
}
//Second level classifications. PathAwareEntity is not included because it
//doesn't really make much sense to categorize by it
if (AgeableMob.class.isAssignableFrom(entityClass)) {
return "passive";
}
if (Mob.class.isAssignableFrom(entityClass)) {
return "mob";
}
//Other classifications only include living entities and projectiles. everything else is categorized as other
if (LivingEntity.class.isAssignableFrom(entityClass)) {
return "living";
}
if (Projectile.class.isAssignableFrom(entityClass)) {
return "projectile";
}
return "other";
}
@Override
public String getDataName() {
return "entities";
}
@Override
public JsonArray generateDataJson() {
JsonArray resultArray = new JsonArray();
Registry<EntityType<?>> entityTypeRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ENTITY_TYPE);
entityTypeRegistry.forEach(entity -> resultArray.add(generateEntity(entityTypeRegistry, entity)));
return resultArray;
}
}
@@ -0,0 +1,52 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.Item;
import java.util.Objects;
public class FoodsDataGenerator implements IDataGenerator {
public static JsonObject generateFoodDescriptor(Registry<Item> registry, Item foodItem) {
JsonObject foodDesc = new JsonObject();
ResourceLocation registryKey = registry.getKey(foodItem);
foodDesc.addProperty("id", registry.getId(foodItem));
foodDesc.addProperty("name", registryKey.getPath());
foodDesc.addProperty("stackSize", foodItem.getDefaultMaxStackSize());
foodDesc.addProperty("displayName", DGU.translateText(foodItem.getDescriptionId()));
FoodProperties foodComponent = Objects.requireNonNull(foodItem.components().get(DataComponents.FOOD));
float foodPoints = foodComponent.nutrition();
float saturationRatio = foodComponent.saturation() * 2.0F;
float saturation = foodPoints * saturationRatio;
foodDesc.addProperty("foodPoints", foodPoints);
foodDesc.addProperty("saturation", saturation);
foodDesc.addProperty("effectiveQuality", foodPoints + saturation);
foodDesc.addProperty("saturationRatio", saturationRatio);
return foodDesc;
}
@Override
public String getDataName() {
return "foods";
}
public JsonArray generateDataJson() {
JsonArray resultsArray = new JsonArray();
Registry<Item> itemRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM);
itemRegistry.stream()
.filter(i -> i.components().has(DataComponents.FOOD))
.forEach(food -> resultsArray.add(generateFoodDescriptor(itemRegistry, food)));
return resultsArray;
}
}
@@ -0,0 +1,25 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
public class InstrumentsDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "instruments";
}
@Override
public JsonElement generateDataJson() {
JsonArray array = new JsonArray();
for (NoteBlockInstrument instrument : NoteBlockInstrument.values()) {
JsonObject object = new JsonObject();
object.addProperty("id", instrument.ordinal());
object.addProperty("name", instrument.getSerializedName());
array.add(object);
}
return array;
}
}
@@ -0,0 +1,81 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantment;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class ItemsDataGenerator implements IDataGenerator {
private static List<Item> calculateItemsToRepairWith(Registry<Item> itemRegistry, Item sourceItem) {
ItemStack sourceItemStack = new ItemStack(sourceItem);
return itemRegistry.stream()
.filter(otherItem -> sourceItemStack.isValidRepairItem(new ItemStack(otherItem)))
.collect(Collectors.toList());
}
public static JsonObject generateItem(Registry<Item> itemRegistry, Item item) {
JsonObject itemDesc = new JsonObject();
ResourceLocation registryKey = itemRegistry.getKey(item);
itemDesc.addProperty("id", itemRegistry.getId(item));
itemDesc.addProperty("name", registryKey.getPath());
itemDesc.addProperty("displayName", DGU.translateText(item.getDescriptionId()));
itemDesc.addProperty("stackSize", item.getDefaultMaxStackSize());
JsonArray enchantCategoriesArray = new JsonArray();
DGU.getWorld().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).stream()
.map(Enchantment::getSupportedItems)
.filter(applicableItems -> applicableItems.contains(itemRegistry.wrapAsHolder(item)))
.map(EnchantmentsDataGenerator::getEnchantmentTargetName)
.distinct()
.forEach(enchantCategoriesArray::add);
if (enchantCategoriesArray.size() > 0) {
itemDesc.add("enchantCategories", enchantCategoriesArray);
}
if (item.components().has(DataComponents.MAX_DAMAGE)) {
List<Item> repairWithItems = calculateItemsToRepairWith(itemRegistry, item);
JsonArray fixedWithArray = new JsonArray();
for (Item repairWithItem : repairWithItems) {
ResourceLocation repairWithName = itemRegistry.getKey(repairWithItem);
fixedWithArray.add(repairWithName.getPath());
}
if (fixedWithArray.size() > 0) {
itemDesc.add("repairWith", fixedWithArray);
}
int maxDurability = Objects.requireNonNull(item.components().get(DataComponents.MAX_DAMAGE));
itemDesc.addProperty("maxDurability", maxDurability);
}
return itemDesc;
}
@Override
public String getDataName() {
return "items";
}
@Override
public JsonArray generateDataJson() {
JsonArray resultArray = new JsonArray();
Registry<Item> itemRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM);
itemRegistry.stream().forEach(item -> resultArray.add(generateItem(itemRegistry, item)));
return resultArray;
}
}
@@ -0,0 +1,27 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class LanguageDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "language";
}
@Override
public JsonElement generateDataJson() {
try {
InputStream inputStream = Objects.requireNonNull(this.getClass().getResourceAsStream("/assets/minecraft/lang/en_us.json"));
return new Gson().fromJson(new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonObject.class);
} catch (Exception ignored) {
}
throw new RuntimeException("Failed to generate language file");
}
}
@@ -0,0 +1,239 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
//TODO entire idea of linking materials to tool speeds is obsolete and just wrong now,
//TODO but we kinda have to support it to let old code work for computing digging times,
//TODO so for now we will handle materials as "virtual" ones based on which tools can break blocks
public class MaterialsDataGenerator implements IDataGenerator {
private static final List<ImmutableList<String>> COMPOSITE_MATERIALS = ImmutableList.<ImmutableList<String>>builder()
.add(ImmutableList.of("plant", makeMaterialNameForTag(BlockTags.MINEABLE_WITH_AXE)))
.add(ImmutableList.of("gourd", makeMaterialNameForTag(BlockTags.MINEABLE_WITH_AXE)))
.add(ImmutableList.of(makeMaterialNameForTag(BlockTags.LEAVES), makeMaterialNameForTag(BlockTags.MINEABLE_WITH_HOE)))
.add(ImmutableList.of(makeMaterialNameForTag(BlockTags.LEAVES), makeMaterialNameForTag(BlockTags.MINEABLE_WITH_AXE), makeMaterialNameForTag(BlockTags.MINEABLE_WITH_HOE)))
.add(ImmutableList.of("vine_or_glow_lichen", "plant", makeMaterialNameForTag(BlockTags.MINEABLE_WITH_AXE)
)).build();
private static final Map<String, Float> TOOL_SPEEDS = new HashMap<>() {{
// Base speeds for each tool type
put("wooden", 2.0f);
put("stone", 4.0f);
put("iron", 6.0f);
put("diamond", 8.0f);
put("netherite", 9.0f);
put("golden", 12.0f);
}};
private static Float getToolSpeed(Item item) {
String itemName = item.toString().toLowerCase();
// Remove minecraft: prefix if present
if (itemName.startsWith("minecraft:")) {
itemName = itemName.substring("minecraft:".length());
}
for (Map.Entry<String, Float> entry : TOOL_SPEEDS.entrySet()) {
if (itemName.startsWith(entry.getKey())) {
return entry.getValue();
}
}
return 1.0f;
}
private static String makeMaterialNameForTag(TagKey<Block> tag) {
return tag.location().getPath();
}
private static void createCompositeMaterialInfo(List<MaterialInfo> allMaterials, List<String> combinedMaterials) {
String compositeMaterialName = String.join(";", combinedMaterials);
List<MaterialInfo> mappedMaterials = combinedMaterials.stream()
.map(otherName -> allMaterials.stream()
.filter(other -> other.getMaterialName().equals(otherName))
.findFirst().orElseThrow(() -> new RuntimeException("Material not found with name " + otherName)))
.collect(Collectors.toList());
Predicate<BlockState> compositePredicate = blockState ->
mappedMaterials.stream().allMatch(it -> it.getPredicate().test(blockState));
MaterialInfo materialInfo = new MaterialInfo(compositeMaterialName, compositePredicate).includes(mappedMaterials);
allMaterials.addFirst(materialInfo);
}
private static void createCompositeMaterial(Map<String, Map<Item, Float>> allMaterials, List<String> combinedMaterials) {
String compositeMaterialName = String.join(";", combinedMaterials);
Map<Item, Float> resultingToolSpeeds = new LinkedHashMap<>();
combinedMaterials.stream()
.map(allMaterials::get)
.forEach(resultingToolSpeeds::putAll);
allMaterials.put(compositeMaterialName, resultingToolSpeeds);
}
public static List<MaterialInfo> getGlobalMaterialInfo() {
ArrayList<MaterialInfo> resultList = new ArrayList<>();
resultList.add(new MaterialInfo("vine_or_glow_lichen", blockState -> blockState.is(Blocks.VINE) || blockState.is(Blocks.GLOW_LICHEN)));
resultList.add(new MaterialInfo("coweb", blockState -> blockState.is(Blocks.COBWEB)));
resultList.add(new MaterialInfo("leaves", blockState -> blockState.is(BlockTags.LEAVES)));
resultList.add(new MaterialInfo("wool", blockState -> blockState.is(BlockTags.WOOL)));
// Block Materials were removed in 1.20 in favor of block tags
resultList.add(new MaterialInfo("gourd", blockState -> blockState.is(Blocks.MELON) || blockState.is(Blocks.PUMPKIN) || blockState.is(Blocks.JACK_O_LANTERN)));
// 'sword_efficient' tag is for all plants, and includes everything from the old PLANT and REPLACEABLE_PLANT materials (see https://minecraft.fandom.com/wiki/Tag#Blocks)
resultList.add(new MaterialInfo("plant", blockState -> blockState.is(BlockTags.SWORD_EFFICIENT)));
HashSet<String> uniqueMaterialNames = new HashSet<>();
Registry<Item> itemRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM);
itemRegistry.forEach(item -> {
if (item.components().get(DataComponents.TOOL) != null) {
item.components().get(DataComponents.TOOL).rules()
.stream().map(rule -> rule.blocks())
.forEach(blocks -> {
Optional<TagKey<Block>> tagKey = blocks.unwrapKey();
if (tagKey.isPresent()) {
String materialName = makeMaterialNameForTag((tagKey.get()));
if (!uniqueMaterialNames.contains(materialName)) {
uniqueMaterialNames.add(materialName);
resultList.add(new MaterialInfo(materialName, blockState -> blockState.is(blocks)));
}
}
});
}
});
COMPOSITE_MATERIALS.forEach(values -> createCompositeMaterialInfo(resultList, values));
return resultList;
}
@Override
public String getDataName() {
return "materials";
}
@Override
public JsonElement generateDataJson() {
Registry<Item> itemRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM);
Map<String, Map<Item, Float>> materialMiningSpeeds = new LinkedHashMap<>();
materialMiningSpeeds.put("default", new LinkedHashMap<>());
//Special materials used for shears and swords special mining speed logic
Map<Item, Float> leavesMaterialSpeeds = new LinkedHashMap<>();
Map<Item, Float> cowebMaterialSpeeds = new LinkedHashMap<>();
Map<Item, Float> plantMaterialSpeeds = new LinkedHashMap<>();
Map<Item, Float> gourdMaterialSpeeds = new LinkedHashMap<>();
materialMiningSpeeds.put(makeMaterialNameForTag(BlockTags.LEAVES), leavesMaterialSpeeds);
materialMiningSpeeds.put("coweb", cowebMaterialSpeeds);
materialMiningSpeeds.put("plant", plantMaterialSpeeds);
materialMiningSpeeds.put("gourd", gourdMaterialSpeeds);
//Shears need special handling because they do not follow normal rules like tools
leavesMaterialSpeeds.put(Items.SHEARS, 15.0f);
cowebMaterialSpeeds.put(Items.SHEARS, 15.0f);
Map<Item, Float> vineOrGlowLichenSpeeds = new LinkedHashMap<>();
vineOrGlowLichenSpeeds.put(Items.SHEARS, 2.0f);
materialMiningSpeeds.put("vine_or_glow_lichen", vineOrGlowLichenSpeeds);
Map<Item, Float> woolSpeeds = new LinkedHashMap<>();
woolSpeeds.put(Items.SHEARS, 5.0f);
materialMiningSpeeds.put("wool", woolSpeeds);
itemRegistry.forEach(item -> {
//Tools are handled rather easily and do not require anything else
if (item.components().get(DataComponents.TOOL) != null) {
item.components().get(DataComponents.TOOL).rules()
.stream().map(rule -> rule.blocks())
.forEach(blocks -> {
Optional<TagKey<Block>> tagKey = blocks.unwrapKey();
if (tagKey.isPresent()) {
String materialName = makeMaterialNameForTag(tagKey.get());
Map<Item, Float> materialSpeeds = materialMiningSpeeds.computeIfAbsent(materialName, k -> new LinkedHashMap<>());
float baseSpeed = getToolSpeed(item);
materialSpeeds.put(item, baseSpeed);
}
}
);
//Swords require special treatment
if (itemRegistry.getKey(item).getPath().contains("sword")) {
cowebMaterialSpeeds.put(item, 15.0f);
plantMaterialSpeeds.put(item, 1.5f);
leavesMaterialSpeeds.put(item, 1.5f);
gourdMaterialSpeeds.put(item, 1.5f);
}
}});
COMPOSITE_MATERIALS.forEach(values -> createCompositeMaterial(materialMiningSpeeds, values));
JsonObject resultObject = new JsonObject();
for (var entry : materialMiningSpeeds.entrySet()) {
JsonObject toolSpeedsObject = new JsonObject();
for (var toolEntry : entry.getValue().entrySet()) {
int rawItemId = itemRegistry.getId(toolEntry.getKey());
toolSpeedsObject.addProperty(Integer.toString(rawItemId), toolEntry.getValue());
}
resultObject.add(entry.getKey(), toolSpeedsObject);
}
return resultObject;
}
public static class MaterialInfo {
private final String materialName;
private final Predicate<BlockState> predicate;
private final List<MaterialInfo> includedMaterials = new ArrayList<>();
public MaterialInfo(String materialName, Predicate<BlockState> predicate) {
this.materialName = materialName;
this.predicate = predicate;
}
protected MaterialInfo includes(List<MaterialInfo> otherMaterials) {
this.includedMaterials.addAll(otherMaterials);
return this;
}
public String getMaterialName() {
return materialName;
}
public Predicate<BlockState> getPredicate() {
return predicate;
}
public boolean includesMaterial(MaterialInfo materialInfo) {
return includedMaterials.contains(materialInfo);
}
@Override
public String toString() {
return materialName;
}
}
}
@@ -0,0 +1,33 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Registry;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
public class ParticlesDataGenerator implements IDataGenerator {
public static JsonObject generateParticleType(Registry<ParticleType<?>> registry, ParticleType<?> particleType) {
JsonObject effectDesc = new JsonObject();
ResourceLocation registryKey = registry.getKey(particleType);
effectDesc.addProperty("id", registry.getId(particleType));
effectDesc.addProperty("name", registryKey.getPath());
return effectDesc;
}
@Override
public String getDataName() {
return "particles";
}
@Override
public JsonArray generateDataJson() {
JsonArray resultsArray = new JsonArray();
Registry<ParticleType<?>> particleTypeRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.PARTICLE_TYPE);
particleTypeRegistry.forEach(particleType -> resultsArray.add(generateParticleType(particleTypeRegistry, particleType)));
return resultsArray;
}
}
@@ -0,0 +1,132 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.common.collect.ArrayListMultimap;
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 net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay;
import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
import net.minecraft.util.context.ContextMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class RecipeDataGenerator implements IDataGenerator {
private static int getRawIdFor(Item item) {
return DGU.getWorld().registryAccess().lookupOrThrow(Registries.ITEM).getId(item);
}
@Override
public String getDataName() {
return "recipes";
}
@Override
public JsonElement generateDataJson() {
RegistryAccess registryManager = DGU.getWorld().registryAccess();
JsonObject finalObj = new JsonObject();
Multimap<Integer, JsonObject> recipes = ArrayListMultimap.create();
for (RecipeHolder<?> recipeE : Objects.requireNonNull(DGU.getWorld()).getServer().getRecipeManager().getRecipes()) {
Recipe<?> recipe = recipeE.value();
if (recipe instanceof ShapedRecipe sr) {
generateShapedRecipe(registryManager, finalObj, sr, 0);
} else if (recipe instanceof ShapelessRecipe sl) {
var ingredients = new JsonArray();
var displays = sl.display();
if (!displays.isEmpty() && displays.get(0) instanceof ShapelessCraftingRecipeDisplay shapelessDisplay) {
for (SlotDisplay slotDisplay : shapelessDisplay.ingredients()) {
var itemStack = slotDisplay.resolveForFirstStack(SlotDisplayContext.fromLevel(DGU.getWorld()));
if (!itemStack.isEmpty()) {
ingredients.add(getRawIdFor(itemStack.getItem()));
}
}
}
var rootRecipeObject = new JsonObject();
rootRecipeObject.add("ingredients", ingredients);
var resultObject = new JsonObject();
resultObject.addProperty("id", getRawIdFor(sl.assemble(CraftingInput.EMPTY, registryManager).getItem()));
resultObject.addProperty("count", sl.assemble(CraftingInput.EMPTY, registryManager).getCount());
rootRecipeObject.add("result", resultObject);
recipes.put(getRawIdFor(sl.assemble(CraftingInput.EMPTY, registryManager).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;
}
private void generateShapedRecipe(RegistryAccess registryManager, JsonObject finalObj, ShapedRecipe sr, int n) {
boolean hasIncremented = false;
var ingredients = sr.getIngredients();
List<Integer> ingr = new ArrayList<>();
for (int i = 0; i < 9; i++) {
if (i >= ingredients.size()) {
ingr.add(null);
continue;
}
var stacks = ingredients.get(i);
if (stacks.isEmpty()) {
ingr.add(null);
continue;
}
var matchingList = stacks.get().items().toList();
if (matchingList.isEmpty()) {
ingr.add(null);
continue;
} else {
// we already have matchingList from above
if (matchingList.size() > n) {
ingr.add(getRawIdFor(matchingList.get(n).value()));
} else {
ingr.add(getRawIdFor(matchingList.get(0).value()));
}
if (matchingList.size() - 1 > n && !hasIncremented) {
generateShapedRecipe(registryManager, finalObj, sr, n+1);
hasIncremented = true;
}
}
}
JsonArray inShape = new JsonArray();
var iter = ingr.iterator();
for (int y = 0; y < sr.getHeight(); y++) {
var jsonRow = new JsonArray();
for (int z = 0; z < sr.getWidth(); z++) {
jsonRow.add(iter.next());
}
inShape.add(jsonRow);
}
JsonObject finalRecipe = new JsonObject();
finalRecipe.add("inShape", inShape);
var resultObject = new JsonObject();
resultObject.addProperty("id", getRawIdFor(sr.assemble(CraftingInput.EMPTY, registryManager).getItem()));
resultObject.addProperty("count", sr.assemble(CraftingInput.EMPTY, registryManager).getCount());
finalRecipe.add("result", resultObject);
String id = ((Integer) getRawIdFor(sr.assemble(CraftingInput.EMPTY, registryManager).getItem())).toString();
if (!finalObj.has(id)) {
finalObj.add(id, new JsonArray());
}
finalObj.get(id).getAsJsonArray().add(finalRecipe);
}
}
@@ -0,0 +1,33 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.sounds.SoundEvent;
public class SoundsDataGenerator implements IDataGenerator {
public static JsonObject generateSound(SoundEvent soundEvent) {
JsonObject soundDesc = new JsonObject();
soundDesc.addProperty("id", BuiltInRegistries.SOUND_EVENT.getId(soundEvent) + 1); // the plus 1 is required for 1.19.2+ due to Mojang using 0 in the packet to say that you should read a string id instead.
soundDesc.addProperty("name", soundEvent.location().getPath());
return soundDesc;
}
@Override
public String getDataName() {
return "sounds";
}
@Override
public JsonArray generateDataJson() {
JsonArray resultsArray = new JsonArray();
Registry<SoundEvent> soundEventRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.SOUND_EVENT);
soundEventRegistry.forEach(sound -> resultsArray.add(generateSound(sound)));
return resultsArray;
}
}
@@ -0,0 +1,166 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import dev.u9g.minecraftdatagenerator.util.DGU;
import dev.u9g.minecraftdatagenerator.util.EmptyRenderBlockView;
import net.minecraft.client.color.block.BlockColors;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.FoliageColor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RedStoneWireBlock;
import java.util.*;
public class TintsDataGenerator implements IDataGenerator {
public static BiomeTintColors generateBiomeTintColors(Registry<Biome> biomeRegistry) {
BiomeTintColors colors = new BiomeTintColors();
biomeRegistry.forEach(biome -> {
int biomeGrassColor = biome.getGrassColor(0.0, 0.0);
int biomeFoliageColor = biome.getFoliageColor();
int biomeWaterColor = biome.getWaterColor();
colors.grassColoursMap.computeIfAbsent(biomeGrassColor, k -> new ArrayList<>()).add(biome);
colors.foliageColoursMap.computeIfAbsent(biomeFoliageColor, k -> new ArrayList<>()).add(biome);
colors.waterColourMap.computeIfAbsent(biomeWaterColor, k -> new ArrayList<>()).add(biome);
});
return colors;
}
public static Map<Integer, Integer> generateRedstoneTintColors() {
Map<Integer, Integer> resultColors = new LinkedHashMap<>();
for (int redstoneLevel : RedStoneWireBlock.POWER.getPossibleValues()) {
// Remove the unintended alpha channel from the redstone tint color
int color = removeAlphaChannel(RedStoneWireBlock.getColorForPower(redstoneLevel));
resultColors.put(redstoneLevel, color);
}
return resultColors;
}
private static int removeAlphaChannel(int color) {
float r = (float) (color >> 16 & 0xFF) / 255;
float g = (float) (color >> 8 & 0xFF) / 255;
float b = (float) (color & 0xFF) / 255;
return ((int)(r * 255) << 16) | ((int)(g * 255) << 8) | (int)(b * 255);
}
private static int getBlockColor(Block block) {
return BlockColors.createDefault().getColor(block.defaultBlockState(), EmptyRenderBlockView.INSTANCE, BlockPos.ZERO, 0xFFFFFF);
}
public static Map<Block, Integer> generateConstantTintColors() {
Map<Block, Integer> resultColors = new LinkedHashMap<>();
resultColors.put(Blocks.BIRCH_LEAVES, FoliageColor.FOLIAGE_BIRCH);
resultColors.put(Blocks.SPRUCE_LEAVES, FoliageColor.FOLIAGE_EVERGREEN);
resultColors.put(Blocks.LILY_PAD, getBlockColor(Blocks.LILY_PAD));
resultColors.put(Blocks.ATTACHED_MELON_STEM, getBlockColor(Blocks.ATTACHED_MELON_STEM));
resultColors.put(Blocks.ATTACHED_PUMPKIN_STEM, getBlockColor(Blocks.ATTACHED_PUMPKIN_STEM));
//not really constant, depend on the block age, but kinda have to be handled since textures are literally white without them
resultColors.put(Blocks.MELON_STEM, getBlockColor(Blocks.MELON_STEM));
resultColors.put(Blocks.PUMPKIN_STEM, getBlockColor(Blocks.PUMPKIN_STEM));
return resultColors;
}
private static JsonObject encodeBiomeColorMap(Registry<Biome> biomeRegistry, Map<Integer, List<Biome>> colorsMap) {
JsonArray resultColorsArray = new JsonArray();
for (var entry : colorsMap.entrySet()) {
JsonObject entryObject = new JsonObject();
JsonArray keysArray = new JsonArray();
for (Biome biome : entry.getValue()) {
ResourceLocation registryKey = biomeRegistry.getKey(biome);
keysArray.add(registryKey.getPath());
}
entryObject.add("keys", keysArray);
entryObject.addProperty("color", entry.getKey());
resultColorsArray.add(entryObject);
}
JsonObject resultObject = new JsonObject();
resultObject.add("data", resultColorsArray);
return resultObject;
}
private static JsonObject encodeRedstoneColorMap(Map<Integer, Integer> colorsMap) {
JsonArray resultColorsArray = new JsonArray();
for (var entry : colorsMap.entrySet()) {
JsonObject entryObject = new JsonObject();
JsonArray keysArray = new JsonArray();
keysArray.add(entry.getKey());
entryObject.add("keys", keysArray);
entryObject.addProperty("color", entry.getValue());
resultColorsArray.add(entryObject);
}
JsonObject resultObject = new JsonObject();
resultObject.add("data", resultColorsArray);
return resultObject;
}
private static JsonObject encodeBlocksColorMap(Registry<Block> blockRegistry, Map<Block, Integer> colorsMap) {
JsonArray resultColorsArray = new JsonArray();
for (var entry : colorsMap.entrySet()) {
JsonObject entryObject = new JsonObject();
JsonArray keysArray = new JsonArray();
ResourceLocation registryKey = blockRegistry.getKey(entry.getKey());
keysArray.add(registryKey.getPath());
entryObject.add("keys", keysArray);
entryObject.addProperty("color", entry.getValue());
resultColorsArray.add(entryObject);
}
JsonObject resultObject = new JsonObject();
resultObject.add("data", resultColorsArray);
return resultObject;
}
@Override
public String getDataName() {
return "tints";
}
@Override
public JsonObject generateDataJson() {
RegistryAccess registryManager = DGU.getWorld().registryAccess();
Registry<Biome> biomeRegistry = registryManager.lookupOrThrow(Registries.BIOME);
Registry<Block> blockRegistry = registryManager.lookupOrThrow(Registries.BLOCK);
BiomeTintColors biomeTintColors = generateBiomeTintColors(biomeRegistry);
Map<Integer, Integer> redstoneColors = generateRedstoneTintColors();
Map<Block, Integer> constantTintColors = generateConstantTintColors();
JsonObject resultObject = new JsonObject();
resultObject.add("grass", encodeBiomeColorMap(biomeRegistry, biomeTintColors.grassColoursMap));
resultObject.add("foliage", encodeBiomeColorMap(biomeRegistry, biomeTintColors.foliageColoursMap));
resultObject.add("water", encodeBiomeColorMap(biomeRegistry, biomeTintColors.waterColourMap));
resultObject.add("redstone", encodeRedstoneColorMap(redstoneColors));
resultObject.add("constant", encodeBlocksColorMap(blockRegistry, constantTintColors));
return resultObject;
}
public static class BiomeTintColors {
final Map<Integer, List<Biome>> grassColoursMap = new LinkedHashMap<>();
final Map<Integer, List<Biome>> foliageColoursMap = new LinkedHashMap<>();
final Map<Integer, List<Biome>> waterColourMap = new LinkedHashMap<>();
}
}
@@ -0,0 +1,36 @@
/*
* SoulFire
* Copyright (C) 2024 AlexProgrammerDE
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package dev.u9g.minecraftdatagenerator.mixin;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.world.flag.FeatureFlags;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(PackRepository.class)
public class DataPackSettingsMixin {
@Inject(method = "getSelectedIds", at = @At("HEAD"), cancellable = true)
public void getEnabled(CallbackInfoReturnable<List<String>> cir) {
cir.setReturnValue(FeatureFlags.REGISTRY.toNames(FeatureFlags.REGISTRY.allFlags()).stream().map(ResourceLocation::getPath).toList());
}
}
@@ -0,0 +1,15 @@
package dev.u9g.minecraftdatagenerator.mixin;
import net.minecraft.server.Eula;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Eula.class)
public class EULAMixin {
@Inject(method = "hasAgreedToEULA()Z", at = @At("TAIL"), cancellable = true)
public void init(CallbackInfoReturnable<Boolean> cir) {
cir.setReturnValue(true);
}
}
@@ -0,0 +1,22 @@
package dev.u9g.minecraftdatagenerator.mixin;
import dev.u9g.minecraftdatagenerator.MinecraftDataGenerator;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.SharedConstants;
import net.minecraft.server.dedicated.DedicatedServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(DedicatedServer.class)
public class ReadyMixin {
@Inject(method = "initServer()Z", at = @At("TAIL"))
private void init(CallbackInfoReturnable<Boolean> cir) {
MinecraftDataGenerator.start(
SharedConstants.VERSION_STRING,
DGU.getCurrentlyRunningServer().getServerDirectory()
);
}
}
@@ -0,0 +1,21 @@
package dev.u9g.minecraftdatagenerator.util;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.locale.Language;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.Level;
public class DGU {
@SuppressWarnings("deprecation")
public static MinecraftServer getCurrentlyRunningServer() {
return (MinecraftServer) FabricLoader.getInstance().getGameInstance();
}
public static String translateText(String translationKey) {
return Language.getInstance().getOrDefault(translationKey);
}
public static Level getWorld() {
return getCurrentlyRunningServer().overworld();
}
}
@@ -0,0 +1,77 @@
package dev.u9g.minecraftdatagenerator.util;
import dev.u9g.minecraftdatagenerator.util.DGU;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSpecialEffects.GrassColorModifier;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.Nullable;
public enum EmptyRenderBlockView implements BlockAndTintGetter {
INSTANCE;
@Nullable
public BlockEntity getBlockEntity(BlockPos pos) {
return null;
}
public BlockState getBlockState(BlockPos pos) {
return Blocks.AIR.defaultBlockState();
}
public FluidState getFluidState(BlockPos pos) {
return Fluids.EMPTY.defaultFluidState();
}
public int getBottomY() {
return 0;
}
@Override
public int getHeight() {
return 384;
}
@Override
public float getShade(Direction direction, boolean shaded) {
return 0.0f;
}
@Override
public LevelLightEngine getLightEngine() {
return null;
}
@Override
public int getBlockTint(BlockPos pos, net.minecraft.world.level.ColorResolver colorResolver) {
Registry<Biome> biomeRegistry = DGU.getWorld().registryAccess().lookupOrThrow(Registries.BIOME);
Biome plainsBiome = biomeRegistry.get(Biomes.PLAINS).orElse(null).value();
return colorResolver.getColor(plainsBiome, pos.getX(), pos.getY());
}
@Override
public int getBrightness(LightLayer type, BlockPos pos) {
return type == LightLayer.SKY ? 15 : 0;
}
public int getBaseLightLevel(BlockPos pos, int ambientDarkness) {
return ambientDarkness;
}
@Override
public int getMinY() {
return -64;
}
}
@@ -0,0 +1,20 @@
{
"schemaVersion": 1,
"id": "minecraft-data-generator",
"version": "${version}",
"name": "Minecraft Data Generator",
"description": "",
"authors": [
"Archengius",
"U9G"
],
"contact": {},
"license": "MIT",
"mixins": [
"minecraft-data-generator.mixins.json"
],
"depends": {
"fabricloader": "*",
"minecraft": "*"
}
}
@@ -0,0 +1,16 @@
{
"required": true,
"minVersion": "0.8",
"package": "dev.u9g.minecraftdatagenerator.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"DataPackSettingsMixin",
"EULAMixin",
"ReadyMixin"
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}
+2 -1
View File
@@ -21,5 +21,6 @@
"1.21.3", "1.21.3",
"1.21.5", "1.21.5",
"1.21.6", "1.21.6",
"1.21.7" "1.21.7",
"1.21.8"
] ]