commit 64250071a290a0acb192924cdd87048fb2cc0b19 Author: u9g <43508353+u9g@users.noreply.github.com> Date: Thu May 5 16:09:16 2022 -0400 make it work diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c37caf --- /dev/null +++ b/.gitignore @@ -0,0 +1,118 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Cache of project +.gradletasknamecache + +**/build/ + +# Common working directory +run/ + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd8879b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Archengius, U9G + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c153cfe --- /dev/null +++ b/build.gradle @@ -0,0 +1,81 @@ +plugins { + id 'fabric-loom' version '0.12-SNAPSHOT' + id 'maven-publish' +} + +version = project.mod_version +group = project.maven_group + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" +} + +processResources { + inputs.property "version", project.version + filteringCharset "UTF-8" + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +def targetJavaVersion = 17 +tasks.withType(JavaCompile).configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + it.options.encoding = "UTF-8" + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + it.options.release = targetJavaVersion + } +} + +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } + archivesBaseName = project.archives_base_name + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +jar { + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}" } + } +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..3f454a2 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,14 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.18 +yarn_mappings=1.18+build.1 +loader_version=0.14.4 +# Mod Properties +mod_version=1.0-SNAPSHOT +maven_group=dev.u9g +archives_base_name=minecrft-data-generator-server +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.44.0+1.18 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e750102 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..c53aefa --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f91a4fe --- /dev/null +++ b/settings.gradle @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/Main.java b/src/main/java/dev/u9g/minecrftdatagenerator/Main.java new file mode 100644 index 0000000..a583497 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/Main.java @@ -0,0 +1,13 @@ +package dev.u9g.minecrftdatagenerator; + +import net.fabricmc.api.ModInitializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Main implements ModInitializer { + public static Logger LOGGER = LoggerFactory.getLogger("mc-data-gen-serv"); + @Override + public void onInitialize() { + + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/BiomesDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/BiomesDataGenerator.java new file mode 100644 index 0000000..5088f85 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/BiomesDataGenerator.java @@ -0,0 +1,57 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.biome.Biome; + +public class BiomesDataGenerator implements IDataGenerator { + + private static String guessBiomeDimensionFromCategory(Biome biome) { + return switch (biome.getCategory()) { + case NETHER -> "nether"; + case THEEND -> "end"; + default -> "overworld"; + }; + } + + public static JsonObject generateBiomeInfo(Registry registry, Biome biome) { + JsonObject biomeDesc = new JsonObject(); + Identifier registryKey = registry.getKey(biome).orElseThrow().getValue(); + String localizationKey = String.format("biome.%s.%s", registryKey.getNamespace(), registryKey.getPath()); + + biomeDesc.addProperty("id", registry.getRawId(biome)); + biomeDesc.addProperty("name", registryKey.getPath()); + + biomeDesc.addProperty("category", biome.getCategory().getName()); + biomeDesc.addProperty("temperature", biome.getTemperature()); + biomeDesc.addProperty("precipitation", biome.getPrecipitation().getName()); + //biomeDesc.addProperty("depth", biome.getDepth()); - Doesn't exist anymore in minecraft source + biomeDesc.addProperty("dimension", guessBiomeDimensionFromCategory(biome)); + biomeDesc.addProperty("displayName", DGU.translateText(localizationKey)); + biomeDesc.addProperty("color", biome.getSkyColor()); + biomeDesc.addProperty("rainfall", biome.getDownfall()); + + return biomeDesc; + } + + @Override + public String getDataName() { + return "biomes"; + } + + @Override + public JsonArray generateDataJson() { + JsonArray biomesArray = new JsonArray(); + DynamicRegistryManager registryManager = DynamicRegistryManager.create(); + Registry biomeRegistry = registryManager.get(Registry.BIOME_KEY); + + biomeRegistry.stream() + .map(biome -> generateBiomeInfo(biomeRegistry, biome)) + .forEach(biomesArray::add); + return biomesArray; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/BlockCollisionShapesDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/BlockCollisionShapesDataGenerator.java new file mode 100644 index 0000000..b966ad7 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/BlockCollisionShapesDataGenerator.java @@ -0,0 +1,111 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.EmptyBlockView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BlockCollisionShapesDataGenerator implements IDataGenerator { + + private static class BlockShapesCache { + public Map uniqueBlockShapes = new HashMap<>(); + public Map> blockCollisionShapes = new HashMap<>(); + private int lastCollisionShapeId = 0; + + public void processBlock(Block block) { + List blockStates = block.getStateManager().getStates(); + List blockCollisionShapes = new ArrayList<>(); + + for (BlockState blockState : blockStates) { + VoxelShape blockShape = blockState.getCollisionShape(EmptyBlockView.INSTANCE, BlockPos.ORIGIN); + 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 blockRegistry) { + JsonObject resultObject = new JsonObject(); + + for (var entry : blockCollisionShapes.entrySet()) { + List blockCollisions = entry.getValue(); + long distinctShapesCount = blockCollisions.stream().distinct().count(); + JsonElement blockCollision; + if (distinctShapesCount == 1L) { + blockCollision = new JsonPrimitive(blockCollisions.get(0)); + } else { + blockCollision = new JsonArray(); + for (int collisionId : blockCollisions) { + ((JsonArray) blockCollision).add(collisionId); + } + } + + Identifier registryKey = blockRegistry.getKey(entry.getKey()).orElseThrow().getValue(); + 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().forEachBox((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; + } + } + + @Override + public String getDataName() { + return "blockCollisionShapes"; + } + + @Override + public JsonObject generateDataJson() { + Registry blockRegistry = Registry.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; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/BlocksDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/BlocksDataGenerator.java new file mode 100644 index 0000000..625d8b9 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/BlocksDataGenerator.java @@ -0,0 +1,195 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.common.base.CaseFormat; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.loot.context.LootContext; +import net.minecraft.loot.context.LootContextParameters; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.IntProperty; +import net.minecraft.state.property.Property; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.EmptyBlockView; +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 Logger logger = LoggerFactory.getLogger(BlocksDataGenerator.class); + + private static List getItemsEffectiveForBlock(BlockState blockState) { + return Registry.ITEM.stream() + .filter(item -> item.getDefaultStack().isSuitableFor(blockState)) + .collect(Collectors.toList()); + } + + private static void populateDropsIfPossible(BlockState blockState, Item firstToolItem, List outDrops) { + MinecraftServer minecraftServer = DGU.getCurrentlyRunningServer(); + if (minecraftServer != null) { + //If we have local world context, we can actually evaluate loot tables and determine actual data + ServerWorld serverWorld = minecraftServer.getOverworld(); + LootContext.Builder lootContext = new LootContext.Builder(serverWorld) + .parameter(LootContextParameters.BLOCK_STATE, blockState) + .parameter(LootContextParameters.ORIGIN, Vec3d.ZERO) + .parameter(LootContextParameters.TOOL, firstToolItem.getDefaultStack()) + .random(0L); + outDrops.addAll(blockState.getDroppedStacks(lootContext)); + } 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.getDefaultStack()); + } + } + } + + private static String getPropertyTypeName(Property property) { + //Explicitly handle default minecraft properties + if (property instanceof BooleanProperty) { + return "bool"; + } + if (property instanceof IntProperty) { + 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 > JsonObject generateStateProperty(Property property) { + JsonObject propertyObject = new JsonObject(); + Collection propertyValues = property.getValues(); + + 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.name(propertyValue)); + } + propertyObject.add("values", propertyValuesArray); + } + return propertyObject; + } + + @Override + public String getDataName() { + return "blocks"; + } + + @Override + public JsonArray generateDataJson() { + JsonArray resultBlocksArray = new JsonArray(); + Registry blockRegistry = Registry.BLOCK; + List availableMaterials = MaterialsDataGenerator.getGlobalMaterialInfo(); + + blockRegistry.forEach(block -> resultBlocksArray.add(generateBlock(blockRegistry, availableMaterials, block))); + return resultBlocksArray; + } + + private static String findMatchingBlockMaterial(BlockState blockState, List materials) { + List matchingMaterials = materials.stream() + .filter(material -> material.getPredicate().test(blockState)) + .collect(Collectors.toList()); + + if (matchingMaterials.size() > 1) { + var firstMaterial = matchingMaterials.get(0); + 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.get(0).getMaterialName(); + } + + public static JsonObject generateBlock(Registry blockRegistry, List materials, Block block) { + JsonObject blockDesc = new JsonObject(); + + List blockStates = block.getStateManager().getStates(); + BlockState defaultState = block.getDefaultState(); + Identifier registryKey = blockRegistry.getKey(block).orElseThrow().getValue(); + String localizationKey = block.getTranslationKey(); + + blockDesc.addProperty("id", blockRegistry.getRawId(block)); + blockDesc.addProperty("name", registryKey.getPath()); +// if (Objects.equals(registryKey.getPath(), "red_candle_cake")) { +// System.out.println(); +// } + blockDesc.addProperty("displayName", DGU.translateText(localizationKey)); + + blockDesc.addProperty("hardness", block.getHardness()); + blockDesc.addProperty("resistance", block.getBlastResistance()); + blockDesc.addProperty("stackSize", block.asItem().getMaxCount()); + blockDesc.addProperty("diggable", block.getHardness() != -1.0f || block.getHardness() == 0f); + blockDesc.addProperty("material", findMatchingBlockMaterial(defaultState, materials)); + + blockDesc.addProperty("transparent", !defaultState.isOpaque()); + blockDesc.addProperty("emitLight", defaultState.getLuminance()); + blockDesc.addProperty("filterLight", defaultState.getOpacity(EmptyBlockView.INSTANCE, BlockPos.ORIGIN)); + + blockDesc.addProperty("defaultState", Block.getRawIdFromState(defaultState)); + blockDesc.addProperty("minStateId", Block.getRawIdFromState(blockStates.get(0))); + blockDesc.addProperty("maxStateId", Block.getRawIdFromState(blockStates.get(blockStates.size() - 1))); + + JsonArray stateProperties = new JsonArray(); + for (Property property : block.getStateManager().getProperties()) { + stateProperties.add(generateStateProperty(property)); + } + blockDesc.add("states", stateProperties); + + List effectiveTools = getItemsEffectiveForBlock(defaultState); + + //Only add harvest tools if tool is required for harvesting this block + if (defaultState.isToolRequired()) { + JsonObject effectiveToolsObject = new JsonObject(); + for (Item effectiveItem : effectiveTools) { + effectiveToolsObject.addProperty(Integer.toString(Item.getRawId(effectiveItem)), true); + } + blockDesc.add("harvestTools", effectiveToolsObject); + } + + List actualBlockDrops = new ArrayList<>(); + populateDropsIfPossible(defaultState, effectiveTools.isEmpty() ? Items.AIR : effectiveTools.get(0), actualBlockDrops); + + JsonArray dropsArray = new JsonArray(); + for (ItemStack dropStack : actualBlockDrops) { + dropsArray.add(Item.getRawId(dropStack.getItem())); + } + blockDesc.add("drops", dropsArray); + + VoxelShape blockCollisionShape = defaultState.getCollisionShape(EmptyBlockView.INSTANCE, BlockPos.ORIGIN); + blockDesc.addProperty("boundingBox", blockCollisionShape.isEmpty() ? "empty" : "block"); + + return blockDesc; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/DataGenerators.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/DataGenerators.java new file mode 100644 index 0000000..69b0150 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/DataGenerators.java @@ -0,0 +1,77 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonElement; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; + +public class DataGenerators { + + private static List GENERATORS = new ArrayList<>(); + private static final Logger logger = LoggerFactory.getLogger(DataGenerators.class); + + public static void register(IDataGenerator generator) { + GENERATORS.add(generator); + } + + public static boolean runDataGenerators(Path outputDirectory) { + try { + Files.createDirectories(outputDirectory); + } catch (IOException exception) { + logger.error("Failed to create data generator output directory at {}", outputDirectory, exception); + return false; + } + + int generatorsFailed = 0; + logger.info("Running minecraft data generators, output at {}", outputDirectory); + + for (IDataGenerator dataGenerator : GENERATORS) { + logger.info("Running generator {}", dataGenerator.getDataName()); + try { + String outputFileName = String.format("%s.json", dataGenerator.getDataName()); + JsonElement outputElement = dataGenerator.generateDataJson(); + Path outputFilePath = outputDirectory.resolve(outputFileName); + + try (Writer writer = Files.newBufferedWriter(outputFilePath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + JsonWriter jsonWriter = new JsonWriter(writer); + jsonWriter.setIndent(" "); + Streams.write(outputElement, jsonWriter); + } + logger.info("Generator: {} -> {}", dataGenerator.getDataName(), outputFileName); + + } catch (Throwable exception) { + logger.error("Failed to run data generator {}", dataGenerator.getDataName(), exception); + generatorsFailed++; + } + } + + logger.info("Finishing running data generators"); + return generatorsFailed == 0; + } + + static { + register(new BiomesDataGenerator()); + register(new BlockCollisionShapesDataGenerator()); + register(new BlocksDataGenerator()); + register(new EffectsDataGenerator()); + register(new EnchantmentsDataGenerator()); + register(new EntitiesDataGenerator()); + register(new FoodsDataGenerator()); + register(new ItemsDataGenerator()); + register(new ParticlesDataGenerator()); + register(new TintsDataGenerator()); + register(new MaterialsDataGenerator()); +// register(new RecipeDataGenerator()); - On hold until mcdata supports multiple materials for a recipe + register(new LanguageDataGenerator()); + register(new InstrumentsDataGenerator()); + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/EffectsDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/EffectsDataGenerator.java new file mode 100644 index 0000000..28c7bdc --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/EffectsDataGenerator.java @@ -0,0 +1,36 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public class EffectsDataGenerator implements IDataGenerator { + + @Override + public String getDataName() { + return "effects"; + } + + @Override + public JsonArray generateDataJson() { + JsonArray resultsArray = new JsonArray(); + Registry statusEffectRegistry = Registry.STATUS_EFFECT; + statusEffectRegistry.forEach(effect -> resultsArray.add(generateEffect(statusEffectRegistry, effect))); + return resultsArray; + } + + public static JsonObject generateEffect(Registry registry, StatusEffect statusEffect) { + JsonObject effectDesc = new JsonObject(); + Identifier registryKey = registry.getKey(statusEffect).orElseThrow().getValue(); + + effectDesc.addProperty("id", registry.getRawId(statusEffect)); + effectDesc.addProperty("name", registryKey.getPath()); + effectDesc.addProperty("displayName", DGU.translateText(statusEffect.getTranslationKey())); + + effectDesc.addProperty("type", statusEffect.isBeneficial() ? "good" : "bad"); + return effectDesc; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/EnchantmentsDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/EnchantmentsDataGenerator.java new file mode 100644 index 0000000..391e6de --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/EnchantmentsDataGenerator.java @@ -0,0 +1,108 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentTarget; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +public class EnchantmentsDataGenerator implements IDataGenerator { + + private static final ImmutableMap ENCHANTMENT_TARGET_NAMES = ImmutableMap.builder() + .put(EnchantmentTarget.ARMOR, "armor") + .put(EnchantmentTarget.ARMOR_FEET, "armor_feet") + .put(EnchantmentTarget.ARMOR_LEGS, "armor_legs") + .put(EnchantmentTarget.ARMOR_CHEST, "armor_chest") + .put(EnchantmentTarget.ARMOR_HEAD, "armor_head") + .put(EnchantmentTarget.WEAPON, "weapon") + .put(EnchantmentTarget.DIGGER, "digger") + .put(EnchantmentTarget.FISHING_ROD, "fishing_rod") + .put(EnchantmentTarget.TRIDENT, "trident") + .put(EnchantmentTarget.BREAKABLE, "breakable") + .put(EnchantmentTarget.BOW, "bow") + .put(EnchantmentTarget.WEARABLE, "wearable") + .put(EnchantmentTarget.CROSSBOW, "crossbow") + .put(EnchantmentTarget.VANISHABLE, "vanishable") + .build(); + + public static String getEnchantmentTargetName(EnchantmentTarget target) { + return ENCHANTMENT_TARGET_NAMES.getOrDefault(target, target.name().toLowerCase(Locale.ROOT)); + } + + //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.getMinPower(0); + int a = enchantment.getMinPower(1) - b; + + JsonObject resultObject = new JsonObject(); + resultObject.addProperty("a", a); + resultObject.addProperty("b", b); + return resultObject; + } + + private static JsonObject generateEnchantmentMaxPowerCoefficients(Enchantment enchantment) { + int b = enchantment.getMaxPower(0); + int a = enchantment.getMaxPower(1) - b; + + JsonObject resultObject = new JsonObject(); + resultObject.addProperty("a", a); + resultObject.addProperty("b", b); + return resultObject; + } + + @Override + public String getDataName() { + return "enchantments"; + } + + @Override + public JsonArray generateDataJson() { + JsonArray resultsArray = new JsonArray(); + Registry enchantmentRegistry = Registry.ENCHANTMENT; + enchantmentRegistry.stream() + .forEach(enchantment -> resultsArray.add(generateEnchantment(enchantmentRegistry, enchantment))); + return resultsArray; + } + + public static JsonObject generateEnchantment(Registry registry, Enchantment enchantment) { + JsonObject enchantmentDesc = new JsonObject(); + Identifier registryKey = registry.getKey(enchantment).orElseThrow().getValue(); + + enchantmentDesc.addProperty("id", registry.getRawId(enchantment)); + enchantmentDesc.addProperty("name", registryKey.getPath()); + enchantmentDesc.addProperty("displayName", DGU.translateText(enchantment.getTranslationKey())); + + enchantmentDesc.addProperty("maxLevel", enchantment.getMaxLevel()); + enchantmentDesc.add("minCost", generateEnchantmentMinPowerCoefficients(enchantment)); + enchantmentDesc.add("maxCost", generateEnchantmentMaxPowerCoefficients(enchantment)); + + enchantmentDesc.addProperty("treasureOnly", enchantment.isTreasure()); + enchantmentDesc.addProperty("curse", enchantment.isCursed()); + + List incompatibleEnchantments = registry.stream() + .filter(other -> !enchantment.canCombine(other)) + .filter(other -> other != enchantment) + .collect(Collectors.toList()); + + JsonArray excludes = new JsonArray(); + for (Enchantment excludedEnchantment : incompatibleEnchantments) { + Identifier otherKey = registry.getKey(excludedEnchantment).orElseThrow().getValue(); + excludes.add(otherKey.getPath()); + } + enchantmentDesc.add("exclude", excludes); + + enchantmentDesc.addProperty("category", getEnchantmentTargetName(enchantment.type)); + enchantmentDesc.addProperty("weight", enchantment.getRarity().getWeight()); + enchantmentDesc.addProperty("tradeable", enchantment.isAvailableForEnchantedBookOffer()); + enchantmentDesc.addProperty("discoverable", enchantment.isAvailableForRandomSelection()); + + return enchantmentDesc; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/EntitiesDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/EntitiesDataGenerator.java new file mode 100644 index 0000000..7237625 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/EntitiesDataGenerator.java @@ -0,0 +1,113 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.AmbientEntity; +import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.mob.WaterCreatureEntity; +import net.minecraft.entity.passive.AnimalEntity; +import net.minecraft.entity.passive.PassiveEntity; +import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public class EntitiesDataGenerator implements IDataGenerator { + + @Override + public String getDataName() { + return "entities"; + } + + @Override + public JsonArray generateDataJson() { + JsonArray resultArray = new JsonArray(); + Registry> entityTypeRegistry = Registry.ENTITY_TYPE; + entityTypeRegistry.forEach(entity -> resultArray.add(generateEntity(entityTypeRegistry, entity))); + return resultArray; + } + + public static JsonObject generateEntity(Registry> entityRegistry, EntityType entityType) { + JsonObject entityDesc = new JsonObject(); + Identifier registryKey = entityRegistry.getKey(entityType).orElseThrow().getValue(); + int entityRawId = entityRegistry.getRawId(entityType); + + entityDesc.addProperty("id", entityRawId); + entityDesc.addProperty("internalId", entityRawId); + entityDesc.addProperty("name", registryKey.getPath()); + + entityDesc.addProperty("displayName", DGU.translateText(entityType.getTranslationKey())); + 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.getOverworld()); + entityTypeString = entityObject != null ? getEntityTypeForClass(entityObject.getClass()) : "player"; + } + entityDesc.addProperty("type", entityTypeString); + entityDesc.addProperty("category", getCategoryFrom(entityType)); + + return entityDesc; + } + + private static String getCategoryFrom(EntityType entityType) { + if (entityType == EntityType.PLAYER) return "UNKNOWN"; // fail early for player entities + Entity entity = EntityType.createInstanceFromId(Registry.ENTITY_TYPE.getRawId(entityType), DGU.getWorld()); + if (entity == null) throw new Error("Entity was null after trying to create a: " + DGU.translateText(entityType.getTranslationKey())); + entity.discard(); + return switch (entity.getClass().getPackageName()) { + case "net.minecraft.entity.decoration", "net.minecraft.entity.decoration.painting" -> "Immobile"; + case "net.minecraft.entity.boss", "net.minecraft.entity.mob", "net.minecraft.entity.boss.dragon" -> "Hostile Mobs"; + case "net.minecraft.entity.projectile", "net.minecraft.entity.projectile.thrown" -> "Projectiles"; + case "net.minecraft.entity.passive" -> "Passive Mobs"; + case "net.minecraft.entity.vehicle" -> "Vehicles"; + case "net.minecraft.entity" -> "UNKNOWN"; + default -> throw new Error("Unexpected entity type: " + entity.getClass().getPackageName()); + }; + } + + //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 entityClass) { + //Top-level classifications + if (WaterCreatureEntity.class.isAssignableFrom(entityClass)) { + return "water_creature"; + } + if (AnimalEntity.class.isAssignableFrom(entityClass)) { + return "animal"; + } + if (HostileEntity.class.isAssignableFrom(entityClass)) { + return "hostile"; + } + if (AmbientEntity.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 (PassiveEntity.class.isAssignableFrom(entityClass)) { + return "passive"; + } + if (MobEntity.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 (ProjectileEntity.class.isAssignableFrom(entityClass)) { + return "projectile"; + } + return "other"; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/FoodsDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/FoodsDataGenerator.java new file mode 100644 index 0000000..9b1282e --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/FoodsDataGenerator.java @@ -0,0 +1,51 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.item.FoodComponent; +import net.minecraft.item.Item; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +import java.util.Objects; + +public class FoodsDataGenerator implements IDataGenerator { + + @Override + public String getDataName() { + return "foods"; + } + + public JsonArray generateDataJson() { + JsonArray resultsArray = new JsonArray(); + Registry itemRegistry = Registry.ITEM; + itemRegistry.stream() + .filter(Item::isFood) + .forEach(food -> resultsArray.add(generateFoodDescriptor(itemRegistry, food))); + return resultsArray; + } + + public static JsonObject generateFoodDescriptor(Registry registry, Item foodItem) { + JsonObject foodDesc = new JsonObject(); + Identifier registryKey = registry.getKey(foodItem).orElseThrow().getValue(); + + foodDesc.addProperty("id", registry.getRawId(foodItem)); + foodDesc.addProperty("name", registryKey.getPath()); + + foodDesc.addProperty("stackSize", foodItem.getMaxCount()); + foodDesc.addProperty("displayName", DGU.translateText(foodItem.getTranslationKey())); + + FoodComponent foodComponent = Objects.requireNonNull(foodItem.getFoodComponent()); + float foodPoints = foodComponent.getHunger(); + float saturationRatio = foodComponent.getSaturationModifier() * 2.0F; + float saturation = foodPoints * saturationRatio; + + foodDesc.addProperty("foodPoints", foodPoints); + foodDesc.addProperty("saturation", saturation); + + foodDesc.addProperty("effectiveQuality", foodPoints + saturation); + foodDesc.addProperty("saturationRatio", saturationRatio); + return foodDesc; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/IDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/IDataGenerator.java new file mode 100644 index 0000000..58e0547 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/IDataGenerator.java @@ -0,0 +1,10 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonElement; + +public interface IDataGenerator { + + String getDataName(); + + JsonElement generateDataJson(); +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/InstrumentsDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/InstrumentsDataGenerator.java new file mode 100644 index 0000000..2f88133 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/InstrumentsDataGenerator.java @@ -0,0 +1,25 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.block.enums.Instrument; + +public class InstrumentsDataGenerator implements IDataGenerator { + @Override + public String getDataName() { + return "instruments"; + } + + @Override + public JsonElement generateDataJson() { + JsonArray array = new JsonArray(); + for (Instrument instrument : Instrument.values()) { + JsonObject object = new JsonObject(); + object.addProperty("id", instrument.ordinal()); + object.addProperty("name", instrument.asString()); + array.add(object); + } + return array; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/ItemsDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/ItemsDataGenerator.java new file mode 100644 index 0000000..c000d13 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/ItemsDataGenerator.java @@ -0,0 +1,78 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.enchantment.EnchantmentTarget; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ItemsDataGenerator implements IDataGenerator { + + private static List calculateItemsToRepairWith(Registry itemRegistry, Item sourceItem) { + ItemStack sourceItemStack = sourceItem.getDefaultStack(); + return itemRegistry.stream() + .filter(otherItem -> sourceItem.canRepair(sourceItemStack, otherItem.getDefaultStack())) + .collect(Collectors.toList()); + } + + private static List getApplicableEnchantmentTargets(Item sourceItem) { + return Arrays.stream(EnchantmentTarget.values()) + .filter(target -> target.isAcceptableItem(sourceItem)) + .collect(Collectors.toList()); + } + + @Override + public String getDataName() { + return "items"; + } + + @Override + public JsonArray generateDataJson() { + JsonArray resultArray = new JsonArray(); + Registry itemRegistry = Registry.ITEM; + itemRegistry.stream().forEach(item -> resultArray.add(generateItem(itemRegistry, item))); + return resultArray; + } + + public static JsonObject generateItem(Registry itemRegistry, Item item) { + JsonObject itemDesc = new JsonObject(); + Identifier registryKey = itemRegistry.getKey(item).orElseThrow().getValue(); + + itemDesc.addProperty("id", itemRegistry.getRawId(item)); + itemDesc.addProperty("name", registryKey.getPath()); + + itemDesc.addProperty("displayName", DGU.translateText(item.getTranslationKey())); + itemDesc.addProperty("stackSize", item.getMaxCount()); + + List enchantmentTargets = getApplicableEnchantmentTargets(item); + + JsonArray enchantCategoriesArray = new JsonArray(); + for (EnchantmentTarget target : enchantmentTargets) { + enchantCategoriesArray.add(EnchantmentsDataGenerator.getEnchantmentTargetName(target)); + } + itemDesc.add("enchantCategories", enchantCategoriesArray); + + if (item.isDamageable()) { + List repairWithItems = calculateItemsToRepairWith(itemRegistry, item); + + JsonArray fixedWithArray = new JsonArray(); + for (Item repairWithItem : repairWithItems) { + Identifier repairWithName = itemRegistry.getKey(repairWithItem).orElseThrow().getValue(); + fixedWithArray.add(repairWithName.getPath()); + } + itemDesc.add("fixedWith", fixedWithArray); + + int maxDurability = item.getMaxDamage(); + itemDesc.addProperty("durability", maxDurability); + itemDesc.addProperty("maxDurability", maxDurability); + } + return itemDesc; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/LanguageDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/LanguageDataGenerator.java new file mode 100644 index 0000000..7159ac4 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/LanguageDataGenerator.java @@ -0,0 +1,26 @@ +package dev.u9g.minecrftdatagenerator.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"); + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/MaterialsDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/MaterialsDataGenerator.java new file mode 100644 index 0000000..5cec342 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/MaterialsDataGenerator.java @@ -0,0 +1,198 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.common.base.Joiner; +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.minecrftdatagenerator.mixin.MiningToolItemAccessor; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.Material; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import net.minecraft.item.MiningToolItem; +import net.minecraft.item.SwordItem; +import net.minecraft.tag.BlockTags; +import net.minecraft.tag.Tag; +import net.minecraft.util.registry.Registry; + +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> COMPOSITE_MATERIALS = ImmutableList.>builder() + .add(ImmutableList.of("plant", makeMaterialNameForTag(BlockTags.AXE_MINEABLE))) + .add(ImmutableList.of("gourd", makeMaterialNameForTag(BlockTags.AXE_MINEABLE))) + .add(ImmutableList.of(makeMaterialNameForTag(BlockTags.LEAVES), makeMaterialNameForTag(BlockTags.HOE_MINEABLE))) + .add(ImmutableList.of(makeMaterialNameForTag(BlockTags.LEAVES), makeMaterialNameForTag(BlockTags.AXE_MINEABLE), makeMaterialNameForTag(BlockTags.HOE_MINEABLE))) + .add(ImmutableList.of("vine_or_glow_lichen", "plant", makeMaterialNameForTag(BlockTags.AXE_MINEABLE) + )).build(); + + @Override + public String getDataName() { + return "materials"; + } + + private static String makeMaterialNameForTag(Tag tag) { + Tag.Identified identifiedTag = (Tag.Identified) tag; + return identifiedTag.getId().getPath(); + } + + public static class MaterialInfo { + private final String materialName; + private final Predicate predicate; + private final List includedMaterials = new ArrayList<>(); + + public MaterialInfo(String materialName, Predicate predicate) { + this.materialName = materialName; + this.predicate = predicate; + } + + protected MaterialInfo includes(List otherMaterials) { + this.includedMaterials.addAll(otherMaterials); + return this; + } + + public String getMaterialName() { + return materialName; + } + + public Predicate getPredicate() { + return predicate; + } + + public boolean includesMaterial(MaterialInfo materialInfo) { + return includedMaterials.contains(materialInfo); + } + + @Override + public String toString() { + return materialName; + } + } + + private static void createCompositeMaterialInfo(List allMaterials, List combinedMaterials) { + String compositeMaterialName = Joiner.on(';').join(combinedMaterials); + + List 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 compositePredicate = blockState -> + mappedMaterials.stream().allMatch(it -> it.getPredicate().test(blockState)); + + MaterialInfo materialInfo = new MaterialInfo(compositeMaterialName, compositePredicate).includes(mappedMaterials); + allMaterials.add(0, materialInfo); + } + + private static void createCompositeMaterial(Map> allMaterials, List combinedMaterials) { + String compositeMaterialName = Joiner.on(';').join(combinedMaterials); + + Map resultingToolSpeeds = new HashMap<>(); + combinedMaterials.stream() + .map(allMaterials::get) + .forEach(resultingToolSpeeds::putAll); + allMaterials.put(compositeMaterialName, resultingToolSpeeds); + } + + public static List getGlobalMaterialInfo() { + ArrayList resultList = new ArrayList<>(); + + resultList.add(new MaterialInfo("vine_or_glow_lichen", blockState -> blockState.isOf(Blocks.VINE) || blockState.isOf(Blocks.GLOW_LICHEN))); + resultList.add(new MaterialInfo("coweb", blockState -> blockState.isOf(Blocks.COBWEB))); + + resultList.add(new MaterialInfo("leaves", blockState -> blockState.isIn(BlockTags.LEAVES))); + resultList.add(new MaterialInfo("wool", blockState -> blockState.isIn(BlockTags.WOOL))); + + resultList.add(new MaterialInfo("gourd", blockState -> blockState.getMaterial() == Material.GOURD)); + resultList.add(new MaterialInfo("plant", blockState -> blockState.getMaterial() == Material.PLANT || blockState.getMaterial() == Material.REPLACEABLE_PLANT)); + + HashSet uniqueMaterialNames = new HashSet<>(); + + Registry itemRegistry = Registry.ITEM; + itemRegistry.forEach(item -> { + if (item instanceof MiningToolItem toolItem) { + Tag effectiveBlocks = ((MiningToolItemAccessor) toolItem).getEffectiveBlocks(); + String materialName = makeMaterialNameForTag(effectiveBlocks); + + if (!uniqueMaterialNames.contains(materialName)) { + uniqueMaterialNames.add(materialName); + resultList.add(new MaterialInfo(materialName, blockState -> blockState.isIn(effectiveBlocks))); + } + } + }); + + COMPOSITE_MATERIALS.forEach(values -> createCompositeMaterialInfo(resultList, values)); + return resultList; + } + + @Override + public JsonElement generateDataJson() { + Registry itemRegistry = Registry.ITEM; + + Map> materialMiningSpeeds = new HashMap<>(); + materialMiningSpeeds.put("default", ImmutableMap.of()); + + //Special materials used for shears and swords special mining speed logic + Map leavesMaterialSpeeds = new HashMap<>(); + Map cowebMaterialSpeeds = new HashMap<>(); + Map plantMaterialSpeeds = new HashMap<>(); + Map gourdMaterialSpeeds = new HashMap<>(); + + 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); + materialMiningSpeeds.put("vine_or_glow_lichen", ImmutableMap.of(Items.SHEARS, 2.0f)); + materialMiningSpeeds.put("wool", ImmutableMap.of(Items.SHEARS, 5.0f)); + + itemRegistry.forEach(item -> { + //Tools are handled rather easily and do not require anything else + if (item instanceof MiningToolItem toolItem) { + Tag effectiveBlocks = ((MiningToolItemAccessor) toolItem).getEffectiveBlocks(); + String materialName = makeMaterialNameForTag(effectiveBlocks); + + Map materialSpeeds = materialMiningSpeeds.computeIfAbsent(materialName, k -> new HashMap<>()); + float miningSpeed = ((MiningToolItemAccessor) toolItem).getMiningSpeed(); + materialSpeeds.put(item, miningSpeed); + } + + //Swords require special treatment + if (item instanceof SwordItem) { + 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.getRawId(toolEntry.getKey()); + toolSpeedsObject.addProperty(Integer.toString(rawItemId), toolEntry.getValue()); + } + resultObject.add(entry.getKey(), toolSpeedsObject); + } + + return resultObject; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/ParticlesDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/ParticlesDataGenerator.java new file mode 100644 index 0000000..40b58ee --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/ParticlesDataGenerator.java @@ -0,0 +1,32 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.minecraft.particle.ParticleType; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public class ParticlesDataGenerator implements IDataGenerator { + + @Override + public String getDataName() { + return "particles"; + } + + @Override + public JsonArray generateDataJson() { + JsonArray resultsArray = new JsonArray(); + Registry>particleTypeRegistry = Registry.PARTICLE_TYPE; + particleTypeRegistry.forEach(particleType -> resultsArray.add(generateParticleType(particleTypeRegistry, particleType))); + return resultsArray; + } + + public static JsonObject generateParticleType(Registry> registry, ParticleType particleType) { + JsonObject effectDesc = new JsonObject(); + Identifier registryKey = registry.getKey(particleType).orElseThrow().getValue(); + + effectDesc.addProperty("id", registry.getRawId(particleType)); + effectDesc.addProperty("name", registryKey.getPath()); + return effectDesc; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/RecipeDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/RecipeDataGenerator.java new file mode 100644 index 0000000..022ff63 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/RecipeDataGenerator.java @@ -0,0 +1,133 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.client.MinecraftClient; +import net.minecraft.item.Item; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.ShapedRecipe; +import net.minecraft.recipe.ShapelessRecipe; +import net.minecraft.util.registry.Registry; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class RecipeDataGenerator implements IDataGenerator { + + @Override + public String getDataName() { + return "recipes"; + } + + @Override + public JsonElement generateDataJson() { + JsonObject finalObj = new JsonObject(); + Multimap recipes = ArrayListMultimap.create(); + for (Recipe recipe : Objects.requireNonNull(DGU.getWorld()).getRecipeManager().values()) { + if (recipe instanceof ShapedRecipe sr) { + var ingredients = sr.getIngredients(); + List ingr = new ArrayList<>(); + for (int i = 0; i < 9; i++) { + if (i >= ingredients.size()) { + ingr.add(-1); + continue; + } + var stacks = ingredients.get(i); + var matching = stacks.getMatchingStacks(); + if (matching.length == 0) { + ingr.add(-1); + } else { + ingr.add(getRawIdFor(matching[0].getItem())); + } + } + Lists.reverse(ingr); + + JsonArray inShape = new JsonArray(); + + var iter = ingr.iterator(); + for (int y = 0; y < 3; y++) { + var jsonRow = new JsonArray(); + int one = iter.next(); + int two = iter.next(); + int three = iter.next(); + if (y > 0 && one == -1 && two == -1 && three == -1) continue; + jsonRow.add(one); + jsonRow.add(two); + jsonRow.add(three); + inShape.add(jsonRow); + } + + JsonObject finalRecipe = new JsonObject(); + finalRecipe.add("inShape", inShape); + + var resultObject = new JsonObject(); + resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); + resultObject.addProperty("count", sr.getOutput().getCount()); + finalRecipe.add("result", resultObject); + + String id = ((Integer) getRawIdFor(sr.getOutput().getItem())).toString(); + + if (!finalObj.has(id)) { + finalObj.add(id, new JsonArray()); + } + finalObj.get(id).getAsJsonArray().add(finalRecipe); +// var input = new JsonArray(); +// var ingredients = sr.getIngredients().stream().toList(); +// for (int y = 0; y < sr.getHeight(); y++) { +// var arr = new JsonArray(); +// for (int x = 0; x < sr.getWidth(); x++) { +// if ((y*3)+x >= ingredients.size()) { +// arr.add(JsonNull.INSTANCE); +// continue; +// } +// var ingredient = ingredients.get((y*3)+x).getMatchingStacks(); // FIXME: fix when there are more than one matching stack +// if (ingredient.length == 0) { +// arr.add(JsonNull.INSTANCE); +// } else { +// arr.add(getRawIdFor(ingredient[0].getItem())); +// } +// } +// input.add(arr); +// } +// var rootRecipeObject = new JsonObject(); +// rootRecipeObject.add("inShape", input); +// var resultObject = new JsonObject(); +// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); +// resultObject.addProperty("count", sr.getOutput().getCount()); +// rootRecipeObject.add("result", resultObject); +// recipes.put(getRawIdFor(sr.getOutput().getItem()), rootRecipeObject); + } else if (recipe instanceof ShapelessRecipe sl) { + var ingredients = new JsonArray(); + for (Ingredient ingredient : sl.getIngredients()) { + if (ingredient.isEmpty()) continue; + ingredients.add(getRawIdFor(ingredient.getMatchingStacks()[0].getItem())); + } + var rootRecipeObject = new JsonObject(); + rootRecipeObject.add("ingredients", ingredients); + var resultObject = new JsonObject(); + resultObject.addProperty("id", getRawIdFor(sl.getOutput().getItem())); + resultObject.addProperty("count", sl.getOutput().getCount()); + rootRecipeObject.add("result", resultObject); + recipes.put(getRawIdFor(sl.getOutput().getItem()), rootRecipeObject); + } + } + recipes.forEach((a, b) -> { + if (!finalObj.has(a.toString())) { + finalObj.add(a.toString(), new JsonArray()); + } + finalObj.get(a.toString()).getAsJsonArray().add(b); + }); + return finalObj; + } + + private static int getRawIdFor (Item item) { + return Registry.ITEM.getRawId(item); + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/generators/TintsDataGenerator.java b/src/main/java/dev/u9g/minecrftdatagenerator/generators/TintsDataGenerator.java new file mode 100644 index 0000000..450c9c7 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/generators/TintsDataGenerator.java @@ -0,0 +1,168 @@ +package dev.u9g.minecrftdatagenerator.generators; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import dev.u9g.minecrftdatagenerator.util.EmptyRenderBlockView; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.RedstoneWireBlock; +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.color.world.FoliageColors; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.biome.Biome; + +import java.util.*; + +public class TintsDataGenerator implements IDataGenerator { + + public static class BiomeTintColors { + Map> grassColoursMap = new HashMap<>(); + Map> foliageColoursMap = new HashMap<>(); + Map> waterColourMap = new HashMap<>(); + } + + public static BiomeTintColors generateBiomeTintColors(Registry biomeRegistry) { + BiomeTintColors colors = new BiomeTintColors(); + + biomeRegistry.forEach(biome -> { + int biomeGrassColor = biome.getGrassColorAt(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 generateRedstoneTintColors() { + Map resultColors = new HashMap<>(); + + for (int redstoneLevel : RedstoneWireBlock.POWER.getValues()) { + int color = RedstoneWireBlock.getWireColor(redstoneLevel); + resultColors.put(redstoneLevel, color); + } + return resultColors; + } + + @Environment(EnvType.CLIENT) + private static int getBlockColor(Block block, BlockColors blockColors) { + return blockColors.getColor(block.getDefaultState(), EmptyRenderBlockView.INSTANCE, BlockPos.ORIGIN, 0xFFFFFF); + } + + @Environment(EnvType.CLIENT) + public static Map generateConstantTintColors() { + Map resultColors = new HashMap<>(); + BlockColors blockColors = BlockColors.create(); + + resultColors.put(Blocks.BIRCH_LEAVES, FoliageColors.getBirchColor()); + resultColors.put(Blocks.SPRUCE_LEAVES, FoliageColors.getSpruceColor()); + + resultColors.put(Blocks.LILY_PAD, getBlockColor(Blocks.LILY_PAD, blockColors)); + resultColors.put(Blocks.ATTACHED_MELON_STEM, getBlockColor(Blocks.ATTACHED_MELON_STEM, blockColors)); + resultColors.put(Blocks.ATTACHED_PUMPKIN_STEM, getBlockColor(Blocks.ATTACHED_PUMPKIN_STEM, blockColors)); + + //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, blockColors)); + resultColors.put(Blocks.PUMPKIN_STEM, getBlockColor(Blocks.PUMPKIN_STEM, blockColors)); + + return resultColors; + } + + private static JsonObject encodeBiomeColorMap(Registry biomeRegistry, Map> 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).orElseThrow().getValue(); + 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 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 blockRegistry, Map colorsMap) { + JsonArray resultColorsArray = new JsonArray(); + for (var entry : colorsMap.entrySet()) { + JsonObject entryObject = new JsonObject(); + + JsonArray keysArray = new JsonArray(); + Identifier registryKey = blockRegistry.getKey(entry.getKey()).orElseThrow().getValue(); + 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() { + DynamicRegistryManager registryManager = DynamicRegistryManager.create(); + Registry biomeRegistry = registryManager.get(Registry.BIOME_KEY); + Registry blockRegistry = registryManager.get(Registry.BLOCK_KEY); + + BiomeTintColors biomeTintColors = generateBiomeTintColors(biomeRegistry); + Map redstoneColors = generateRedstoneTintColors(); + Map constantTintColors = Collections.emptyMap(); + + EnvType currentEnvironment = FabricLoader.getInstance().getEnvironmentType(); + if (currentEnvironment == EnvType.CLIENT) { + 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; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/mixin/ClientPlayerInteractionManagerMixin.java b/src/main/java/dev/u9g/minecrftdatagenerator/mixin/ClientPlayerInteractionManagerMixin.java new file mode 100644 index 0000000..5ac98f1 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/mixin/ClientPlayerInteractionManagerMixin.java @@ -0,0 +1,57 @@ +package dev.u9g.minecrftdatagenerator.mixin; + +import com.google.common.base.Preconditions; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientPlayerInteractionManager.class) +public abstract class ClientPlayerInteractionManagerMixin { + @Unique + private long blockBreakingStartTicks; + @Unique + private BlockState stateBeingBroken; + + @Inject(method = "attackBlock", at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;currentBreakingProgress:F", opcode = Opcodes.PUTFIELD)) + private void onBlockAttacked(BlockPos pos, Direction direction, CallbackInfoReturnable callbackInfo) { + World world = Preconditions.checkNotNull(MinecraftClient.getInstance().world); + this.blockBreakingStartTicks = world.getTime(); + } + + @Inject(method = "cancelBlockBreaking", at = @At("HEAD")) + private void onBlockBreakingCanceled(CallbackInfo callbackInfo) { + this.blockBreakingStartTicks = -1L; + } + + @Inject(method = "breakBlock", at = @At("HEAD")) + private void onBreakBlockHead(BlockPos pos, CallbackInfoReturnable callbackInfo) { + World world = Preconditions.checkNotNull(MinecraftClient.getInstance().world); + this.stateBeingBroken = world.getBlockState(pos); + } + + @Inject(method = "breakBlock", at = @At("RETURN")) + private void onBreakBlockReturn(BlockPos pos, CallbackInfoReturnable callbackInfo) { + World world = Preconditions.checkNotNull(MinecraftClient.getInstance().world); + if (callbackInfo.getReturnValue()) { + Preconditions.checkNotNull(stateBeingBroken); + if (blockBreakingStartTicks != -1L) { + long totalTicksElapsed = world.getTime() - this.blockBreakingStartTicks; + long totalTimeElapsed = totalTicksElapsed * 50L; + System.out.printf("Breaking block %s took %dms%n", stateBeingBroken.getBlock(), totalTimeElapsed); + } else { + System.out.printf("Breaking block %s took 0ms [INSTANT BREAK]%n", stateBeingBroken.getBlock()); + } + } + this.blockBreakingStartTicks = -1L; + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/mixin/MiningToolItemAccessor.java b/src/main/java/dev/u9g/minecrftdatagenerator/mixin/MiningToolItemAccessor.java new file mode 100644 index 0000000..46e174f --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/mixin/MiningToolItemAccessor.java @@ -0,0 +1,17 @@ +package dev.u9g.minecrftdatagenerator.mixin; + +import net.minecraft.block.Block; +import net.minecraft.item.MiningToolItem; +import net.minecraft.tag.Tag; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MiningToolItem.class) +public interface MiningToolItemAccessor { + + @Accessor + Tag getEffectiveBlocks(); + + @Accessor + float getMiningSpeed(); +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/mixin/ReadyMixin.java b/src/main/java/dev/u9g/minecrftdatagenerator/mixin/ReadyMixin.java new file mode 100644 index 0000000..34d78e6 --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/mixin/ReadyMixin.java @@ -0,0 +1,27 @@ +package dev.u9g.minecrftdatagenerator.mixin; + +import dev.u9g.minecrftdatagenerator.Main; +import dev.u9g.minecrftdatagenerator.generators.DataGenerators; +import dev.u9g.minecrftdatagenerator.util.DGU; +import net.minecraft.MinecraftVersion; +import net.minecraft.server.dedicated.MinecraftDedicatedServer; +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.nio.file.Path; + +@Mixin(MinecraftDedicatedServer.class) +public class ReadyMixin { + @Inject(method = "setupServer()Z", at = @At("TAIL")) + private void init(CallbackInfoReturnable cir) { + Main.LOGGER.info("Starting data generation!"); + String versionName = MinecraftVersion.CURRENT.getName(); + Path serverRootDirectory = DGU.getCurrentlyRunningServer().getRunDirectory().toPath().toAbsolutePath(); + Path dataDumpDirectory = serverRootDirectory.resolve("minecraft-data").resolve(versionName); + DataGenerators.runDataGenerators(dataDumpDirectory); + Main.LOGGER.info("Done data generation!"); + DGU.getCurrentlyRunningServer().stop(false); + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/util/DGU.java b/src/main/java/dev/u9g/minecrftdatagenerator/util/DGU.java new file mode 100644 index 0000000..3a9061c --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/util/DGU.java @@ -0,0 +1,60 @@ +package dev.u9g.minecrftdatagenerator.util; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.resource.language.I18n; +import net.minecraft.server.MinecraftServer; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Language; +import net.minecraft.world.World; + +public class DGU { + + @Environment(EnvType.CLIENT) + private static MinecraftServer getCurrentlyRunningServerClient() { + return MinecraftClient.getInstance().getServer(); + } + + @SuppressWarnings("deprecation") + private static MinecraftServer getCurrentlyRunningServerDedicated() { + return (MinecraftServer) FabricLoader.getInstance().getGameInstance(); + } + + public static MinecraftServer getCurrentlyRunningServer() { + EnvType environmentType = FabricLoader.getInstance().getEnvironmentType(); + if (environmentType == EnvType.CLIENT) { + return getCurrentlyRunningServerClient(); + } else if (environmentType == EnvType.SERVER) { + return getCurrentlyRunningServerDedicated(); + } + throw new UnsupportedOperationException(); + } + + @Environment(EnvType.CLIENT) + private static String translateTextClient(String translationKey) { + return I18n.translate(translationKey); + } + private static Language language = Language.getInstance(); + + private static String translateTextFallback(String translationKey) { + try { + return language.get(translationKey); + } catch (Exception ignored) {} + throw new RuntimeException("Failed to translate: '" + translationKey + "'"); + } + + public static String translateText(String translationKey) { + EnvType environmentType = FabricLoader.getInstance().getEnvironmentType(); + if (environmentType == EnvType.CLIENT) { + return translateTextClient(translationKey); + } + return translateTextFallback(translationKey); + } + + public static World getWorld() { + return getCurrentlyRunningServer().getOverworld(); + } +} diff --git a/src/main/java/dev/u9g/minecrftdatagenerator/util/EmptyRenderBlockView.java b/src/main/java/dev/u9g/minecrftdatagenerator/util/EmptyRenderBlockView.java new file mode 100644 index 0000000..d66db9d --- /dev/null +++ b/src/main/java/dev/u9g/minecrftdatagenerator/util/EmptyRenderBlockView.java @@ -0,0 +1,73 @@ +package dev.u9g.minecrftdatagenerator.util; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.fluid.FluidState; +import net.minecraft.fluid.Fluids; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.BlockRenderView; +import net.minecraft.world.LightType; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.BiomeKeys; +import net.minecraft.world.chunk.light.LightingProvider; +import net.minecraft.world.level.ColorResolver; +import org.jetbrains.annotations.Nullable; + +public enum EmptyRenderBlockView implements BlockRenderView { + INSTANCE; + + @Nullable + public BlockEntity getBlockEntity(BlockPos pos) { + return null; + } + + public BlockState getBlockState(BlockPos pos) { + return Blocks.AIR.getDefaultState(); + } + + public FluidState getFluidState(BlockPos pos) { + return Fluids.EMPTY.getDefaultState(); + } + + public int getBottomY() { + return 0; + } + + public int getHeight() { + return 0; + } + + + @Override + public float getBrightness(Direction direction, boolean shaded) { + return 0.0f; + } + + @Override + public LightingProvider getLightingProvider() { + return null; + } + + @Override + public int getColor(BlockPos pos, ColorResolver colorResolver) { + DynamicRegistryManager registryManager = DynamicRegistryManager.create(); + Registry biomeRegistry = registryManager.get(Registry.BIOME_KEY); + Biome plainsBiome = biomeRegistry.get(BiomeKeys.PLAINS); + + return colorResolver.getColor(plainsBiome, pos.getX(), pos.getY()); + } + + @Override + public int getLightLevel(LightType type, BlockPos pos) { + return type == LightType.SKY ? getMaxLightLevel() : 0; + } + + @Override + public int getBaseLightLevel(BlockPos pos, int ambientDarkness) { + return ambientDarkness; + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..6f4e430 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": 1, + "id": "minecrft-data-generator", + "version": "${version}", + "name": "Minecrft Data Generator", + "description": "", + "authors": [ + "Archengius", + "U9G" + ], + "contact": {}, + "license": "MIT", + "icon": "assets/minecrft-data-generator/icon.png", + "environment": "server", + "entrypoints": { + "main": [ + "dev.u9g.minecrftdatagenerator.Main" + ] + }, + "mixins": [ + "minecrft-data-generator.mixins.json" + ], + "depends": { + "fabricloader": ">=0.14.4", + "fabric": "*", + "minecraft": "1.18" + } +} diff --git a/src/main/resources/minecrft-data-generator.mixins.json b/src/main/resources/minecrft-data-generator.mixins.json new file mode 100644 index 0000000..fc96201 --- /dev/null +++ b/src/main/resources/minecrft-data-generator.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "dev.u9g.minecrftdatagenerator.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "ClientPlayerInteractionManagerMixin", + "MiningToolItemAccessor", + "ReadyMixin" + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +}