Compare commits

...
12 Commits
Author SHA1 Message Date
adman 4b6708a806 forgor the second thingy
Java CI with Gradle / define-versions (push) Successful in 11s
Java CI with Gradle / Build ${{ fromJSON(needs.define-versions.outputs.versions) }} (push) Failing after 47m4s
Java CI with Gradle / Repackage versions (push) Skipped
2026-07-28 14:18:32 +02:00
adman bfbf4ba647 changes
Java CI with Gradle / define-versions (push) Successful in 11s
Java CI with Gradle / Build ${{ fromJSON(needs.define-versions.outputs.versions) }} (push) Failing after 1m26s
Java CI with Gradle / Repackage versions (push) Skipped
2026-07-28 14:16:10 +02:00
adman 7593a2d2d8 Add multi_noise_biome_source_parameter_list generator for 1.21.5-1.21.11
Java CI with Gradle / define-versions (push) Successful in 12s
Java CI with Gradle / Build ${{ fromJSON(needs.define-versions.outputs.versions) }} (push) Failing after 7m41s
Java CI with Gradle / Repackage versions (push) Skipped
2026-07-11 22:00:35 +02:00
DatArnoGuyandGitHub 99ca789213 Fix 1.21.5 - 1.21.11 saturationRatio (#75)
* Fix food saturation for 1.21.4 - 1.21.11

* Revert "Merge branch 'PrismarineJS:main' into main"

This reverts commit ce5b588c750b250777805ca2953cf2aa08a52556, reversing
changes made to 52505caf9a147d6a89e38b7fad14cf02885e74ca.

* Revert "Fix food saturation for 1.21.4 - 1.21.11 (#73)"

This reverts commit 80ce09d3a9.

* Reapply "Fix food saturation for 1.21.4 - 1.21.11 (#73)"

This reverts commit 01a2e1ce83522eccfcf0cde418fe24dfe3e6dc0f.

* Reapply "Merge branch 'PrismarineJS:main' into main"

This reverts commit 7a829644230f0f5d4f0d2586160f4d48f3af66f8.

* Fix 1.21.5 - 1.21.11 saturationRatio
2026-07-09 09:29:12 -04:00
NoClipMonsterandGitHub 4b8d344721 1.20.5-1.21 - Prioritize mineable material when multiple matches are found to fix problem of wrong dig time computation (#71)
* Enhance BlocksDataGenerator to prioritize mineable material when multiple matches are found to fix problem of wrong dig time computation

* Ported the changes from the previous commit to all versions that require this fix.

* Forgot to add 1.21.9
2026-07-09 02:19:43 -04:00
DatArnoGuyandGitHub 80ce09d3a9 Fix food saturation for 1.21.4 - 1.21.11 (#73) 2026-07-09 02:17:27 -04:00
Paul FulhamandGitHub f1381cdff3 Improve agent instructions (#69) 2026-02-22 20:42:51 -05:00
rom1504botGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>CopilotextremeheatSuperGamerTronPaul Fulham
5c22820841 🎈 Add version 1.21.11 (#64)
* Add version 1.21.11

* Fix 1.21.11 compilation errors from Minecraft API changes (#65)

* Initial plan

* Fix 1.21.11 compilation: Replace ResourceLocation with Identifier and update WaterAnimal/getSkyColor

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

* Add type-safe instanceof check before casting sky color argument

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

---------

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

* Fix incorrect biome color representation (#66)

* Fix water_creature entities no longer being classified correctly (#67)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: extremeheat <13713600+extremeheat@users.noreply.github.com>
Co-authored-by: SuperGamerTron <45374546+SuperGamerTron@users.noreply.github.com>
Co-authored-by: Paul Fulham <paul.fulham0@gmail.com>
2026-02-19 03:56:48 -05:00
extremeheatandGitHub a72d0a7ec2 🎈 1.21.10 (#63)
* Add version '1.12.10'

* bump: Prevent destructive action when multiple versions pile
2025-12-23 20:51:36 -05:00
rom1504botGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>Copilot
894e187411 🎈 Add version 1.21.9 (#58)
* Add version 1.21.9

* Support Minecraft 1.21.9: Replace deprecated VERSION_STRING with getCurrentVersion().name() (#59)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2025-10-04 16:34:05 +02:00
extremeheatandGitHub e988c56352 Update copilot-instructions.md
Updated instructions for retrieving additional Minecraft versions from the repository.
2025-09-25 07:55:15 -04:00
rom1504botGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>extremeheatCopilotextremeheat
e359900c40 🎈 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>
2025-09-07 08:33:48 +02:00
145 changed files with 9704 additions and 27 deletions
+6
View File
@@ -20,6 +20,12 @@ If the PR is **not** passing:
`
module.exports = async ([newVersion], helpers) => {
const existingPr = await gh.findPullRequest({ titleIncludes: '🎈', status: 'open' })
if (existingPr) {
console.warn('There is already an open PR; cannot create a new one until that one is closed.', existingPr)
await gh.comment(existingPr.number, `Cannot open new PR for version '${newVersion}' as this one is already open. Maintainers: Once this is merged, please manually re-run the update workflow for the new version (${newVersion}).`)
return
}
try {
bump(newVersion)
try { exec('git branch -D bump') } catch (e) { console.log('No existing branch to delete; ok.') }
+1 -1
View File
@@ -6,7 +6,7 @@ module.exports = async ([mergedArtifactURL], helpers) => {
owner: 'PrismarineJS',
repo: 'minecraft-data',
workflow: 'handle-mcpc-generator.yml',
branch: 'main',
branch: 'master',
inputs: {
versions: JSON.stringify(require('../../versions.json')),
mergedArtifactURL,
@@ -12,8 +12,8 @@ Generated data ends up under `mc/<version>/run/server/minecraft-data` in the rep
- mc/ — per-Minecraft-version directories of OUR extractor code (e.g. `mc/1.21.8`)
- versions.json — canonical JSON array of versions that are supported (i.e. there are folders in mc/ for it)
- mc-source/<version> — (External) minecraft java edition code for relevant version with Mojang mappings that you can reference for changes.
* we store the last 2 most recent versions in the folder to save space, but if you need more versions you can do `cd mc-source/1.21.3 && git fetch origin client1.21.3 && git switch client1.21.3` (as these folders are a repo cloned with specific branches)
* there is a .diff file in mc-source/ like `1.21.7_to_1.21.8.diff` that contains result of `git diff --no-index old new` that you can reference for changes (note it's typically large)
* we store the last 2 most recent versions in the folder to save space, but if you need more versions you can do `cd mc-source && git ls-remote 'https://github.com/extremeheat/extracted_minecraft_data' && git clone 'https://github.com/extremeheat/extracted_minecraft_data' --depth 1 -b client1.20.3 1.20.3 && cd 1.20.3`
* there is a .diff file in mc-source/ like `1.21.7_to_1.21.8.diff` that contains result of `git diff --no-index -M50% -l2000 old new` that you can reference for changes (note it's typically large)
* Since our generator is using Mojang mappings, the API naming is the same.
- README.md — info how to set up
@@ -30,9 +30,17 @@ and figure out how to replicate, debug and fix the logical issues in data genera
## Troubleshooting
Simply iterating over errors one by one is a good way to fix most issues. Inside mc-source/ you are provided the latest
code for the latest minecraft versions you can reference to investigate any code changes that we may need to accomodate in our
generator code.
Simply iterating over errors one by one is a good way to fix most issues. The `mc-source/` directory contains the two
most recent Minecraft versions (e.g. `mc-source/1.21.10/` and `mc-source/1.21.11/`) plus a diff file between them.
When searching for code, always target a specific version directory (e.g. search in `mc-source/1.21.11/` not `mc-source/`)
to avoid clutter and confusion.
Importantly, before assuming a class/method was removed, check the diff file for renames first. Renames/moves appear as:
```
rename from net/minecraft/world/entity/animal/WaterAnimal.java
rename to net/minecraft/world/entity/animal/fish/WaterAnimal.java
```
If not found in the diff (rename detection has a 2000 file limit), search the specific version directory to locate the class.
Sometimes, some APIs might change inside the mc code and make the data that the current code extracts no longer
valid. In these cases, do your best to conform the new data to the old structure even if it feels wrong. If you
@@ -44,8 +52,8 @@ You can propose a new schema to the user (so that the relevant changes can be ma
- Reproduce the problem locally inside the container and run the generator task.
- Example run command: `./gradlew :mc:1.21.6:runServer --stacktrace`
- See if the problem exists on an older version -- if so inform the user, otherwise compare logs and code
- Inspect diffs in `mc-source/` to correlate Mojang API changes.
- See mc-source/
- Inspect diffs in `mc-source/` for API changes, renames, and moves
- Search the specific version directory (e.g. `mc-source/1.21.11/`) to locate moved/renamed classes
- Validate generated output at `mc/<version>/run/server/minecraft-data` to make sure all files are there
---
+4 -2
View File
@@ -54,7 +54,7 @@ jobs:
uses: actions/checkout@v4
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v4
uses: gradle/actions/wrapper-validation@v3
- name: Set up JDK 21
uses: actions/setup-java@v4
@@ -63,7 +63,7 @@ jobs:
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v3
with:
add-job-summary-as-pr-comment: on-failure
@@ -104,6 +104,8 @@ jobs:
path: artifacts/*
if-no-files-found: error
- run: cat "$GITHUB_EVENT_PATH"
- name: Sending artifacts to minecraft-data
# Check if PR starts with marker
if: startsWith(github.event.pull_request.title, env.MARKER)
+1 -1
View File
@@ -71,6 +71,6 @@ jobs:
- name: Write diff
# NOTE: this returns exit code 1 if there are differences
run: git diff --no-index ${{ steps.sb.outputs.second_latest_version }} ${{ steps.sb.outputs.latest_version }} > ${{ steps.sb.outputs.second_latest_version }}_to_${{ steps.sb.outputs.latest_version }}.diff
run: git diff --no-index -M50% -l2000 ${{ steps.sb.outputs.second_latest_version }} ${{ steps.sb.outputs.latest_version }} > ${{ steps.sb.outputs.second_latest_version }}_to_${{ steps.sb.outputs.latest_version }}.diff
working-directory: mc-source
continue-on-error: true
+2 -1
View File
@@ -1,5 +1,6 @@
# User-specific stuff
.idea/
.jdks/
*.iml
*.ipr
@@ -121,4 +122,4 @@ bin/
mc-source/
node_modules
package-lock.json
package-lock.json
+4 -2
View File
@@ -1,7 +1,8 @@
# minecraft-data-generator
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.
## Usage
@@ -25,7 +26,8 @@ You can then find the minecraft-data in the `mc/<version>/run/minecraft-data` di
## 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
@@ -115,7 +115,11 @@ public class BlocksDataGenerator implements IDataGenerator {
if (matchingMaterials.isEmpty()) {
return "default";
}
return matchingMaterials.getFirst().getMaterialName();
return matchingMaterials.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> matchingMaterials.getFirst().getMaterialName());
}
public static JsonObject generateBlock(List<MaterialsDataGenerator.MaterialInfo> materials, Block block) {
+25
View File
@@ -0,0 +1,25 @@
import xyz.wagyourtail.unimined.internal.minecraft.MinecraftProvider
plugins {
id 'dg-mc-conventions'
}
unimined.minecraft {
version "1.21.10"
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,205 @@
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.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> 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 saturation = foodComponent.saturation();
float saturationRatio = saturation / foodPoints;
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,92 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList;
import java.lang.reflect.Method;
import java.util.Map;
public class MultiNoiseBiomeSourceParameterListDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "multi_noise_biome_source_parameter_list";
}
@Override
public boolean isEnabled() {
// Only available in 1.18+
try {
Class.forName("net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public JsonObject generateDataJson() {
JsonObject root = new JsonObject();
Map<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> presets =
MultiNoiseBiomeSourceParameterList.knownPresets();
for (Map.Entry<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> entry : presets.entrySet()) {
String presetName = entry.getKey().id().toString();
JsonArray entries = new JsonArray();
for (Pair<Climate.ParameterPoint, ResourceKey<Biome>> pair : entry.getValue().values()) {
Climate.ParameterPoint params = pair.getFirst();
ResourceKey<Biome> biomeKey = pair.getSecond();
JsonObject item = new JsonObject();
item.addProperty("biome", resourceKeyLocation(biomeKey).toString());
item.add("parameters", serializeParameterPoint(params));
entries.add(item);
}
root.add(presetName, entries);
}
return root;
}
private static JsonObject serializeParameterPoint(Climate.ParameterPoint params) {
JsonObject obj = new JsonObject();
obj.add("temperature", serializeParameter(params.temperature()));
obj.add("humidity", serializeParameter(params.humidity()));
obj.add("continentalness", serializeParameter(params.continentalness()));
obj.add("erosion", serializeParameter(params.erosion()));
obj.add("depth", serializeParameter(params.depth()));
obj.add("weirdness", serializeParameter(params.weirdness()));
obj.addProperty("offset", params.offset() / 10000.0f);
return obj;
}
private static JsonArray serializeParameter(Climate.Parameter parameter) {
JsonArray arr = new JsonArray();
arr.add(parameter.min());
arr.add(parameter.max());
return arr;
}
private static Object resourceKeyLocation(ResourceKey<?> key) {
try {
Method location = key.getClass().getMethod("location");
return location.invoke(key);
} catch (NoSuchMethodException e) {
try {
Method identifier = key.getClass().getMethod("identifier");
return identifier.invoke(key);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -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.util.context.ContextMap;
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 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.getCurrentVersion().name(),
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
}
}
+25
View File
@@ -0,0 +1,25 @@
import xyz.wagyourtail.unimined.internal.minecraft.MinecraftProvider
plugins {
id 'dg-mc-conventions'
}
unimined.minecraft {
version "1.21.11"
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,124 @@
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.Identifier;
import net.minecraft.tags.BiomeTags;
import net.minecraft.world.attribute.EnvironmentAttributes;
import net.minecraft.world.attribute.EnvironmentAttributeMap;
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();
Identifier 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));
// In 1.21.11, sky color moved to EnvironmentAttributes
EnvironmentAttributeMap.Entry<Integer, ?> skyColorEntry = biome.getAttributes().get(EnvironmentAttributes.SKY_COLOR);
int skyColor = 0;
if (skyColorEntry != null && skyColorEntry.argument() instanceof Integer) {
// convert to RGB
skyColor = (Integer) skyColorEntry.argument() & 0xFFFFFF;
}
biomeDesc.addProperty("color", skyColor);
//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.Identifier;
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);
}
}
Identifier 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,205 @@
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.Identifier;
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.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> 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();
Identifier 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.Identifier;
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();
Identifier 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.Identifier;
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, Identifier.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();
Identifier 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) {
Identifier 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.Identifier;
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.fish.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();
Identifier 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.Identifier;
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();
Identifier 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 saturation = foodComponent.saturation();
float saturationRatio = saturation / foodPoints;
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.Identifier;
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();
Identifier 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) {
Identifier 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,92 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList;
import java.lang.reflect.Method;
import java.util.Map;
public class MultiNoiseBiomeSourceParameterListDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "multi_noise_biome_source_parameter_list";
}
@Override
public boolean isEnabled() {
// Only available in 1.18+
try {
Class.forName("net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public JsonObject generateDataJson() {
JsonObject root = new JsonObject();
Map<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> presets =
MultiNoiseBiomeSourceParameterList.knownPresets();
for (Map.Entry<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> entry : presets.entrySet()) {
String presetName = entry.getKey().id().toString();
JsonArray entries = new JsonArray();
for (Pair<Climate.ParameterPoint, ResourceKey<Biome>> pair : entry.getValue().values()) {
Climate.ParameterPoint params = pair.getFirst();
ResourceKey<Biome> biomeKey = pair.getSecond();
JsonObject item = new JsonObject();
item.addProperty("biome", resourceKeyLocation(biomeKey).toString());
item.add("parameters", serializeParameterPoint(params));
entries.add(item);
}
root.add(presetName, entries);
}
return root;
}
private static JsonObject serializeParameterPoint(Climate.ParameterPoint params) {
JsonObject obj = new JsonObject();
obj.add("temperature", serializeParameter(params.temperature()));
obj.add("humidity", serializeParameter(params.humidity()));
obj.add("continentalness", serializeParameter(params.continentalness()));
obj.add("erosion", serializeParameter(params.erosion()));
obj.add("depth", serializeParameter(params.depth()));
obj.add("weirdness", serializeParameter(params.weirdness()));
obj.addProperty("offset", params.offset() / 10000.0f);
return obj;
}
private static JsonArray serializeParameter(Climate.Parameter parameter) {
JsonArray arr = new JsonArray();
arr.add(parameter.min());
arr.add(parameter.max());
return arr;
}
private static Object resourceKeyLocation(ResourceKey<?> key) {
try {
Method location = key.getClass().getMethod("location");
return location.invoke(key);
} catch (NoSuchMethodException e) {
try {
Method identifier = key.getClass().getMethod("identifier");
return identifier.invoke(key);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -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.Identifier;
public class ParticlesDataGenerator implements IDataGenerator {
public static JsonObject generateParticleType(Registry<ParticleType<?>> registry, ParticleType<?> particleType) {
JsonObject effectDesc = new JsonObject();
Identifier 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.util.context.ContextMap;
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 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.Identifier;
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()) {
Identifier 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();
Identifier 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.Identifier;
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(Identifier::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.getCurrentVersion().name(),
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
}
}
@@ -115,7 +115,11 @@ public class BlocksDataGenerator implements IDataGenerator {
if (matchingMaterials.isEmpty()) {
return "default";
}
return matchingMaterials.getFirst().getMaterialName();
return matchingMaterials.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> matchingMaterials.getFirst().getMaterialName());
}
public static JsonObject generateBlock(List<MaterialsDataGenerator.MaterialInfo> materials, Block block) {
@@ -115,7 +115,11 @@ public class BlocksDataGenerator implements IDataGenerator {
if (matchingMaterials.isEmpty()) {
return "default";
}
return matchingMaterials.getFirst().getMaterialName();
return matchingMaterials.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> matchingMaterials.getFirst().getMaterialName());
}
public static JsonObject generateBlock(List<MaterialsDataGenerator.MaterialInfo> materials, Block block) {
@@ -25,8 +25,8 @@ public class FoodsDataGenerator implements IDataGenerator {
FoodProperties foodComponent = Objects.requireNonNull(foodItem.components().get(DataComponents.FOOD));
float foodPoints = foodComponent.nutrition();
float saturationRatio = foodComponent.saturation() * 2.0F;
float saturation = foodPoints * saturationRatio;
float saturation = foodComponent.saturation();
float saturationRatio = saturation / foodPoints;
foodDesc.addProperty("foodPoints", foodPoints);
foodDesc.addProperty("saturation", saturation);
@@ -0,0 +1,92 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList;
import java.lang.reflect.Method;
import java.util.Map;
public class MultiNoiseBiomeSourceParameterListDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "multi_noise_biome_source_parameter_list";
}
@Override
public boolean isEnabled() {
// Only available in 1.18+
try {
Class.forName("net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public JsonObject generateDataJson() {
JsonObject root = new JsonObject();
Map<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> presets =
MultiNoiseBiomeSourceParameterList.knownPresets();
for (Map.Entry<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> entry : presets.entrySet()) {
String presetName = entry.getKey().id().toString();
JsonArray entries = new JsonArray();
for (Pair<Climate.ParameterPoint, ResourceKey<Biome>> pair : entry.getValue().values()) {
Climate.ParameterPoint params = pair.getFirst();
ResourceKey<Biome> biomeKey = pair.getSecond();
JsonObject item = new JsonObject();
item.addProperty("biome", resourceKeyLocation(biomeKey).toString());
item.add("parameters", serializeParameterPoint(params));
entries.add(item);
}
root.add(presetName, entries);
}
return root;
}
private static JsonObject serializeParameterPoint(Climate.ParameterPoint params) {
JsonObject obj = new JsonObject();
obj.add("temperature", serializeParameter(params.temperature()));
obj.add("humidity", serializeParameter(params.humidity()));
obj.add("continentalness", serializeParameter(params.continentalness()));
obj.add("erosion", serializeParameter(params.erosion()));
obj.add("depth", serializeParameter(params.depth()));
obj.add("weirdness", serializeParameter(params.weirdness()));
obj.addProperty("offset", params.offset() / 10000.0f);
return obj;
}
private static JsonArray serializeParameter(Climate.Parameter parameter) {
JsonArray arr = new JsonArray();
arr.add(parameter.min());
arr.add(parameter.max());
return arr;
}
private static Object resourceKeyLocation(ResourceKey<?> key) {
try {
Method location = key.getClass().getMethod("location");
return location.invoke(key);
} catch (NoSuchMethodException e) {
try {
Method identifier = key.getClass().getMethod("identifier");
return identifier.invoke(key);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -115,7 +115,11 @@ public class BlocksDataGenerator implements IDataGenerator {
if (matchingMaterials.isEmpty()) {
return "default";
}
return matchingMaterials.getFirst().getMaterialName();
return matchingMaterials.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> matchingMaterials.getFirst().getMaterialName());
}
public static JsonObject generateBlock(List<MaterialsDataGenerator.MaterialInfo> materials, Block block) {
@@ -25,8 +25,8 @@ public class FoodsDataGenerator implements IDataGenerator {
FoodProperties foodComponent = Objects.requireNonNull(foodItem.components().get(DataComponents.FOOD));
float foodPoints = foodComponent.nutrition();
float saturationRatio = foodComponent.saturation() * 2.0F;
float saturation = foodPoints * saturationRatio;
float saturation = foodComponent.saturation();
float saturationRatio = saturation / foodPoints;
foodDesc.addProperty("foodPoints", foodPoints);
foodDesc.addProperty("saturation", saturation);
@@ -0,0 +1,92 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList;
import java.lang.reflect.Method;
import java.util.Map;
public class MultiNoiseBiomeSourceParameterListDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "multi_noise_biome_source_parameter_list";
}
@Override
public boolean isEnabled() {
// Only available in 1.18+
try {
Class.forName("net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public JsonObject generateDataJson() {
JsonObject root = new JsonObject();
Map<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> presets =
MultiNoiseBiomeSourceParameterList.knownPresets();
for (Map.Entry<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> entry : presets.entrySet()) {
String presetName = entry.getKey().id().toString();
JsonArray entries = new JsonArray();
for (Pair<Climate.ParameterPoint, ResourceKey<Biome>> pair : entry.getValue().values()) {
Climate.ParameterPoint params = pair.getFirst();
ResourceKey<Biome> biomeKey = pair.getSecond();
JsonObject item = new JsonObject();
item.addProperty("biome", resourceKeyLocation(biomeKey).toString());
item.add("parameters", serializeParameterPoint(params));
entries.add(item);
}
root.add(presetName, entries);
}
return root;
}
private static JsonObject serializeParameterPoint(Climate.ParameterPoint params) {
JsonObject obj = new JsonObject();
obj.add("temperature", serializeParameter(params.temperature()));
obj.add("humidity", serializeParameter(params.humidity()));
obj.add("continentalness", serializeParameter(params.continentalness()));
obj.add("erosion", serializeParameter(params.erosion()));
obj.add("depth", serializeParameter(params.depth()));
obj.add("weirdness", serializeParameter(params.weirdness()));
obj.addProperty("offset", params.offset() / 10000.0f);
return obj;
}
private static JsonArray serializeParameter(Climate.Parameter parameter) {
JsonArray arr = new JsonArray();
arr.add(parameter.min());
arr.add(parameter.max());
return arr;
}
private static Object resourceKeyLocation(ResourceKey<?> key) {
try {
Method location = key.getClass().getMethod("location");
return location.invoke(key);
} catch (NoSuchMethodException e) {
try {
Method identifier = key.getClass().getMethod("identifier");
return identifier.invoke(key);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -115,7 +115,11 @@ public class BlocksDataGenerator implements IDataGenerator {
if (matchingMaterials.isEmpty()) {
return "default";
}
return matchingMaterials.getFirst().getMaterialName();
return matchingMaterials.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> matchingMaterials.getFirst().getMaterialName());
}
public static JsonObject generateBlock(List<MaterialsDataGenerator.MaterialInfo> materials, Block block) {
@@ -25,8 +25,8 @@ public class FoodsDataGenerator implements IDataGenerator {
FoodProperties foodComponent = Objects.requireNonNull(foodItem.components().get(DataComponents.FOOD));
float foodPoints = foodComponent.nutrition();
float saturationRatio = foodComponent.saturation() * 2.0F;
float saturation = foodPoints * saturationRatio;
float saturation = foodComponent.saturation();
float saturationRatio = saturation / foodPoints;
foodDesc.addProperty("foodPoints", foodPoints);
foodDesc.addProperty("saturation", saturation);
@@ -0,0 +1,92 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList;
import java.lang.reflect.Method;
import java.util.Map;
public class MultiNoiseBiomeSourceParameterListDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "multi_noise_biome_source_parameter_list";
}
@Override
public boolean isEnabled() {
// Only available in 1.18+
try {
Class.forName("net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public JsonObject generateDataJson() {
JsonObject root = new JsonObject();
Map<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> presets =
MultiNoiseBiomeSourceParameterList.knownPresets();
for (Map.Entry<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> entry : presets.entrySet()) {
String presetName = entry.getKey().id().toString();
JsonArray entries = new JsonArray();
for (Pair<Climate.ParameterPoint, ResourceKey<Biome>> pair : entry.getValue().values()) {
Climate.ParameterPoint params = pair.getFirst();
ResourceKey<Biome> biomeKey = pair.getSecond();
JsonObject item = new JsonObject();
item.addProperty("biome", resourceKeyLocation(biomeKey).toString());
item.add("parameters", serializeParameterPoint(params));
entries.add(item);
}
root.add(presetName, entries);
}
return root;
}
private static JsonObject serializeParameterPoint(Climate.ParameterPoint params) {
JsonObject obj = new JsonObject();
obj.add("temperature", serializeParameter(params.temperature()));
obj.add("humidity", serializeParameter(params.humidity()));
obj.add("continentalness", serializeParameter(params.continentalness()));
obj.add("erosion", serializeParameter(params.erosion()));
obj.add("depth", serializeParameter(params.depth()));
obj.add("weirdness", serializeParameter(params.weirdness()));
obj.addProperty("offset", params.offset() / 10000.0f);
return obj;
}
private static JsonArray serializeParameter(Climate.Parameter parameter) {
JsonArray arr = new JsonArray();
arr.add(parameter.min());
arr.add(parameter.max());
return arr;
}
private static Object resourceKeyLocation(ResourceKey<?> key) {
try {
Method location = key.getClass().getMethod("location");
return location.invoke(key);
} catch (NoSuchMethodException e) {
try {
Method identifier = key.getClass().getMethod("identifier");
return identifier.invoke(key);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
+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,205 @@
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.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> 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 saturation = foodComponent.saturation();
float saturationRatio = saturation / foodPoints;
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,92 @@
package dev.u9g.minecraftdatagenerator.generators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList;
import java.lang.reflect.Method;
import java.util.Map;
public class MultiNoiseBiomeSourceParameterListDataGenerator implements IDataGenerator {
@Override
public String getDataName() {
return "multi_noise_biome_source_parameter_list";
}
@Override
public boolean isEnabled() {
// Only available in 1.18+
try {
Class.forName("net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public JsonObject generateDataJson() {
JsonObject root = new JsonObject();
Map<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> presets =
MultiNoiseBiomeSourceParameterList.knownPresets();
for (Map.Entry<MultiNoiseBiomeSourceParameterList.Preset, Climate.ParameterList<ResourceKey<Biome>>> entry : presets.entrySet()) {
String presetName = entry.getKey().id().toString();
JsonArray entries = new JsonArray();
for (Pair<Climate.ParameterPoint, ResourceKey<Biome>> pair : entry.getValue().values()) {
Climate.ParameterPoint params = pair.getFirst();
ResourceKey<Biome> biomeKey = pair.getSecond();
JsonObject item = new JsonObject();
item.addProperty("biome", resourceKeyLocation(biomeKey).toString());
item.add("parameters", serializeParameterPoint(params));
entries.add(item);
}
root.add(presetName, entries);
}
return root;
}
private static JsonObject serializeParameterPoint(Climate.ParameterPoint params) {
JsonObject obj = new JsonObject();
obj.add("temperature", serializeParameter(params.temperature()));
obj.add("humidity", serializeParameter(params.humidity()));
obj.add("continentalness", serializeParameter(params.continentalness()));
obj.add("erosion", serializeParameter(params.erosion()));
obj.add("depth", serializeParameter(params.depth()));
obj.add("weirdness", serializeParameter(params.weirdness()));
obj.addProperty("offset", params.offset() / 10000.0f);
return obj;
}
private static JsonArray serializeParameter(Climate.Parameter parameter) {
JsonArray arr = new JsonArray();
arr.add(parameter.min());
arr.add(parameter.max());
return arr;
}
private static Object resourceKeyLocation(ResourceKey<?> key) {
try {
Method location = key.getClass().getMethod("location");
return location.invoke(key);
} catch (NoSuchMethodException e) {
try {
Method identifier = key.getClass().getMethod("identifier");
return identifier.invoke(key);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -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
}
}
+25
View File
@@ -0,0 +1,25 @@
import xyz.wagyourtail.unimined.internal.minecraft.MinecraftProvider
plugins {
id 'dg-mc-conventions'
}
unimined.minecraft {
version "1.21.9"
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,205 @@
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.stream()
.filter(m -> m.getMaterialName().startsWith("mineable/"))
.findFirst()
.map(MaterialsDataGenerator.MaterialInfo::getMaterialName)
.orElseGet(() -> 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;
}
}

Some files were not shown because too many files have changed in this diff Show More