diff --git a/.github/workflows/check-local-changes.yml b/.github/workflows/check-local-changes.yml
index ef101ceacd..0609d67e89 100644
--- a/.github/workflows/check-local-changes.yml
+++ b/.github/workflows/check-local-changes.yml
@@ -41,7 +41,7 @@ jobs:
run: ./gradlew generatePackageInfos
- name: Gen patches
- run: ./gradlew :neoforge:unpackSourcePatches
+ run: ./gradlew :neoforge:genPatches
- name: Run datagen with Gradle
run: ./gradlew :neoforge:runData :tests:runData
diff --git a/.github/workflows/test-prs.yml b/.github/workflows/test-prs.yml
index 51afdcddad..471304015a 100644
--- a/.github/workflows/test-prs.yml
+++ b/.github/workflows/test-prs.yml
@@ -47,6 +47,15 @@ jobs:
- name: Run JUnit tests with Gradle
run: ./gradlew :tests:runUnitTests
+ - name: Install software OpenGL rendering
+ run: sudo apt-get install xvfb libgl1-mesa-dri
+
+ - name: Run production client self-test
+ run: xvfb-run ./gradlew :neoforge:testProductionClient
+
+ - name: Run production server self-test
+ run: ./gradlew :neoforge:testProductionServer
+
- name: Store reports
if: failure()
uses: actions/upload-artifact@v4
diff --git a/.gitignore b/.gitignore
index 883731fee7..b5c5b10b6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,4 +44,4 @@ build
**/eclipse/
**/runs/
-**/out/
\ No newline at end of file
+**/out/
diff --git a/build.gradle b/build.gradle
index 6c46fb5ba6..fbc90b7d61 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ import java.util.regex.Pattern
plugins {
id 'net.neoforged.gradleutils' version '3.0.0'
id 'com.diffplug.spotless' version '6.22.0' apply false
- id 'net.neoforged.licenser' version '0.7.2'
+ id 'net.neoforged.licenser' version '0.7.5'
id 'neoforge.formatting-conventions'
id 'neoforge.versioning'
}
@@ -23,19 +23,22 @@ System.out.println("NeoForge version ${project.version}")
allprojects {
version rootProject.version
group 'net.neoforged'
- repositories {
- mavenLocal()
- }
-}
-subprojects {
apply plugin: 'java'
java.toolchain.languageVersion.set(JavaLanguageVersion.of(project.java_version))
}
-repositories {
- mavenCentral()
+// Remove src/ sources from the root project. They are used in the neoforge subproject.
+sourceSets {
+ main {
+ java {
+ srcDirs = []
+ }
+ resources {
+ srcDirs = []
+ }
+ }
}
// Put licenser here otherwise it tries to license all source sets including decompiled MC sources
diff --git a/buildSrc/README.md b/buildSrc/README.md
new file mode 100644
index 0000000000..2e63a427c0
--- /dev/null
+++ b/buildSrc/README.md
@@ -0,0 +1,81 @@
+# NeoForge Development Gradle Plugin
+
+## NeoForge Project Structure
+
+Before understanding the `buildSrc` plugin, one should understand the structure of the NeoForge Gradle project it is
+applied to.
+
+The project consists of a project tree with the following structure:
+
+| Folder Path | Gradle Project Path | Applied Plugins | Description |
+|------------------------------------------------------------------------|----------------------|:------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [`/build.gradle`](../build.gradle) | `:` | — | The root project. Since this project is reused for Kits, the root project name is based on the checkout folder, which actually can lead to issues if it is called `NeoForge`. |
+| [`/projects/neoforge/build.gradle`](../projects/neoforge/build.gradle) | `:neoforge` | [NeoDevPlugin](#neodevplugin) | The core NeoForge project, which produces the artifacts that will be published. |
+| [`/projects/base/build.gradle`](../projects/base/build.gradle) | `:base` | [NeoDevBasePlugin](#neodevbaseplugin) | A utility project that contains the Minecraft sources without any NeoForge additions. Can be used to quickly compare what NeoForge has changed. |
+| [`/tests/build.gradle`](../tests/build.gradle) | `:tests` | [NeoDevExtraPlugin](#neodevextraplugin) | Contains the game and unit tests for NeoForge. |
+| [`/testframework/build.gradle`](../testframework/build.gradle) | `:testframework` | [MinecraftDependenciesPlugin](#minecraftdependenciesplugin) | A library providing support classes around writing game tests. |
+| [`/coremods/build.gradle`](../coremods/build.gradle) | `:neoforge-coremods` | — | Java Bytecode transformers that are embedded into NeoForge as a nested Jar file. |
+|
+
+## Plugins
+
+### NeoDevBasePlugin
+
+Sources: [NeoDevBasePlugin.java](src/main/java/net/neoforged/neodev/NeoDevBasePlugin.java)
+
+Implicitly applies: [MinecraftDependenciesPlugin](#minecraftdependenciesplugin).
+
+Sets up a `setup` task that reuses code from [NeoDevPlugin](#neodevplugin) to decompile Minecraft and place the
+decompiled sources in `projects/base/src/main/java`.
+
+### NeoDevPlugin
+
+Sources: [NeoDevPlugin.java](src/main/java/net/neoforged/neodev/NeoDevPlugin.java)
+
+Implicitly applies: [MinecraftDependenciesPlugin](#minecraftdependenciesplugin).
+
+This is the primary of this repository and is used to configure the `neoforge` subproject.
+
+#### Setup
+
+It creates a `setup` task that performs the following actions via various subtasks:
+
+- Decompile Minecraft using the [NeoForm Runtime](https://github.com/neoforged/neoformruntime) and Minecraft version specific [NeoForm data](https://github.com/neoforged/NeoForm).
+- Applies [Access Transformers](../src/main/resources/META-INF/accesstransformer.cfg) to Minecraft sources.
+- Applies [NeoForge patches](../patches) to Minecraft sources. Any rejects are saved to the `/rejects` folder in the repository for manual inspection. During updates to new versions, the task can be run with `-Pupdating=true` to apply patches more leniently.
+- Unpacks the patched sources to `projects/neoforge/src/main/java`.
+
+#### Config Generation
+
+The plugin creates and configures the tasks to create various configuration files used downstream to develop
+mods with this version of NeoForge ([CreateUserDevConfig](src/main/java/net/neoforged/neodev/CreateUserDevConfig.java)), or install it ([CreateInstallerProfile](src/main/java/net/neoforged/neodev/installer/CreateInstallerProfile.java) and [CreateLauncherProfile](src/main/java/net/neoforged/neodev/installer/CreateLauncherProfile.java)).
+
+A separate userdev profile is created for use by other subprojects in this repository.
+The only difference is that it uses the FML launch types ending in `dev` rather than `userdev`.
+
+#### Patch Generation
+
+NeoForge injects its hooks into Minecraft by patching the decompiled source code.
+Changes are made locally to the decompiled and patched source.
+Since that source cannot be published, patches need to be generated before checking in.
+The plugin configures the necessary task to do this
+([GenerateSourcePatches](src/main/java/net/neoforged/neodev/GenerateSourcePatches.java)).
+
+The source patches are only used during development of NeoForge itself and development of mods that use Gradle plugins implementing the decompile/patch/recompile pipeline.
+For use by the installer intended for players as well as Gradle plugins wanting to replicate the production artifacts more closely, binary patches are generated using the ([GenerateBinaryPatches](src/main/java/net/neoforged/neodev/GenerateBinaryPatches.java)) task.
+
+### NeoDevExtraPlugin
+
+Sources: [NeoDevExtraPlugin.java](src/main/java/net/neoforged/neodev/NeoDevExtraPlugin.java)
+
+This plugin can be applied to obtain a dependency on the `neoforge` project to depend on NeoForge including Minecraft
+itself. Besides wiring up the dependency, it also creates run configurations based on the run-types defined in the
+`neoforge` project.
+
+### MinecraftDependenciesPlugin
+
+This plugin is reused from [ModDevGradle](https://github.com/neoforged/ModDevGradle/).
+
+It sets up repositories and attributes such that
+the [libraries that Minecraft itself depends upon](https://github.com/neoforged/GradleMinecraftDependencies) can be
+used.
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 6784052459..6d0ce4c5af 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -1,3 +1,26 @@
plugins {
+ id 'java-gradle-plugin'
id 'groovy-gradle-plugin'
}
+
+repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ maven {
+ name = "NeoForged"
+ url = "https://maven.neoforged.net/releases"
+ content {
+ includeGroup "codechicken"
+ includeGroup "net.neoforged"
+ }
+ }
+}
+
+dependencies {
+ // buildSrc is an includedbuild of the parent directory (gradle.parent)
+ // ../settings.gradle sets these version properties accordingly
+ implementation "net.neoforged:moddev-gradle:${gradle.parent.ext.moddevgradle_plugin_version}"
+
+ implementation "com.google.code.gson:gson:${gradle.parent.ext.gson_version}"
+ implementation "io.codechicken:DiffPatch:${gradle.parent.ext.diffpatch_version}"
+}
diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/buildSrc/src/main/groovy/neoforge.formatting-conventions.gradle b/buildSrc/src/main/groovy/neoforge.formatting-conventions.gradle
index 0ba7b48852..e0f554b820 100644
--- a/buildSrc/src/main/groovy/neoforge.formatting-conventions.gradle
+++ b/buildSrc/src/main/groovy/neoforge.formatting-conventions.gradle
@@ -2,9 +2,14 @@ import java.util.regex.Matcher
project.plugins.apply('com.diffplug.spotless')
-final generatePackageInfos = tasks.register('generatePackageInfos', Task) {
- doLast {
- fileTree('src/main/java').each { javaFile ->
+abstract class GeneratePackageInfos extends DefaultTask {
+ @InputFiles
+ @PathSensitive(PathSensitivity.RELATIVE)
+ abstract ConfigurableFileCollection getFiles();
+
+ @TaskAction
+ void generatePackageInfos() {
+ getFiles().each { javaFile ->
def packageInfoFile = new File(javaFile.parent, 'package-info.java')
if (!packageInfoFile.exists()) {
def pkgName = javaFile.toString().replaceAll(Matcher.quoteReplacement(File.separator), '/')
@@ -27,6 +32,9 @@ final generatePackageInfos = tasks.register('generatePackageInfos', Task) {
}
}
}
+final generatePackageInfos = tasks.register('generatePackageInfos', GeneratePackageInfos) {
+ it.files.from fileTree("src/main/java")
+}
spotless {
java {
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/ApplyAccessTransformer.java b/buildSrc/src/main/java/net/neoforged/neodev/ApplyAccessTransformer.java
new file mode 100644
index 0000000000..7131cc8fdd
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/ApplyAccessTransformer.java
@@ -0,0 +1,72 @@
+package net.neoforged.neodev;
+
+import net.neoforged.neodev.utils.FileUtils;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Classpath;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Internal;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Runs JavaSourceTransformer to apply
+ * access transformers to the Minecraft source code for extending the access level of existing classes/methods/etc.
+ *
+ * Note that at runtime, FML also applies access transformers.
+ */
+abstract class ApplyAccessTransformer extends JavaExec {
+ @InputFile
+ public abstract RegularFileProperty getInputJar();
+
+ @InputFile
+ public abstract RegularFileProperty getAccessTransformer();
+
+ @Input
+ public abstract Property getValidate();
+
+ @OutputFile
+ public abstract RegularFileProperty getOutputJar();
+
+ // Used to give JST more information about the classes.
+ @Classpath
+ public abstract ConfigurableFileCollection getLibraries();
+
+ @Internal
+ public abstract RegularFileProperty getLibrariesFile();
+
+ @Inject
+ public ApplyAccessTransformer() {}
+
+ @Override
+ @TaskAction
+ public void exec() {
+ try {
+ FileUtils.writeLinesSafe(
+ getLibrariesFile().getAsFile().get().toPath(),
+ getLibraries().getFiles().stream().map(File::getAbsolutePath).toList(),
+ StandardCharsets.UTF_8);
+ } catch (IOException exception) {
+ throw new UncheckedIOException("Failed to write libraries for JST.", exception);
+ }
+
+ args(
+ "--enable-accesstransformers",
+ "--access-transformer", getAccessTransformer().getAsFile().get().getAbsolutePath(),
+ "--access-transformer-validation", getValidate().get() ? "error" : "log",
+ "--libraries-list", getLibrariesFile().getAsFile().get().getAbsolutePath(),
+ getInputJar().getAsFile().get().getAbsolutePath(),
+ getOutputJar().getAsFile().get().getAbsolutePath());
+
+ super.exec();
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/ApplyPatches.java b/buildSrc/src/main/java/net/neoforged/neodev/ApplyPatches.java
new file mode 100644
index 0000000000..037e5f9bf6
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/ApplyPatches.java
@@ -0,0 +1,79 @@
+package net.neoforged.neodev;
+
+import io.codechicken.diffpatch.cli.PatchOperation;
+import io.codechicken.diffpatch.util.Input.MultiInput;
+import io.codechicken.diffpatch.util.Output.MultiOutput;
+import io.codechicken.diffpatch.util.PatchMode;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Project;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.PathSensitive;
+import org.gradle.api.tasks.PathSensitivity;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.work.DisableCachingByDefault;
+
+import javax.inject.Inject;
+import java.io.IOException;
+
+/**
+ * Applies Java source patches to a source jar and produces a patched source jar as an output.
+ * It can optionally store rejected hunks into a given folder, which is primarily used for updating
+ * when the original sources changed and some hunks are expected to fail.
+ */
+@DisableCachingByDefault(because = "Not worth caching")
+abstract class ApplyPatches extends DefaultTask {
+ @InputFile
+ public abstract RegularFileProperty getOriginalJar();
+
+ @InputDirectory
+ @PathSensitive(PathSensitivity.NONE)
+ public abstract DirectoryProperty getPatchesFolder();
+
+ @OutputFile
+ public abstract RegularFileProperty getPatchedJar();
+
+ @OutputDirectory
+ public abstract DirectoryProperty getRejectsFolder();
+
+ @Input
+ protected abstract Property getIsUpdating();
+
+ @Inject
+ public ApplyPatches(Project project) {
+ getIsUpdating().set(project.getProviders().gradleProperty("updating").map(Boolean::parseBoolean).orElse(false));
+ }
+
+ @TaskAction
+ public void applyPatches() throws IOException {
+ var isUpdating = getIsUpdating().get();
+
+ var builder = PatchOperation.builder()
+ .logTo(getLogger()::lifecycle)
+ .baseInput(MultiInput.detectedArchive(getOriginalJar().get().getAsFile().toPath()))
+ .patchesInput(MultiInput.folder(getPatchesFolder().get().getAsFile().toPath()))
+ .patchedOutput(MultiOutput.detectedArchive(getPatchedJar().get().getAsFile().toPath()))
+ .rejectsOutput(MultiOutput.folder(getRejectsFolder().get().getAsFile().toPath()))
+ .mode(isUpdating ? PatchMode.FUZZY : PatchMode.ACCESS)
+ .aPrefix("a/")
+ .bPrefix("b/")
+ .level(isUpdating ? io.codechicken.diffpatch.util.LogLevel.ALL : io.codechicken.diffpatch.util.LogLevel.WARN)
+ .minFuzz(0.9f); // The 0.5 default in DiffPatch is too low.
+
+ var result = builder.build().operate();
+
+ int exit = result.exit;
+ if (exit != 0 && exit != 1) {
+ throw new RuntimeException("DiffPatch failed with exit code: " + exit);
+ }
+ if (exit != 0 && !isUpdating) {
+ throw new RuntimeException("Patches failed to apply.");
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/CreateCleanArtifacts.java b/buildSrc/src/main/java/net/neoforged/neodev/CreateCleanArtifacts.java
new file mode 100644
index 0000000000..9854d597e4
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/CreateCleanArtifacts.java
@@ -0,0 +1,46 @@
+package net.neoforged.neodev;
+
+import net.neoforged.nfrtgradle.CreateMinecraftArtifacts;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.OutputFile;
+
+import javax.inject.Inject;
+
+abstract class CreateCleanArtifacts extends CreateMinecraftArtifacts {
+ /**
+ * The unmodified downloaded client jar.
+ */
+ @OutputFile
+ abstract RegularFileProperty getRawClientJar();
+
+ @OutputFile
+ abstract RegularFileProperty getCleanClientJar();
+
+ /**
+ * The unmodified downloaded server jar.
+ */
+ @OutputFile
+ abstract RegularFileProperty getRawServerJar();
+
+ @OutputFile
+ abstract RegularFileProperty getCleanServerJar();
+
+ @OutputFile
+ abstract RegularFileProperty getCleanJoinedJar();
+
+ @OutputFile
+ abstract RegularFileProperty getMergedMappings();
+
+ @Inject
+ public CreateCleanArtifacts() {
+ getAdditionalResults().put("node.downloadClient.output.output", getRawClientJar().getAsFile());
+ getAdditionalResults().put("node.stripClient.output.output", getCleanClientJar().getAsFile());
+ getAdditionalResults().put("node.downloadServer.output.output", getRawServerJar().getAsFile());
+ getAdditionalResults().put("node.stripServer.output.output", getCleanServerJar().getAsFile());
+ getAdditionalResults().put("node.rename.output.output", getCleanJoinedJar().getAsFile());
+ getAdditionalResults().put("node.mergeMappings.output.output", getMergedMappings().getAsFile());
+
+ // TODO: does anyone care about this? they should be contained in the client mappings
+ //"--write-result", "node.downloadServerMappings.output.output:" + getServerMappings().get().getAsFile().getAbsolutePath()
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/CreateUserDevConfig.java b/buildSrc/src/main/java/net/neoforged/neodev/CreateUserDevConfig.java
new file mode 100644
index 0000000000..e046bf09c9
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/CreateUserDevConfig.java
@@ -0,0 +1,199 @@
+package net.neoforged.neodev;
+
+import com.google.gson.GsonBuilder;
+import net.neoforged.neodev.utils.FileUtils;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.ListProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Creates the userdev configuration file used by the various Gradle plugins used to develop
+ * mods for NeoForge, such as Architectury Loom,
+ * ModDevGradle
+ * or NeoGradle.
+ */
+abstract class CreateUserDevConfig extends DefaultTask {
+ @Inject
+ public CreateUserDevConfig() {}
+
+ @Input
+ abstract Property getFmlVersion();
+
+ @Input
+ abstract Property getMinecraftVersion();
+
+ @Input
+ abstract Property getNeoForgeVersion();
+
+ @Input
+ abstract Property getRawNeoFormVersion();
+
+ @Input
+ abstract ListProperty getLibraries();
+
+ @Input
+ abstract ListProperty getModules();
+
+ @Input
+ abstract ListProperty getTestLibraries();
+
+ @Input
+ abstract ListProperty getIgnoreList();
+
+ @Input
+ abstract Property getBinpatcherGav();
+
+ @OutputFile
+ abstract RegularFileProperty getUserDevConfig();
+
+ @TaskAction
+ public void writeUserDevConfig() throws IOException {
+ var config = new UserDevConfig(
+ 2,
+ "net.neoforged:neoform:%s-%s@zip".formatted(getMinecraftVersion().get(), getRawNeoFormVersion().get()),
+ "ats/",
+ "joined.lzma",
+ new BinpatcherConfig(
+ getBinpatcherGav().get(),
+ List.of("--clean", "{clean}", "--output", "{output}", "--apply", "{patch}")),
+ "patches/",
+ "net.neoforged:neoforge:%s:sources".formatted(getNeoForgeVersion().get()),
+ "net.neoforged:neoforge:%s:universal".formatted(getNeoForgeVersion().get()),
+ getLibraries().get(),
+ getTestLibraries().get(),
+ new LinkedHashMap<>(),
+ getModules().get());
+
+ for (var runType : RunType.values()) {
+ var launchTarget = switch (runType) {
+ case CLIENT -> "neoforgeclientdev";
+ case CLIENT_DATA -> "neoforgeclientdatadev";
+ case SERVER_DATA -> "neoforgeserverdatadev";
+ case GAME_TEST_SERVER, SERVER -> "neoforgeserverdev";
+ case JUNIT -> "neoforgejunitdev";
+ };
+
+ List args = new ArrayList<>();
+ Collections.addAll(args,
+ "--launchTarget", launchTarget);
+
+ if (runType == RunType.CLIENT || runType == RunType.JUNIT) {
+ // TODO: this is copied from NG but shouldn't it be the MC version?
+ Collections.addAll(args,
+ "--version", getNeoForgeVersion().get());
+ }
+
+ if (runType == RunType.CLIENT || runType == RunType.CLIENT_DATA || runType == RunType.JUNIT) {
+ Collections.addAll(args,
+ "--assetIndex", "{asset_index}",
+ "--assetsDir", "{assets_root}");
+ }
+
+ Collections.addAll(args,
+ "--gameDir", ".",
+ "--fml.fmlVersion", getFmlVersion().get(),
+ "--fml.mcVersion", getMinecraftVersion().get(),
+ "--fml.neoForgeVersion", getNeoForgeVersion().get(),
+ "--fml.neoFormVersion", getRawNeoFormVersion().get());
+
+ Map systemProperties = new LinkedHashMap<>();
+ systemProperties.put("java.net.preferIPv6Addresses", "system");
+ systemProperties.put("ignoreList", String.join(",", getIgnoreList().get()));
+ systemProperties.put("legacyClassPath.file", "{minecraft_classpath_file}");
+
+ if (runType == RunType.CLIENT || runType == RunType.GAME_TEST_SERVER) {
+ systemProperties.put("neoforge.enableGameTest", "true");
+
+ if (runType == RunType.GAME_TEST_SERVER) {
+ systemProperties.put("neoforge.gameTestServer", "true");
+ }
+ }
+
+ config.runs().put(runType.jsonName, new UserDevRunType(
+ runType != RunType.JUNIT,
+ "cpw.mods.bootstraplauncher.BootstrapLauncher",
+ args,
+ List.of(
+ "-p", "{modules}",
+ "--add-modules", "ALL-MODULE-PATH",
+ "--add-opens", "java.base/java.util.jar=cpw.mods.securejarhandler",
+ "--add-opens", "java.base/java.lang.invoke=cpw.mods.securejarhandler",
+ "--add-exports", "java.base/sun.security.util=cpw.mods.securejarhandler",
+ "--add-exports", "jdk.naming.dns/com.sun.jndi.dns=java.naming"),
+ runType == RunType.CLIENT || runType == RunType.JUNIT || runType == RunType.CLIENT_DATA,
+ runType == RunType.GAME_TEST_SERVER || runType == RunType.SERVER || runType == RunType.SERVER_DATA,
+ runType == RunType.CLIENT_DATA || runType == RunType.SERVER_DATA,
+ runType == RunType.CLIENT || runType == RunType.GAME_TEST_SERVER,
+ runType == RunType.JUNIT,
+ Map.of(
+ "MOD_CLASSES", "{source_roots}"),
+ systemProperties
+ ));
+ }
+
+ FileUtils.writeStringSafe(
+ getUserDevConfig().getAsFile().get().toPath(),
+ new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(config),
+ // TODO: Not sure what this should be? Most likely the file is ASCII.
+ StandardCharsets.UTF_8);
+ }
+
+ private enum RunType {
+ CLIENT("client"),
+ CLIENT_DATA("clientData"),
+ SERVER_DATA("serverData"),
+ GAME_TEST_SERVER("gameTestServer"),
+ SERVER("server"),
+ JUNIT("junit");
+
+ private final String jsonName;
+
+ RunType(String jsonName) {
+ this.jsonName = jsonName;
+ }
+ }
+}
+
+record UserDevConfig(
+ int spec,
+ String mcp,
+ String ats,
+ String binpatches,
+ BinpatcherConfig binpatcher,
+ String patches,
+ String sources,
+ String universal,
+ List libraries,
+ List testLibraries,
+ Map runs,
+ List modules) {}
+
+record BinpatcherConfig(
+ String version,
+ List args) {}
+
+record UserDevRunType(
+ boolean singleInstance,
+ String main,
+ List args,
+ List jvmArgs,
+ boolean client,
+ boolean server,
+ boolean dataGenerator,
+ boolean gameTest,
+ boolean unitTest,
+ Map env,
+ Map props) {}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/GenerateBinaryPatches.java b/buildSrc/src/main/java/net/neoforged/neodev/GenerateBinaryPatches.java
new file mode 100644
index 0000000000..35a06a8c33
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/GenerateBinaryPatches.java
@@ -0,0 +1,70 @@
+package net.neoforged.neodev;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+
+import javax.inject.Inject;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+abstract class GenerateBinaryPatches extends JavaExec {
+ @Inject
+ public GenerateBinaryPatches() {}
+
+ /**
+ * The jar file containing classes in the base state.
+ */
+ @InputFile
+ abstract RegularFileProperty getCleanJar();
+
+ /**
+ * The jar file containing classes in the desired target state.
+ */
+ @InputFile
+ abstract RegularFileProperty getPatchedJar();
+
+ @InputFile
+ abstract RegularFileProperty getMappings();
+
+ /**
+ * This directory of patch files for the Java sources is used as a hint to only diff class files that
+ * supposedly have changed. If it is not set, the tool will diff every .class file instead.
+ */
+ @InputDirectory
+ @Optional
+ abstract DirectoryProperty getSourcePatchesFolder();
+
+ /**
+ * The location where the LZMA compressed binary patches are written to.
+ */
+ @OutputFile
+ abstract RegularFileProperty getOutputFile();
+
+ @Override
+ public void exec() {
+ args("--clean", getCleanJar().get().getAsFile().getAbsolutePath());
+ args("--dirty", getPatchedJar().get().getAsFile().getAbsolutePath());
+ args("--srg", getMappings().get().getAsFile().getAbsolutePath());
+ if (getSourcePatchesFolder().isPresent()) {
+ args("--patches", getSourcePatchesFolder().get().getAsFile().getAbsolutePath());
+ }
+ args("--output", getOutputFile().get().getAsFile().getAbsolutePath());
+
+ var logFile = new File(getTemporaryDir(), "console.log");
+ try (var out = new BufferedOutputStream(new FileOutputStream(logFile))) {
+ getLogger().info("Logging binpatcher console output to {}", logFile.getAbsolutePath());
+ setStandardOutput(out);
+ super.exec();
+ } catch (IOException e) {
+ throw new GradleException("Failed to create binary patches.", e);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/GenerateSourcePatches.java b/buildSrc/src/main/java/net/neoforged/neodev/GenerateSourcePatches.java
new file mode 100644
index 0000000000..c6e1c7e730
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/GenerateSourcePatches.java
@@ -0,0 +1,55 @@
+package net.neoforged.neodev;
+
+import io.codechicken.diffpatch.cli.CliOperation;
+import io.codechicken.diffpatch.cli.DiffOperation;
+import io.codechicken.diffpatch.util.Input.MultiInput;
+import io.codechicken.diffpatch.util.Output.MultiOutput;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.PathSensitive;
+import org.gradle.api.tasks.PathSensitivity;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.IOException;
+
+abstract class GenerateSourcePatches extends DefaultTask {
+ @InputFile
+ public abstract RegularFileProperty getOriginalJar();
+
+ @InputDirectory
+ @PathSensitive(PathSensitivity.RELATIVE)
+ public abstract DirectoryProperty getModifiedSources();
+
+ @OutputFile
+ public abstract RegularFileProperty getPatchesJar();
+
+ @Inject
+ public GenerateSourcePatches() {}
+
+ @TaskAction
+ public void generateSourcePatches() throws IOException {
+ var builder = DiffOperation.builder()
+ .logTo(getLogger()::lifecycle)
+ .baseInput(MultiInput.detectedArchive(getOriginalJar().get().getAsFile().toPath()))
+ .changedInput(MultiInput.folder(getModifiedSources().get().getAsFile().toPath()))
+ .patchesOutput(MultiOutput.detectedArchive(getPatchesJar().get().getAsFile().toPath()))
+ .autoHeader(true)
+ .level(io.codechicken.diffpatch.util.LogLevel.WARN)
+ .summary(false)
+ .aPrefix("a/")
+ .bPrefix("b/")
+ .lineEnding("\n");
+
+ CliOperation.Result result = builder.build().operate();
+
+ int exit = result.exit;
+ if (exit != 0 && exit != 1) {
+ throw new RuntimeException("DiffPatch failed with exit code: " + exit);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/NeoDevBasePlugin.java b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevBasePlugin.java
new file mode 100644
index 0000000000..de480b5682
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevBasePlugin.java
@@ -0,0 +1,65 @@
+package net.neoforged.neodev;
+
+import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin;
+import net.neoforged.moddevgradle.internal.NeoDevFacade;
+import net.neoforged.nfrtgradle.CreateMinecraftArtifacts;
+import net.neoforged.nfrtgradle.DownloadAssets;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.tasks.Sync;
+
+public class NeoDevBasePlugin implements Plugin {
+ @Override
+ public void apply(Project project) {
+ // These plugins allow us to declare dependencies on Minecraft libraries needed to compile the official sources
+ project.getPlugins().apply(MinecraftDependenciesPlugin.class);
+
+ var dependencyFactory = project.getDependencyFactory();
+ var tasks = project.getTasks();
+ var neoDevBuildDir = project.getLayout().getBuildDirectory().dir("neodev");
+
+ var rawNeoFormVersion = project.getProviders().gradleProperty("neoform_version");
+ var minecraftVersion = project.getProviders().gradleProperty("minecraft_version");
+ var mcAndNeoFormVersion = minecraftVersion.zip(rawNeoFormVersion, (mc, nf) -> mc + "-" + nf);
+
+ var extension = project.getExtensions().create(NeoDevExtension.NAME, NeoDevExtension.class);
+
+ var createSources = NeoDevPlugin.configureMinecraftDecompilation(project);
+ // Task must run on sync to have MC resources available for IDEA nondelegated builds.
+ NeoDevFacade.runTaskOnProjectSync(project, createSources);
+
+ tasks.register("setup", Sync.class, task -> {
+ task.setGroup(NeoDevPlugin.GROUP);
+ task.setDescription("Replaces the contents of the base project sources with the unpatched, decompiled Minecraft source code.");
+ task.from(project.zipTree(createSources.flatMap(CreateMinecraftArtifacts::getSourcesArtifact)));
+ task.into(project.file("src/main/java/"));
+ });
+
+ var downloadAssets = tasks.register("downloadAssets", DownloadAssets.class, task -> {
+ task.setGroup(NeoDevPlugin.INTERNAL_GROUP);
+ task.getNeoFormArtifact().set(createSources.flatMap(CreateMinecraftArtifacts::getNeoFormArtifact));
+ task.getAssetPropertiesFile().set(neoDevBuildDir.map(dir -> dir.file("minecraft_assets.properties")));
+ });
+
+ // MC looks for its resources on the classpath.
+ var runtimeClasspath = project.getConfigurations().getByName(JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME);
+ runtimeClasspath.getDependencies().add(
+ dependencyFactory.create(
+ project.files(createSources.flatMap(CreateMinecraftArtifacts::getResourcesArtifact))
+ )
+ );
+ NeoDevFacade.setupRuns(
+ project,
+ neoDevBuildDir,
+ extension.getRuns(),
+ // Pass an empty file collection for the userdev config.
+ // This will cause MDG to generate a dummy config suitable for vanilla.
+ project.files(),
+ modulePath -> {},
+ legacyClasspath -> {},
+ downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile),
+ mcAndNeoFormVersion
+ );
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/NeoDevConfigurations.java b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevConfigurations.java
new file mode 100644
index 0000000000..35eb5b570d
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevConfigurations.java
@@ -0,0 +1,204 @@
+package net.neoforged.neodev;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.attributes.Bundling;
+import org.gradle.api.plugins.JavaPlugin;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Helper class to keep track of the many {@link Configuration}s used for the {@code neoforge} project.
+ */
+class NeoDevConfigurations {
+ static NeoDevConfigurations createAndSetup(Project project) {
+ return new NeoDevConfigurations(project);
+ }
+
+ //
+ // Configurations against which dependencies should be declared ("dependency scopes").
+ //
+
+ /**
+ * Only the NeoForm data zip and the dependencies to run NeoForm.
+ * Does not contain the dependencies to run vanilla Minecraft.
+ */
+ final Configuration neoFormData;
+ /**
+ * Only the NeoForm dependencies.
+ * These are the dependencies required to run NeoForm-decompiled Minecraft.
+ * Does not contain the dependencies to run the NeoForm process itself.
+ */
+ final Configuration neoFormDependencies;
+ /**
+ * Libraries used by NeoForge at compilation and runtime.
+ * These will end up on the MC-BOOTSTRAP module layer.
+ */
+ final Configuration libraries;
+ /**
+ * Libraries used by NeoForge at compilation and runtime that need to be placed on the jvm's module path to end up in the boot layer.
+ * Currently, this only contains the few dependencies that are needed to create the MC-BOOTSTRAP module layer.
+ * (i.e. BootstrapLauncher and its dependencies).
+ */
+ final Configuration moduleLibraries;
+ /**
+ * Libraries that should be accessible in mod development environments at compilation time only.
+ * Currently, this is only used for MixinExtras, which is already available at runtime via JiJ in the NeoForge universal jar.
+ */
+ final Configuration userdevCompileOnly;
+ /**
+ * Libraries that should be accessible at runtime in unit tests.
+ * Currently, this only contains the fml-junit test fixtures.
+ */
+ final Configuration userdevTestFixtures;
+
+ //
+ // Resolvable configurations.
+ //
+
+ /**
+ * Resolved {@link #neoFormData}.
+ * This is used to add NeoForm to the installer libraries.
+ * Only the zip is used (for the mappings), not the NeoForm tools, so it's not transitive.
+ */
+ final Configuration neoFormDataOnly;
+ /**
+ * Resolvable {@link #neoFormDependencies}.
+ */
+ final Configuration neoFormClasspath;
+ /**
+ * Resolvable {@link #moduleLibraries}.
+ */
+ final Configuration modulePath;
+ /**
+ * Userdev dependencies (written to a json file in the userdev jar).
+ * This should contain all of NeoForge's additional dependencies for userdev,
+ * but does not need to include Minecraft or NeoForm's libraries.
+ */
+ final Configuration userdevClasspath;
+ /**
+ * Resolvable {@link #userdevCompileOnly}, to add these entries to the ignore list of BootstrapLauncher.
+ */
+ final Configuration userdevCompileOnlyClasspath;
+ /**
+ * Resolvable {@link #userdevTestFixtures}, to write it in the userdev jar.
+ */
+ final Configuration userdevTestClasspath;
+ /**
+ * Libraries that need to be added to the classpath when launching NeoForge through the launcher.
+ * This contains all dependencies added by NeoForge, but does not include all of Minecraft's libraries.
+ * This is also used to produce the legacy classpath file for server installs.
+ */
+ final Configuration launcherProfileClasspath;
+
+ //
+ // The configurations for resolution only are declared in the build.gradle file.
+ //
+
+ /**
+ * To download each executable tool, we use a resolvable configuration.
+ * These configurations support both declaration and resolution.
+ */
+ final Map toolClasspaths;
+
+ private static Configuration dependencyScope(ConfigurationContainer configurations, String name) {
+ return configurations.create(name, configuration -> {
+ configuration.setCanBeConsumed(false);
+ configuration.setCanBeResolved(false);
+ });
+ }
+
+ private static Configuration resolvable(ConfigurationContainer configurations, String name) {
+ return configurations.create(name, configuration -> {
+ configuration.setCanBeConsumed(false);
+ configuration.setCanBeDeclared(false);
+ });
+ }
+
+ private NeoDevConfigurations(Project project) {
+ var configurations = project.getConfigurations();
+
+ neoFormData = dependencyScope(configurations, "neoFormData");
+ neoFormDependencies = dependencyScope(configurations, "neoFormDependencies");
+ libraries = dependencyScope(configurations, "libraries");
+ moduleLibraries = dependencyScope(configurations, "moduleLibraries");
+ userdevCompileOnly = dependencyScope(configurations, "userdevCompileOnly");
+ userdevTestFixtures = dependencyScope(configurations, "userdevTestFixtures");
+
+ neoFormDataOnly = resolvable(configurations, "neoFormDataOnly");
+ neoFormClasspath = resolvable(configurations, "neoFormClasspath");
+ modulePath = resolvable(configurations, "modulePath");
+ userdevClasspath = resolvable(configurations, "userdevClasspath");
+ userdevCompileOnlyClasspath = resolvable(configurations, "userdevCompileOnlyClasspath");
+ userdevTestClasspath = resolvable(configurations, "userdevTestClasspath");
+ launcherProfileClasspath = resolvable(configurations, "launcherProfileClasspath");
+
+ // Libraries & module libraries & MC dependencies need to be available when compiling in NeoDev,
+ // and on the runtime classpath too for IDE debugging support.
+ configurations.getByName("implementation").extendsFrom(libraries, moduleLibraries, neoFormDependencies);
+
+ // runtimeClasspath is our reference for all MC dependency versions.
+ // Make sure that any classpath we resolve is consistent with it.
+ var runtimeClasspath = configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
+
+ neoFormDataOnly.setTransitive(false);
+ neoFormDataOnly.extendsFrom(neoFormData);
+
+ neoFormClasspath.extendsFrom(neoFormDependencies);
+
+ modulePath.extendsFrom(moduleLibraries);
+ modulePath.shouldResolveConsistentlyWith(runtimeClasspath);
+
+ userdevClasspath.extendsFrom(libraries, moduleLibraries, userdevCompileOnly);
+ userdevClasspath.shouldResolveConsistentlyWith(runtimeClasspath);
+
+ userdevCompileOnlyClasspath.extendsFrom(userdevCompileOnly);
+ userdevCompileOnlyClasspath.shouldResolveConsistentlyWith(runtimeClasspath);
+
+ userdevTestClasspath.extendsFrom(userdevTestFixtures);
+ userdevTestClasspath.shouldResolveConsistentlyWith(runtimeClasspath);
+
+ launcherProfileClasspath.extendsFrom(libraries, moduleLibraries);
+ launcherProfileClasspath.shouldResolveConsistentlyWith(runtimeClasspath);
+
+ toolClasspaths = createToolClasspaths(project);
+ }
+
+ private static Map createToolClasspaths(Project project) {
+ var configurations = project.getConfigurations();
+ var dependencyFactory = project.getDependencyFactory();
+
+ var result = new HashMap();
+
+ for (var tool : Tools.values()) {
+ var configuration = configurations.create(tool.getGradleConfigurationName(), spec -> {
+ spec.setDescription("Resolves the executable for tool " + tool.name());
+ spec.setCanBeConsumed(false);
+ // Tools are considered to be executable jars.
+ // Gradle requires the classpath for JavaExec to only contain a single file for these.
+ if (tool.isRequestFatJar()) {
+ spec.attributes(attr -> {
+ attr.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.SHADOWED));
+ });
+ }
+
+ var gav = tool.asGav(project);
+ spec.getDependencies().add(dependencyFactory.create(gav));
+ });
+ result.put(tool, configuration);
+ }
+
+ return Map.copyOf(result);
+ }
+
+ /**
+ * Gets a configuration representing the classpath for an executable tool.
+ * Some tools are assumed to be executable jars, and their configurations only contain a single file.
+ */
+ public Configuration getExecutableTool(Tools tool) {
+ return Objects.requireNonNull(toolClasspaths.get(tool));
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/NeoDevExtension.java b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevExtension.java
new file mode 100644
index 0000000000..224c7ab7e9
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevExtension.java
@@ -0,0 +1,35 @@
+package net.neoforged.neodev;
+
+import net.neoforged.moddevgradle.dsl.ModModel;
+import net.neoforged.moddevgradle.dsl.RunModel;
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+
+public class NeoDevExtension {
+ public static final String NAME = "neoDev";
+
+ private final NamedDomainObjectContainer mods;
+ private final NamedDomainObjectContainer runs;
+
+ public NeoDevExtension(Project project) {
+ mods = project.container(ModModel.class);
+ runs = project.container(RunModel.class, name -> project.getObjects().newInstance(RunModel.class, name, project, mods));
+ }
+
+ public NamedDomainObjectContainer getMods() {
+ return mods;
+ }
+
+ public void mods(Action> action) {
+ action.execute(mods);
+ }
+
+ public NamedDomainObjectContainer getRuns() {
+ return runs;
+ }
+
+ public void runs(Action> action) {
+ action.execute(runs);
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/NeoDevExtraPlugin.java b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevExtraPlugin.java
new file mode 100644
index 0000000000..fd5c0d391e
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevExtraPlugin.java
@@ -0,0 +1,81 @@
+package net.neoforged.neodev;
+
+import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin;
+import net.neoforged.moddevgradle.internal.NeoDevFacade;
+import net.neoforged.nfrtgradle.DownloadAssets;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.artifacts.dsl.DependencyFactory;
+import org.gradle.api.tasks.testing.Test;
+
+import java.util.function.Consumer;
+
+// TODO: the only point of this is to configure runs that depend on neoforge. Maybe this could be done with less code duplication...
+// TODO: Gradle says "thou shalt not referenceth otherth projects" yet here we are
+// TODO: depend on neoforge configurations that the moddev plugin also uses
+public class NeoDevExtraPlugin implements Plugin {
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().apply(MinecraftDependenciesPlugin.class);
+
+ var neoForgeProject = project.getRootProject().getChildProjects().get("neoforge");
+
+ var dependencyFactory = project.getDependencyFactory();
+ var tasks = project.getTasks();
+ var neoDevBuildDir = project.getLayout().getBuildDirectory().dir("neodev");
+
+ var extension = project.getExtensions().create(NeoDevExtension.NAME, NeoDevExtension.class);
+
+ var modulePathDependency = projectDep(dependencyFactory, neoForgeProject, "net.neoforged:neoforge-moddev-module-path");
+
+ // TODO: this is temporary
+ var downloadAssets = neoForgeProject.getTasks().named("downloadAssets", DownloadAssets.class);
+
+ var neoForgeConfigOnly = project.getConfigurations().create("neoForgeConfigOnly", spec -> {
+ spec.getDependencies().add(projectDep(dependencyFactory, neoForgeProject, "net.neoforged:neoforge-moddev-config"));
+ });
+
+ Consumer configureLegacyClasspath = spec -> {
+ spec.getDependencies().add(projectDep(dependencyFactory, neoForgeProject, "net.neoforged:neoforge-dependencies"));
+ };
+
+ extension.getRuns().configureEach(run -> {
+ configureLegacyClasspath.accept(run.getAdditionalRuntimeClasspathConfiguration());
+ });
+ NeoDevFacade.setupRuns(
+ project,
+ neoDevBuildDir,
+ extension.getRuns(),
+ neoForgeConfigOnly,
+ modulePath -> modulePath.getDependencies().add(modulePathDependency),
+ configureLegacyClasspath,
+ downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile)
+ );
+
+ var testExtension = project.getExtensions().create(NeoDevTestExtension.NAME, NeoDevTestExtension.class);
+ var testTask = tasks.register("junitTest", Test.class, test -> test.setGroup("verification"));
+ tasks.named("check").configure(task -> task.dependsOn(testTask));
+
+ NeoDevFacade.setupTestTask(
+ project,
+ neoDevBuildDir,
+ testTask,
+ neoForgeConfigOnly,
+ testExtension.getLoadedMods(),
+ testExtension.getTestedMod(),
+ modulePath -> modulePath.getDependencies().add(modulePathDependency),
+ configureLegacyClasspath,
+ downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile)
+ );
+ }
+
+ private static ProjectDependency projectDep(DependencyFactory dependencyFactory, Project project, String capabilityNotation) {
+ var dep = dependencyFactory.create(project);
+ dep.capabilities(caps -> {
+ caps.requireCapability(capabilityNotation);
+ });
+ return dep;
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/NeoDevPlugin.java b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevPlugin.java
new file mode 100644
index 0000000000..d38f91f46b
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevPlugin.java
@@ -0,0 +1,616 @@
+package net.neoforged.neodev;
+
+import net.neoforged.minecraftdependencies.MinecraftDependenciesPlugin;
+import net.neoforged.moddevgradle.internal.NeoDevFacade;
+import net.neoforged.moddevgradle.tasks.JarJar;
+import net.neoforged.neodev.e2e.InstallProductionClient;
+import net.neoforged.neodev.e2e.InstallProductionServer;
+import net.neoforged.neodev.e2e.RunProductionClient;
+import net.neoforged.neodev.e2e.RunProductionServer;
+import net.neoforged.neodev.e2e.TestProductionClient;
+import net.neoforged.neodev.e2e.TestProductionServer;
+import net.neoforged.neodev.installer.CreateArgsFile;
+import net.neoforged.neodev.installer.CreateInstallerProfile;
+import net.neoforged.neodev.installer.CreateLauncherProfile;
+import net.neoforged.neodev.installer.IdentifiedFile;
+import net.neoforged.neodev.installer.InstallerProcessor;
+import net.neoforged.neodev.utils.DependencyUtils;
+import net.neoforged.nfrtgradle.CreateMinecraftArtifacts;
+import net.neoforged.nfrtgradle.DownloadAssets;
+import net.neoforged.nfrtgradle.NeoFormRuntimePlugin;
+import net.neoforged.nfrtgradle.NeoFormRuntimeTask;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.file.Directory;
+import org.gradle.api.file.RegularFile;
+import org.gradle.api.plugins.BasePluginExtension;
+import org.gradle.api.plugins.JavaPlugin;
+import org.gradle.api.plugins.JavaPluginExtension;
+import org.gradle.api.provider.Provider;
+import org.gradle.api.tasks.Sync;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.api.tasks.bundling.Zip;
+
+import java.io.File;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class NeoDevPlugin implements Plugin {
+ static final String GROUP = "neoforge development";
+ static final String INTERNAL_GROUP = "neoforge development/internal";
+
+ @Override
+ public void apply(Project project) {
+ project.getPlugins().apply(MinecraftDependenciesPlugin.class);
+
+ var dependencyFactory = project.getDependencyFactory();
+ var tasks = project.getTasks();
+ var neoDevBuildDir = project.getLayout().getBuildDirectory().dir("neodev");
+
+ var rawNeoFormVersion = project.getProviders().gradleProperty("neoform_version");
+ var fmlVersion = project.getProviders().gradleProperty("fancy_mod_loader_version");
+ var minecraftVersion = project.getProviders().gradleProperty("minecraft_version");
+ var neoForgeVersion = project.provider(() -> project.getVersion().toString());
+ var mcAndNeoFormVersion = minecraftVersion.zip(rawNeoFormVersion, (mc, nf) -> mc + "-" + nf);
+
+ var extension = project.getExtensions().create(NeoDevExtension.NAME, NeoDevExtension.class);
+ var configurations = NeoDevConfigurations.createAndSetup(project);
+
+ /*
+ * MINECRAFT SOURCES SETUP
+ */
+ // 1. Obtain decompiled Minecraft sources jar using NeoForm.
+ var createSourceArtifacts = configureMinecraftDecompilation(project);
+ // Task must run on sync to have MC resources available for IDEA nondelegated builds.
+ NeoDevFacade.runTaskOnProjectSync(project, createSourceArtifacts);
+
+ // 2. Apply AT to the source jar from 1.
+ var atFile = project.getRootProject().file("src/main/resources/META-INF/accesstransformer.cfg");
+ var applyAt = configureAccessTransformer(
+ project,
+ configurations,
+ createSourceArtifacts,
+ neoDevBuildDir,
+ atFile);
+
+ // 3. Apply patches to the source jar from 2.
+ var patchesFolder = project.getRootProject().file("patches");
+ var applyPatches = tasks.register("applyPatches", ApplyPatches.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getOriginalJar().set(applyAt.flatMap(ApplyAccessTransformer::getOutputJar));
+ task.getPatchesFolder().set(patchesFolder);
+ task.getPatchedJar().set(neoDevBuildDir.map(dir -> dir.file("artifacts/patched-sources.jar")));
+ task.getRejectsFolder().set(project.getRootProject().file("rejects"));
+ });
+
+ // 4. Unpack jar from 3.
+ var mcSourcesPath = project.file("src/main/java");
+ tasks.register("setup", Sync.class, task -> {
+ task.setGroup(GROUP);
+ task.from(project.zipTree(applyPatches.flatMap(ApplyPatches::getPatchedJar)));
+ task.into(mcSourcesPath);
+ });
+
+ /*
+ * RUNS SETUP
+ */
+
+ // 1. Write configs that contain the runs in a format understood by MDG/NG/etc. Currently one for neodev and one for userdev.
+ var writeUserDevConfig = tasks.register("writeUserDevConfig", CreateUserDevConfig.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getUserDevConfig().set(neoDevBuildDir.map(dir -> dir.file("userdev-config.json")));
+ task.getFmlVersion().set(fmlVersion);
+ task.getMinecraftVersion().set(minecraftVersion);
+ task.getNeoForgeVersion().set(neoForgeVersion);
+ task.getRawNeoFormVersion().set(rawNeoFormVersion);
+ task.getLibraries().addAll(DependencyUtils.configurationToGavList(configurations.userdevClasspath));
+ task.getModules().addAll(DependencyUtils.configurationToGavList(configurations.modulePath));
+ task.getTestLibraries().addAll(DependencyUtils.configurationToGavList(configurations.userdevTestClasspath));
+ task.getTestLibraries().add(neoForgeVersion.map(v -> "net.neoforged:testframework:" + v));
+ task.getIgnoreList().addAll(configurations.userdevCompileOnlyClasspath.getIncoming().getArtifacts().getResolvedArtifacts().map(results -> {
+ return results.stream().map(r -> r.getFile().getName()).toList();
+ }));
+ task.getIgnoreList().addAll("client-extra", "neoforge-");
+ task.getBinpatcherGav().set(Tools.BINPATCHER.asGav(project));
+ });
+
+ // 2. Task to download assets.
+ var downloadAssets = tasks.register("downloadAssets", DownloadAssets.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getNeoFormArtifact().set(mcAndNeoFormVersion.map(v -> "net.neoforged:neoform:" + v + "@zip"));
+ task.getAssetPropertiesFile().set(neoDevBuildDir.map(dir -> dir.file("minecraft_assets.properties")));
+ });
+
+ // FML needs Minecraft resources on the classpath to find it. Add to runtimeOnly so subprojects also get it at runtime.
+ var runtimeClasspath = project.getConfigurations().getByName(JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME);
+ runtimeClasspath.getDependencies().add(
+ dependencyFactory.create(
+ project.files(createSourceArtifacts.flatMap(CreateMinecraftArtifacts::getResourcesArtifact))
+ )
+ );
+ // 3. Let MDG do the rest of the setup. :)
+ NeoDevFacade.setupRuns(
+ project,
+ neoDevBuildDir,
+ extension.getRuns(),
+ writeUserDevConfig,
+ modulePath -> {
+ modulePath.extendsFrom(configurations.moduleLibraries);
+ },
+ legacyClassPath -> {
+ legacyClassPath.getDependencies().addLater(mcAndNeoFormVersion.map(v -> dependencyFactory.create("net.neoforged:neoform:" + v).capabilities(caps -> {
+ caps.requireCapability("net.neoforged:neoform-dependencies");
+ })));
+ legacyClassPath.extendsFrom(configurations.libraries, configurations.moduleLibraries, configurations.userdevCompileOnly);
+ },
+ downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile)
+ );
+ // TODO: Gradle run tasks should be moved to gradle group GROUP
+
+ /*
+ * OTHER TASKS
+ */
+
+ // Generate source patches into a patch archive.
+ var genSourcePatches = tasks.register("generateSourcePatches", GenerateSourcePatches.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getOriginalJar().set(applyAt.flatMap(ApplyAccessTransformer::getOutputJar));
+ task.getModifiedSources().set(project.file("src/main/java"));
+ task.getPatchesJar().set(neoDevBuildDir.map(dir -> dir.file("source-patches.zip")));
+ });
+
+ // Update the patch/ folder with the current patches.
+ tasks.register("genPatches", Sync.class, task -> {
+ task.setGroup(GROUP);
+ task.from(project.zipTree(genSourcePatches.flatMap(GenerateSourcePatches::getPatchesJar)));
+ task.into(project.getRootProject().file("patches"));
+ });
+
+ // Universal jar = the jar that contains NeoForge classes
+ // TODO: signing?
+ var universalJar = tasks.register("universalJar", Jar.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getArchiveClassifier().set("universal");
+
+ task.from(project.zipTree(
+ tasks.named("jar", Jar.class).flatMap(AbstractArchiveTask::getArchiveFile)));
+ task.exclude("net/minecraft/**");
+ task.exclude("com/**");
+ task.exclude("mcp/**");
+
+ task.manifest(manifest -> {
+ manifest.attributes(Map.of("FML-System-Mods", "neoforge"));
+ // These attributes are used from NeoForgeVersion.java to find the NF version without command line arguments.
+ manifest.attributes(
+ Map.of(
+ "Specification-Title", "NeoForge",
+ "Specification-Vendor", "NeoForge",
+ "Specification-Version", project.getVersion().toString().substring(0, project.getVersion().toString().lastIndexOf(".")),
+ "Implementation-Title", project.getGroup(),
+ "Implementation-Version", project.getVersion(),
+ "Implementation-Vendor", "NeoForged"),
+ "net/neoforged/neoforge/internal/versions/neoforge/");
+ manifest.attributes(
+ Map.of(
+ "Specification-Title", "Minecraft",
+ "Specification-Vendor", "Mojang",
+ "Specification-Version", minecraftVersion,
+ "Implementation-Title", "MCP",
+ "Implementation-Version", mcAndNeoFormVersion,
+ "Implementation-Vendor", "NeoForged"),
+ "net/neoforged/neoforge/versions/neoform/");
+ });
+ });
+
+ var jarJarTask = JarJar.registerWithConfiguration(project, "jarJar");
+ jarJarTask.configure(task -> task.setGroup(INTERNAL_GROUP));
+ universalJar.configure(task -> task.from(jarJarTask));
+
+ var createCleanArtifacts = tasks.register("createCleanArtifacts", CreateCleanArtifacts.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.setDescription("This task retrieves various files for the Minecraft version without applying NeoForge patches to them");
+ var cleanArtifactsDir = neoDevBuildDir.map(dir -> dir.dir("artifacts/clean"));
+ task.getRawClientJar().set(cleanArtifactsDir.map(dir -> dir.file("raw-client.jar")));
+ task.getCleanClientJar().set(cleanArtifactsDir.map(dir -> dir.file("client.jar")));
+ task.getRawServerJar().set(cleanArtifactsDir.map(dir -> dir.file("raw-server.jar")));
+ task.getCleanServerJar().set(cleanArtifactsDir.map(dir -> dir.file("server.jar")));
+ task.getCleanJoinedJar().set(cleanArtifactsDir.map(dir -> dir.file("joined.jar")));
+ task.getMergedMappings().set(cleanArtifactsDir.map(dir -> dir.file("merged-mappings.txt")));
+ task.getNeoFormArtifact().set(mcAndNeoFormVersion.map(version -> "net.neoforged:neoform:" + version + "@zip"));
+ });
+
+ var binaryPatchOutputs = configureBinaryPatchCreation(
+ project,
+ configurations,
+ createCleanArtifacts,
+ neoDevBuildDir,
+ patchesFolder
+ );
+
+ // Launcher profile = the version.json file used by the Minecraft launcher.
+ var createLauncherProfile = tasks.register("createLauncherProfile", CreateLauncherProfile.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getFmlVersion().set(fmlVersion);
+ task.getMinecraftVersion().set(minecraftVersion);
+ task.getNeoForgeVersion().set(neoForgeVersion);
+ task.getRawNeoFormVersion().set(rawNeoFormVersion);
+ task.setLibraries(configurations.launcherProfileClasspath);
+ task.getRepositoryURLs().set(project.provider(() -> {
+ List repos = new ArrayList<>();
+ for (var repo : project.getRepositories().withType(MavenArtifactRepository.class)) {
+ var uri = repo.getUrl();
+ if (!uri.toString().endsWith("/")) {
+ uri = URI.create(uri + "/");
+ }
+ repos.add(uri);
+ }
+ return repos;
+ }));
+ // ${version_name}.jar will be filled out by the launcher. It corresponds to the raw SRG Minecraft client jar.
+ task.getIgnoreList().addAll("client-extra", "${version_name}.jar");
+ task.setModules(configurations.modulePath);
+ task.getLauncherProfile().set(neoDevBuildDir.map(dir -> dir.file("launcher-profile.json")));
+ });
+
+ // Installer profile = the .json file used by the NeoForge installer.
+ var createInstallerProfile = tasks.register("createInstallerProfile", CreateInstallerProfile.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getMinecraftVersion().set(minecraftVersion);
+ task.getNeoForgeVersion().set(neoForgeVersion);
+ task.getMcAndNeoFormVersion().set(mcAndNeoFormVersion);
+ task.getIcon().set(project.getRootProject().file("docs/assets/neoforged.ico"));
+ // Anything that is on the launcher classpath should be downloaded by the installer.
+ // (At least on the server side).
+ task.addLibraries(configurations.launcherProfileClasspath);
+ // We need the NeoForm zip for the SRG mappings.
+ task.addLibraries(configurations.neoFormDataOnly);
+ task.getRepositoryURLs().set(project.provider(() -> {
+ List repos = new ArrayList<>();
+ for (var repo : project.getRepositories().withType(MavenArtifactRepository.class)) {
+ var uri = repo.getUrl();
+ if (!uri.toString().endsWith("/")) {
+ uri = URI.create(uri + "/");
+ }
+ repos.add(uri);
+ }
+ return repos;
+ }));
+ task.getUniversalJar().set(universalJar.flatMap(AbstractArchiveTask::getArchiveFile));
+ task.getInstallerProfile().set(neoDevBuildDir.map(dir -> dir.file("installer-profile.json")));
+
+ // Make all installer processor tools available to the profile
+ for (var installerProcessor : InstallerProcessor.values()) {
+ var configuration = configurations.getExecutableTool(installerProcessor.tool);
+ // Different processors might use different versions of the same library,
+ // but that is fine because each processor gets its own classpath.
+ task.addLibraries(configuration);
+ task.getProcessorClasspaths().put(installerProcessor, DependencyUtils.configurationToGavList(configuration));
+ task.getProcessorGavs().put(installerProcessor, installerProcessor.tool.asGav(project));
+ }
+ });
+
+ var createWindowsServerArgsFile = tasks.register("createWindowsServerArgsFile", CreateArgsFile.class, task -> {
+ task.setLibraries(";", configurations.launcherProfileClasspath, configurations.modulePath);
+ task.getArgsFile().set(neoDevBuildDir.map(dir -> dir.file("windows-server-args.txt")));
+ });
+ var createUnixServerArgsFile = tasks.register("createUnixServerArgsFile", CreateArgsFile.class, task -> {
+ task.setLibraries(":", configurations.launcherProfileClasspath, configurations.modulePath);
+ task.getArgsFile().set(neoDevBuildDir.map(dir -> dir.file("unix-server-args.txt")));
+ });
+
+ for (var taskProvider : List.of(createWindowsServerArgsFile, createUnixServerArgsFile)) {
+ taskProvider.configure(task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getTemplate().set(project.getRootProject().file("server_files/args.txt"));
+ task.getFmlVersion().set(fmlVersion);
+ task.getMinecraftVersion().set(minecraftVersion);
+ task.getNeoForgeVersion().set(neoForgeVersion);
+ task.getRawNeoFormVersion().set(rawNeoFormVersion);
+ // In theory, new BootstrapLauncher shouldn't need the module path in the ignore list anymore.
+ // However, in server installs libraries are passed as relative paths here.
+ // Module path detection doesn't currently work with relative paths (BootstrapLauncher #20).
+ task.getIgnoreList().set(configurations.modulePath.getIncoming().getArtifacts().getResolvedArtifacts().map(results -> {
+ return results.stream().map(r -> r.getFile().getName()).toList();
+ }));
+ task.getRawServerJar().set(createCleanArtifacts.flatMap(CreateCleanArtifacts::getRawServerJar));
+ });
+ }
+
+ var installerConfig = configurations.getExecutableTool(Tools.LEGACYINSTALLER);
+ // TODO: signing?
+ // We want to inherit the executable JAR manifest from LegacyInstaller.
+ // - Jar tasks have special manifest handling, so use Zip.
+ // - The manifest must be the first entry in the jar so LegacyInstaller has to be the first input.
+ var installerJar = tasks.register("installerJar", Zip.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getArchiveClassifier().set("installer");
+ task.getArchiveExtension().set("jar");
+ task.setMetadataCharset("UTF-8");
+ task.getDestinationDirectory().convention(project.getExtensions().getByType(BasePluginExtension.class).getLibsDirectory());
+
+ task.from(project.zipTree(project.provider(installerConfig::getSingleFile)), spec -> {
+ spec.exclude("big_logo.png");
+ });
+ task.from(createLauncherProfile.flatMap(CreateLauncherProfile::getLauncherProfile), spec -> {
+ spec.rename(s -> "version.json");
+ });
+ task.from(createInstallerProfile.flatMap(CreateInstallerProfile::getInstallerProfile), spec -> {
+ spec.rename(s -> "install_profile.json");
+ });
+ task.from(project.getRootProject().file("src/main/resources/url.png"));
+ task.from(project.getRootProject().file("src/main/resources/neoforged_logo.png"), spec -> {
+ spec.rename(s -> "big_logo.png");
+ });
+ task.from(createUnixServerArgsFile.flatMap(CreateArgsFile::getArgsFile), spec -> {
+ spec.into("data");
+ spec.rename(s -> "unix_args.txt");
+ });
+ task.from(createWindowsServerArgsFile.flatMap(CreateArgsFile::getArgsFile), spec -> {
+ spec.into("data");
+ spec.rename(s -> "win_args.txt");
+ });
+ task.from(binaryPatchOutputs.binaryPatchesForClient(), spec -> {
+ spec.into("data");
+ spec.rename(s -> "client.lzma");
+ });
+ task.from(binaryPatchOutputs.binaryPatchesForServer(), spec -> {
+ spec.into("data");
+ spec.rename(s -> "server.lzma");
+ });
+ var mavenPath = neoForgeVersion.map(v -> "net/neoforged/neoforge/" + v);
+ task.getInputs().property("mavenPath", mavenPath);
+ task.from(project.getRootProject().files("server_files"), spec -> {
+ spec.into("data");
+ spec.exclude("args.txt");
+ spec.filter(s -> {
+ return s.replaceAll("@MAVEN_PATH@", mavenPath.get());
+ });
+ });
+
+ // This is true by default (see gradle.properties), and needs to be disabled explicitly when building (see release.yml).
+ String installerDebugProperty = "neogradle.runtime.platform.installer.debug";
+ if (project.getProperties().containsKey(installerDebugProperty) && Boolean.parseBoolean(project.getProperties().get(installerDebugProperty).toString())) {
+ task.from(universalJar.flatMap(AbstractArchiveTask::getArchiveFile), spec -> {
+ spec.into(String.format("/maven/net/neoforged/neoforge/%s/", neoForgeVersion.get()));
+ spec.rename(name -> String.format("neoforge-%s-universal.jar", neoForgeVersion.get()));
+ });
+ }
+ });
+
+ var userdevJar = tasks.register("userdevJar", Jar.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.getArchiveClassifier().set("userdev");
+
+ task.from(writeUserDevConfig.flatMap(CreateUserDevConfig::getUserDevConfig), spec -> {
+ spec.rename(s -> "config.json");
+ });
+ task.from(atFile, spec -> {
+ spec.into("ats/");
+ });
+ task.from(binaryPatchOutputs.binaryPatchesForMerged(), spec -> {
+ spec.rename(s -> "joined.lzma");
+ });
+ task.from(project.zipTree(genSourcePatches.flatMap(GenerateSourcePatches::getPatchesJar)), spec -> {
+ spec.into("patches/");
+ });
+ });
+
+ project.getExtensions().getByType(JavaPluginExtension.class).withSourcesJar();
+ var sourcesJarProvider = project.getTasks().named("sourcesJar", Jar.class);
+ sourcesJarProvider.configure(task -> {
+ task.exclude("net/minecraft/**");
+ task.exclude("com/**");
+ task.exclude("mcp/**");
+ });
+
+ tasks.named("assemble", task -> {
+ task.dependsOn(installerJar);
+ task.dependsOn(universalJar);
+ task.dependsOn(userdevJar);
+ task.dependsOn(sourcesJarProvider);
+ });
+
+ // Set up E2E testing of the produced installer
+ setupProductionClientTest(
+ project,
+ configurations,
+ downloadAssets,
+ installerJar,
+ minecraftVersion,
+ neoForgeVersion,
+ createCleanArtifacts.flatMap(CreateCleanArtifacts::getRawClientJar)
+ );
+ setupProductionServerTest(project, installerJar);
+ }
+
+ private static TaskProvider configureAccessTransformer(
+ Project project,
+ NeoDevConfigurations configurations,
+ TaskProvider createSourceArtifacts,
+ Provider neoDevBuildDir,
+ File atFile) {
+
+ // Pass -PvalidateAccessTransformers to validate ATs.
+ var validateAts = project.getProviders().gradleProperty("validateAccessTransformers").map(p -> true).orElse(false);
+ return project.getTasks().register("applyAccessTransformer", ApplyAccessTransformer.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.classpath(configurations.getExecutableTool(Tools.JST));
+ task.getInputJar().set(createSourceArtifacts.flatMap(CreateMinecraftArtifacts::getSourcesArtifact));
+ task.getAccessTransformer().set(atFile);
+ task.getValidate().set(validateAts);
+ task.getOutputJar().set(neoDevBuildDir.map(dir -> dir.file("artifacts/access-transformed-sources.jar")));
+ task.getLibraries().from(configurations.neoFormClasspath);
+ task.getLibrariesFile().set(neoDevBuildDir.map(dir -> dir.file("minecraft-libraries-for-jst.txt")));
+ });
+ }
+
+ private static BinaryPatchOutputs configureBinaryPatchCreation(Project project,
+ NeoDevConfigurations configurations,
+ TaskProvider createCleanArtifacts,
+ Provider neoDevBuildDir,
+ File sourcesPatchesFolder) {
+ var tasks = project.getTasks();
+
+ var artConfig = configurations.getExecutableTool(Tools.AUTO_RENAMING_TOOL);
+ var remapClientJar = tasks.register("remapClientJar", RemapJar.class, task -> {
+ task.setDescription("Creates a Minecraft client jar with the official mappings applied. Used as the base for generating binary patches for the client.");
+ task.getInputJar().set(createCleanArtifacts.flatMap(CreateCleanArtifacts::getCleanClientJar));
+ task.getOutputJar().set(neoDevBuildDir.map(dir -> dir.file("remapped-client.jar")));
+ });
+ var remapServerJar = tasks.register("remapServerJar", RemapJar.class, task -> {
+ task.setDescription("Creates a Minecraft dedicated server jar with the official mappings applied. Used as the base for generating binary patches for the client.");
+ task.getInputJar().set(createCleanArtifacts.flatMap(CreateCleanArtifacts::getCleanServerJar));
+ task.getOutputJar().set(neoDevBuildDir.map(dir -> dir.file("remapped-server.jar")));
+ });
+ for (var remapTask : List.of(remapClientJar, remapServerJar)) {
+ remapTask.configure(task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.classpath(artConfig);
+ task.getMappings().set(createCleanArtifacts.flatMap(CreateCleanArtifacts::getMergedMappings));
+ });
+ }
+
+ var binpatcherConfig = configurations.getExecutableTool(Tools.BINPATCHER);
+ var generateMergedBinPatches = tasks.register("generateMergedBinPatches", GenerateBinaryPatches.class, task -> {
+ task.setDescription("Creates binary patch files by diffing a merged client/server jar-file and the compiled Minecraft classes in this project.");
+ task.getCleanJar().set(createCleanArtifacts.flatMap(CreateCleanArtifacts::getCleanJoinedJar));
+ task.getOutputFile().set(neoDevBuildDir.map(dir -> dir.file("merged-binpatches.lzma")));
+ });
+ var generateClientBinPatches = tasks.register("generateClientBinPatches", GenerateBinaryPatches.class, task -> {
+ task.setDescription("Creates binary patch files by diffing a merged client jar-file and the compiled Minecraft classes in this project.");
+ task.getCleanJar().set(remapClientJar.flatMap(RemapJar::getOutputJar));
+ task.getOutputFile().set(neoDevBuildDir.map(dir -> dir.file("client-binpatches.lzma")));
+ });
+ var generateServerBinPatches = tasks.register("generateServerBinPatches", GenerateBinaryPatches.class, task -> {
+ task.setDescription("Creates binary patch files by diffing a merged server jar-file and the compiled Minecraft classes in this project.");
+ task.getCleanJar().set(remapServerJar.flatMap(RemapJar::getOutputJar));
+ task.getOutputFile().set(neoDevBuildDir.map(dir -> dir.file("server-binpatches.lzma")));
+ });
+ for (var generateBinPatchesTask : List.of(generateMergedBinPatches, generateClientBinPatches, generateServerBinPatches)) {
+ generateBinPatchesTask.configure(task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.classpath(binpatcherConfig);
+ task.getPatchedJar().set(tasks.named("jar", Jar.class).flatMap(Jar::getArchiveFile));
+ task.getSourcePatchesFolder().set(sourcesPatchesFolder);
+ task.getMappings().set(createCleanArtifacts.flatMap(CreateCleanArtifacts::getMergedMappings));
+ });
+ }
+
+ return new BinaryPatchOutputs(
+ generateMergedBinPatches.flatMap(GenerateBinaryPatches::getOutputFile),
+ generateClientBinPatches.flatMap(GenerateBinaryPatches::getOutputFile),
+ generateServerBinPatches.flatMap(GenerateBinaryPatches::getOutputFile)
+ );
+ }
+
+ private record BinaryPatchOutputs(
+ Provider binaryPatchesForMerged,
+ Provider binaryPatchesForClient,
+ Provider binaryPatchesForServer
+ ) {
+ }
+
+ /**
+ * Sets up NFRT, and creates the sources and resources artifacts.
+ */
+ static TaskProvider configureMinecraftDecompilation(Project project) {
+ project.getPlugins().apply(NeoFormRuntimePlugin.class);
+
+ var configurations = project.getConfigurations();
+ var dependencyFactory = project.getDependencyFactory();
+ var tasks = project.getTasks();
+ var neoDevBuildDir = project.getLayout().getBuildDirectory().dir("neodev");
+
+ var rawNeoFormVersion = project.getProviders().gradleProperty("neoform_version");
+ var minecraftVersion = project.getProviders().gradleProperty("minecraft_version");
+ var mcAndNeoFormVersion = minecraftVersion.zip(rawNeoFormVersion, (mc, nf) -> mc + "-" + nf);
+
+ // Configuration for all artifacts that should be passed to NFRT to prevent repeated downloads
+ var neoFormRuntimeArtifactManifestNeoForm = configurations.create("neoFormRuntimeArtifactManifestNeoForm", spec -> {
+ spec.setCanBeConsumed(false);
+ spec.setCanBeResolved(true);
+ spec.getDependencies().addLater(mcAndNeoFormVersion.map(version -> {
+ return dependencyFactory.create("net.neoforged:neoform:" + version);
+ }));
+ });
+
+ tasks.withType(NeoFormRuntimeTask.class, task -> {
+ task.addArtifactsToManifest(neoFormRuntimeArtifactManifestNeoForm);
+ });
+
+ return tasks.register("createSourceArtifacts", CreateMinecraftArtifacts.class, task -> {
+ var minecraftArtifactsDir = neoDevBuildDir.map(dir -> dir.dir("artifacts"));
+ task.getSourcesArtifact().set(minecraftArtifactsDir.map(dir -> dir.file("base-sources.jar")));
+ task.getResourcesArtifact().set(minecraftArtifactsDir.map(dir -> dir.file("minecraft-resources.jar")));
+ task.getNeoFormArtifact().set(mcAndNeoFormVersion.map(version -> "net.neoforged:neoform:" + version + "@zip"));
+ });
+ }
+
+ private void setupProductionClientTest(Project project,
+ NeoDevConfigurations configurations,
+ TaskProvider extends DownloadAssets> downloadAssets,
+ TaskProvider extends AbstractArchiveTask> installer,
+ Provider minecraftVersion,
+ Provider neoForgeVersion,
+ Provider originalClientJar
+ ) {
+
+ var installClient = project.getTasks().register("installProductionClient", InstallProductionClient.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.setDescription("Runs the installer produced by this build and installs a production client.");
+ task.getInstaller().from(installer.flatMap(AbstractArchiveTask::getArchiveFile));
+
+ var destinationDir = project.getLayout().getBuildDirectory().dir("production-client");
+ task.getInstallationDir().set(destinationDir);
+ });
+
+ Consumer configureRunProductionClient = task -> {
+ task.getLibraryFiles().addAll(IdentifiedFile.listFromConfiguration(project, configurations.neoFormClasspath));
+ task.getLibraryFiles().addAll(IdentifiedFile.listFromConfiguration(project, configurations.launcherProfileClasspath));
+ task.getAssetPropertiesFile().set(downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile));
+ task.getMinecraftVersion().set(minecraftVersion);
+ task.getNeoForgeVersion().set(neoForgeVersion);
+ task.getInstallationDir().set(installClient.flatMap(InstallProductionClient::getInstallationDir));
+ task.getOriginalClientJar().set(originalClientJar);
+ };
+ project.getTasks().register("runProductionClient", RunProductionClient.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.setDescription("Runs the production client installed by installProductionClient.");
+ configureRunProductionClient.accept(task);
+ });
+ project.getTasks().register("testProductionClient", TestProductionClient.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.setDescription("Tests the production client installed by installProductionClient.");
+ configureRunProductionClient.accept(task);
+ });
+ }
+
+ private void setupProductionServerTest(Project project, TaskProvider extends AbstractArchiveTask> installer) {
+ var installServer = project.getTasks().register("installProductionServer", InstallProductionServer.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.setDescription("Runs the installer produced by this build and installs a production server.");
+ task.getInstaller().from(installer.flatMap(AbstractArchiveTask::getArchiveFile));
+
+ var destinationDir = project.getLayout().getBuildDirectory().dir("production-server");
+ task.getInstallationDir().set(destinationDir);
+ });
+
+ project.getTasks().register("runProductionServer", RunProductionServer.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.setDescription("Runs the production server installed by installProductionServer.");
+ task.getInstallationDir().set(installServer.flatMap(InstallProductionServer::getInstallationDir));
+ });
+
+ project.getTasks().register("testProductionServer", TestProductionServer.class, task -> {
+ task.setGroup(INTERNAL_GROUP);
+ task.setDescription("Tests the production server installed by installProductionServer.");
+ task.getInstallationDir().set(installServer.flatMap(InstallProductionServer::getInstallationDir));
+ });
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/NeoDevTestExtension.java b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevTestExtension.java
new file mode 100644
index 0000000000..57d3f7be33
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/NeoDevTestExtension.java
@@ -0,0 +1,30 @@
+package net.neoforged.neodev;
+
+import net.neoforged.moddevgradle.dsl.ModModel;
+import org.gradle.api.provider.Property;
+import org.gradle.api.provider.SetProperty;
+
+import javax.inject.Inject;
+
+public abstract class NeoDevTestExtension {
+ public static final String NAME = "neoDevTest";
+
+ @Inject
+ public NeoDevTestExtension() {
+ }
+
+ /**
+ * The mod that will be loaded in JUnit tests.
+ * The compiled classes from {@code src/test/java} and the resources from {@code src/test/resources}
+ * will be added to that mod at runtime.
+ */
+ public abstract Property getTestedMod();
+
+ /**
+ * The mods to load when running unit tests. Defaults to all mods registered in the project.
+ * This must contain {@link #getTestedMod()}.
+ *
+ * @see ModModel
+ */
+ public abstract SetProperty getLoadedMods();
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/RemapJar.java b/buildSrc/src/main/java/net/neoforged/neodev/RemapJar.java
new file mode 100644
index 0000000000..e93ccb8b94
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/RemapJar.java
@@ -0,0 +1,53 @@
+package net.neoforged.neodev;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.OutputFile;
+
+import javax.inject.Inject;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Produces a remapped jar-file that has almost no other changes applied with the intent of being
+ * the base against which we {@link GenerateBinaryPatches generate binary patches}.
+ *
+ * The installer produces the same Jar file as this task does and then applies the patches against that.
+ *
+ * Any changes to the options used here have to be reflected in the {@link net.neoforged.neodev.installer.CreateInstallerProfile installer profile}
+ * and vice versa, to ensure the patches are generated against the same binary files as they are applied to later.
+ */
+abstract class RemapJar extends JavaExec {
+ @Inject
+ public RemapJar() {}
+
+ @InputFile
+ abstract RegularFileProperty getInputJar();
+
+ @InputFile
+ abstract RegularFileProperty getMappings();
+
+ @OutputFile
+ abstract RegularFileProperty getOutputJar();
+
+ @Override
+ public void exec() {
+ args("--input", getInputJar().get().getAsFile().getAbsolutePath());
+ args("--output", getOutputJar().get().getAsFile().getAbsolutePath());
+ args("--names", getMappings().get().getAsFile().getAbsolutePath());
+ args("--ann-fix", "--ids-fix", "--src-fix", "--record-fix");
+
+ var logFile = new File(getTemporaryDir(), "console.log");
+ try (var out = new BufferedOutputStream(new FileOutputStream(logFile))) {
+ getLogger().info("Logging ART console output to {}", logFile.getAbsolutePath());
+ setStandardOutput(out);
+ super.exec();
+ } catch (IOException e) {
+ throw new GradleException("Failed to remap jar.", e);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/Tools.java b/buildSrc/src/main/java/net/neoforged/neodev/Tools.java
new file mode 100644
index 0000000000..a9f9106697
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/Tools.java
@@ -0,0 +1,53 @@
+package net.neoforged.neodev;
+
+import org.gradle.api.Project;
+
+// If a GAV is changed, make sure to change the corresponding renovate comment in gradle.properties.
+public enum Tools {
+ // Fatjar jst-cli-bundle instead of jst-cli because publication of the latter is currently broken.
+ JST("net.neoforged.jst:jst-cli-bundle:%s", "jst_version", "toolJstClasspath", true),
+ // Fatjar because the contents are copy/pasted into the installer jar which must be standalone.
+ LEGACYINSTALLER("net.neoforged:legacyinstaller:%s:shrunk", "legacyinstaller_version", "toolLegacyinstallerClasspath", true),
+ // Fatjar because the slim jar currently does not have the main class set in its manifest.
+ AUTO_RENAMING_TOOL("net.neoforged:AutoRenamingTool:%s:all", "art_version", "toolAutoRenamingToolClasspath", true),
+ INSTALLERTOOLS("net.neoforged.installertools:installertools:%s", "installertools_version", "toolInstallertoolsClasspath", false),
+ JARSPLITTER("net.neoforged.installertools:jarsplitter:%s", "installertools_version", "toolJarsplitterClasspath", false),
+ // Fatjar because it was like that in the userdev json in the past.
+ // To reconsider, we need to get in touch with 3rd party plugin developers or wait for a BC window.
+ BINPATCHER("net.neoforged.installertools:binarypatcher:%s:fatjar", "installertools_version", "toolBinpatcherClasspath", true);
+
+ private final String gavPattern;
+ private final String versionProperty;
+ private final String gradleConfigurationName;
+ private final boolean requestFatJar;
+
+ Tools(String gavPattern, String versionProperty, String gradleConfigurationName, boolean requestFatJar) {
+ this.gavPattern = gavPattern;
+ this.versionProperty = versionProperty;
+ this.gradleConfigurationName = gradleConfigurationName;
+ this.requestFatJar = requestFatJar;
+ }
+
+ /**
+ * The name of the Gradle {@link org.gradle.api.artifacts.Configuration} used to resolve this particular tool.
+ */
+ public String getGradleConfigurationName() {
+ return gradleConfigurationName;
+ }
+
+ /**
+ * Some tools may be incorrectly packaged and declare transitive dependencies even for their "fatjar" variants.
+ * Gradle will not run these, so we ignore them.
+ */
+ public boolean isRequestFatJar() {
+ return requestFatJar;
+ }
+
+ public String asGav(Project project) {
+ var version = project.property(versionProperty);
+ if (version == null) {
+ throw new IllegalStateException("Could not find property " + versionProperty);
+ }
+ return gavPattern.formatted(version);
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/e2e/InstallProductionClient.java b/buildSrc/src/main/java/net/neoforged/neodev/e2e/InstallProductionClient.java
new file mode 100644
index 0000000000..bc16b4b386
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/e2e/InstallProductionClient.java
@@ -0,0 +1,64 @@
+package net.neoforged.neodev.e2e;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+
+/**
+ * Downloads and installs a production NeoForge client.
+ * By extending this task from {@link JavaExec}, it's possible to debug the actual legacy installer
+ * via IntelliJ directly.
+ */
+public abstract class InstallProductionClient extends JavaExec {
+ /**
+ * This file collection should contain exactly one file:
+ * The NeoForge Installer Jar-File.
+ */
+ @InputFiles
+ public abstract ConfigurableFileCollection getInstaller();
+
+ /**
+ * Where NeoForge should be installed.
+ */
+ @OutputDirectory
+ public abstract DirectoryProperty getInstallationDir();
+
+ @Inject
+ public InstallProductionClient() {
+ classpath(getInstaller());
+ }
+
+ @TaskAction
+ @Override
+ public void exec() {
+ var installDir = getInstallationDir().getAsFile().get().toPath().toAbsolutePath();
+
+ // Installer looks for this file
+ var profilesJsonPath = installDir.resolve("launcher_profiles.json");
+ try {
+ Files.writeString(profilesJsonPath, "{}");
+ } catch (IOException e) {
+ throw new GradleException("Failed to write fake launcher profiles file.", e);
+ }
+
+ setWorkingDir(installDir.toFile());
+ args("--install-client", installDir.toString());
+ try {
+ setStandardOutput(new BufferedOutputStream(Files.newOutputStream(installDir.resolve("install.log"))));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ super.exec();
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/e2e/InstallProductionServer.java b/buildSrc/src/main/java/net/neoforged/neodev/e2e/InstallProductionServer.java
new file mode 100644
index 0000000000..9360a88e88
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/e2e/InstallProductionServer.java
@@ -0,0 +1,63 @@
+package net.neoforged.neodev.e2e;
+
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+
+/**
+ * Runs the installer produced by the main build to install a dedicated server in a chosen directory.
+ */
+public abstract class InstallProductionServer extends JavaExec {
+ /**
+ * The NeoForge installer jar is expected to be the only file in this file collection.
+ */
+ @InputFiles
+ public abstract ConfigurableFileCollection getInstaller();
+
+ /**
+ * Where the server should be installed.
+ */
+ @OutputDirectory
+ public abstract DirectoryProperty getInstallationDir();
+
+ /**
+ * Points to the server.jar produced by the installer.
+ */
+ @OutputFile
+ public abstract RegularFileProperty getServerLauncher();
+
+ @Inject
+ public InstallProductionServer() {
+ classpath(getInstaller());
+ getServerLauncher().set(getInstallationDir().map(id -> id.file("server.jar")));
+ getServerLauncher().finalizeValueOnRead();
+ }
+
+ @TaskAction
+ @Override
+ public void exec() {
+ var installDir = getInstallationDir().getAsFile().get().toPath().toAbsolutePath();
+
+ setWorkingDir(installDir.toFile());
+ args("--install-server", installDir.toString());
+ args("--server.jar");
+ try {
+ setStandardOutput(new BufferedOutputStream(Files.newOutputStream(installDir.resolve("install.log"))));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ super.exec();
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/e2e/RunProductionClient.java b/buildSrc/src/main/java/net/neoforged/neodev/e2e/RunProductionClient.java
new file mode 100644
index 0000000000..e710999e70
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/e2e/RunProductionClient.java
@@ -0,0 +1,363 @@
+package net.neoforged.neodev.e2e;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import net.neoforged.neodev.installer.IdentifiedFile;
+import net.neoforged.neodev.utils.MavenIdentifier;
+import org.apache.tools.ant.taskdefs.condition.Os;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.ListProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.process.ExecOperations;
+import org.gradle.process.JavaExecSpec;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Runs a production client previously installed by {@link InstallProductionClient}.
+ *
+ * This task has to extend from {@link JavaExec} instead of using {@link org.gradle.process.ExecOperations} internally
+ * to allow debugging it via IntelliJ directly.
+ * (Technically, implementing {@link org.gradle.process.JavaForkOptions} would suffice).
+ *
+ * The main complication of this task is evaluating the Vanilla version manifest and building a libraries
+ * directory and classpath as the Vanilla launcher would.
+ */
+public abstract class RunProductionClient extends JavaExec {
+ private final ExecOperations execOperations;
+
+ /**
+ * The folder where the game was installed.
+ */
+ @InputDirectory
+ public abstract DirectoryProperty getInstallationDir();
+
+ /**
+ * The pre-processed libraries as a file collection.
+ */
+ @Nested
+ public abstract ListProperty getLibraryFiles();
+
+ /**
+ * The asset properties file produced by {@link net.neoforged.nfrtgradle.DownloadAssets}.
+ */
+ @InputFile
+ public abstract RegularFileProperty getAssetPropertiesFile();
+
+ /**
+ * The Minecraft version matching the NeoForge version to install.
+ */
+ @Input
+ public abstract Property getMinecraftVersion();
+
+ /**
+ * The NeoForge version, used for placeholders when launching the game.
+ * It needs to match the installer used.
+ */
+ @Input
+ public abstract Property getNeoForgeVersion();
+
+ /**
+ * The original, unmodified client jar.
+ * The Vanilla launcher puts this on the classpath when it launches the game.
+ */
+ @InputFile
+ public abstract RegularFileProperty getOriginalClientJar();
+
+ @Inject
+ public RunProductionClient(ExecOperations execOperations) {
+ this.execOperations = execOperations;
+ }
+
+ @TaskAction
+ @Override
+ public void exec() {
+ var installDir = getInstallationDir().getAsFile().get().toPath();
+ var nativesDir = installDir.resolve("natives");
+ try {
+ Files.createDirectories(nativesDir);
+ } catch (IOException e) {
+ throw new GradleException("Failed to pre-create natives directory " + nativesDir, e);
+ }
+ var librariesDir = installDir.resolve("libraries");
+
+ var minecraftVersion = getMinecraftVersion().get();
+ var versionId = "neoforge-" + getNeoForgeVersion().get();
+
+ var assetProperties = new Properties();
+ try (var in = new FileInputStream(getAssetPropertiesFile().getAsFile().get())) {
+ assetProperties.load(in);
+ } catch (IOException e) {
+ throw new GradleException("Failed to read asset properties " + getAssetPropertiesFile(), e);
+ }
+
+ var assetIndex = Objects.requireNonNull(assetProperties.getProperty("asset_index"), "asset_index");
+ var assetsRoot = Objects.requireNonNull(assetProperties.getProperty("assets_root"), "assets_root");
+
+ // Set up the placeholders generally used by Vanilla profiles in their argument definitions.
+ var placeholders = new HashMap();
+ placeholders.put("auth_player_name", "Dev");
+ placeholders.put("version_name", minecraftVersion);
+ placeholders.put("game_directory", installDir.toAbsolutePath().toString());
+ placeholders.put("auth_uuid", "00000000-0000-4000-8000-000000000000");
+ placeholders.put("auth_access_token", "0");
+ placeholders.put("clientid", "0");
+ placeholders.put("auth_xuid", "0");
+ placeholders.put("user_type", "legacy");
+ placeholders.put("version_type", "release");
+ placeholders.put("assets_index_name", assetIndex);
+ placeholders.put("assets_root", assetsRoot);
+ placeholders.put("launcher_name", "NeoForgeProdInstallation");
+ placeholders.put("launcher_version", "1.0");
+ placeholders.put("natives_directory", nativesDir.toAbsolutePath().toString());
+ // These are used by NF but provided by the launcher
+ placeholders.put("library_directory", librariesDir.toAbsolutePath().toString());
+ placeholders.put("classpath_separator", File.pathSeparator);
+
+ execOperations.javaexec(spec -> {
+ // The JVM args at this point may include debugging options when started through IntelliJ
+ spec.jvmArgs(getJvmArguments().get());
+ spec.workingDir(installDir);
+
+ spec.environment(getEnvironment());
+ applyVersionManifest(installDir, versionId, placeholders, librariesDir, spec);
+ });
+ }
+
+ /**
+ * Applies a Vanilla Launcher version manifest to the JavaForkOptions.
+ */
+ private void applyVersionManifest(Path installDir,
+ String versionId,
+ Map placeholders,
+ Path librariesDir,
+ JavaExecSpec spec) {
+ var manifests = loadVersionManifests(installDir, versionId);
+
+ var mergedProgramArgs = new ArrayList();
+ var mergedJvmArgs = new ArrayList();
+
+ for (var manifest : manifests) {
+ var mainClass = manifest.getAsJsonPrimitive("mainClass");
+ if (mainClass != null) {
+ spec.getMainClass().set(mainClass.getAsString());
+ }
+
+ mergedProgramArgs.addAll(getArguments(manifest, "game"));
+ mergedJvmArgs.addAll(getArguments(manifest, "jvm"));
+ }
+
+ // Index all available libraries
+ var availableLibraries = new HashMap();
+ for (var identifiedFile : getLibraryFiles().get()) {
+ availableLibraries.put(
+ identifiedFile.getIdentifier().get(),
+ identifiedFile.getFile().get().getAsFile().toPath()
+ );
+ }
+
+ // The libraries are built in reverse, and libraries already added are not added again from parent manifests
+ var librariesAdded = new HashSet();
+ var classpathItems = new ArrayList();
+ for (var i = manifests.size() - 1; i >= 0; i--) {
+ var manifest = manifests.get(i);
+
+ var libraries = manifest.getAsJsonArray("libraries");
+ for (var library : libraries) {
+ var libraryObj = library.getAsJsonObject();
+
+ // Skip if disabled by rule
+ if (isDisabledByRules(libraryObj)) {
+ getLogger().info("Skipping library {} since it's condition is not met.", libraryObj);
+ continue;
+ }
+
+ var id = MavenIdentifier.parse(libraryObj.get("name").getAsString());
+
+ // We use this to deduplicate the same library in different versions across manifests
+ var idWithoutVersion = new MavenIdentifier(
+ id.group(),
+ id.artifact(),
+ "",
+ id.classifier(),
+ id.extension()
+ );
+
+ if (!librariesAdded.add(idWithoutVersion)) {
+ continue; // The library was overridden by a child profile
+ }
+
+ // Try finding the library in the classpath we got from Gradle
+ var availableLibrary = availableLibraries.get(id);
+ if (availableLibrary == null) {
+ throw new GradleException("Version manifest asks for " + id + " but this library is not available through Gradle.");
+ }
+
+ // Copy over the library to the libraries directory, since our loader only deduplicates class-path
+ // items with module-path items when they are at the same location (and the module-path is defined
+ // relative to the libraries directory).
+ Path destination = librariesDir.resolve(id.repositoryPath());
+ copyIfNeeded(availableLibrary, destination);
+ classpathItems.add(destination.toAbsolutePath().toString());
+ }
+ }
+
+ // The Vanilla launcher adds the actual game jar (obfuscated) as the last classpath item
+ var gameJar = installDir.resolve("versions").resolve(versionId).resolve(versionId + ".jar");
+ copyIfNeeded(getOriginalClientJar().get().getAsFile().toPath(), gameJar);
+ classpathItems.add(gameJar.toAbsolutePath().toString());
+ placeholders.put("version_name", versionId);
+
+ var classpath = String.join(File.pathSeparator, classpathItems);
+ placeholders.putIfAbsent("classpath", classpath);
+
+ expandPlaceholders(mergedProgramArgs, placeholders);
+ spec.args(mergedProgramArgs);
+ expandPlaceholders(mergedJvmArgs, placeholders);
+ spec.jvmArgs(mergedJvmArgs);
+ }
+
+ // Returns the inherited manifests first
+ private static List loadVersionManifests(Path installDir, String versionId) {
+ // Read back the version manifest and get the startup arguments
+ var manifestPath = installDir.resolve("versions").resolve(versionId).resolve(versionId + ".json");
+ JsonObject manifest;
+ try {
+ manifest = readJson(manifestPath);
+ } catch (IOException e) {
+ throw new GradleException("Failed to read launcher profile " + manifestPath, e);
+ }
+
+ var result = new ArrayList();
+ var inheritsFrom = manifest.getAsJsonPrimitive("inheritsFrom");
+ if (inheritsFrom != null) {
+ result.addAll(loadVersionManifests(installDir, inheritsFrom.getAsString()));
+ }
+
+ result.add(manifest);
+
+ return result;
+ }
+
+ private static void expandPlaceholders(List args, Map variables) {
+ var pattern = Pattern.compile("\\$\\{([^}]+)}");
+
+ args.replaceAll(s -> {
+ var matcher = pattern.matcher(s);
+ return matcher.replaceAll(match -> {
+ var variable = match.group(1);
+ return Matcher.quoteReplacement(variables.getOrDefault(variable, matcher.group()));
+ });
+ });
+ }
+
+ private static List getArguments(JsonObject manifest, String kind) {
+ var result = new ArrayList();
+
+ var gameArgs = manifest.getAsJsonObject("arguments").getAsJsonArray(kind);
+ for (var gameArg : gameArgs) {
+ if (gameArg.isJsonObject()) {
+ var conditionalArgument = gameArg.getAsJsonObject();
+ if (!isDisabledByRules(conditionalArgument)) {
+ var value = conditionalArgument.get("value");
+ if (value.isJsonPrimitive()) {
+ result.add(value.getAsString());
+ } else {
+ for (var valueEl : value.getAsJsonArray()) {
+ result.add(valueEl.getAsString());
+ }
+ }
+ }
+ } else {
+ result.add(gameArg.getAsString());
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean isDisabledByRules(JsonObject ruleObject) {
+ var rules = ruleObject.getAsJsonArray("rules");
+ if (rules == null) {
+ return false;
+ }
+
+ for (var ruleEl : rules) {
+ var rule = ruleEl.getAsJsonObject();
+ boolean allow = "allow".equals(rule.getAsJsonPrimitive("action").getAsString());
+ // We only care about "os" rules
+ if (rule.has("os")) {
+ var os = rule.getAsJsonObject("os");
+ var name = os.getAsJsonPrimitive("name");
+ var arch = os.getAsJsonPrimitive("arch");
+ boolean ruleMatches = (name == null || isCurrentOsName(name.getAsString())) && (arch == null || isCurrentOsArch(arch.getAsString()));
+ if (ruleMatches != allow) {
+ return true;
+ }
+ } else {
+ // We assume unknown rules do not apply
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isCurrentOsName(String os) {
+ return switch (os) {
+ case "windows" -> Os.isFamily(Os.FAMILY_WINDOWS);
+ case "osx" -> Os.isFamily(Os.FAMILY_MAC);
+ case "linux" -> Os.isFamily(Os.FAMILY_UNIX);
+ default -> false;
+ };
+ }
+
+ private static boolean isCurrentOsArch(String arch) {
+ return switch (arch) {
+ case "x86" -> System.getProperty("os.arch").equals("x86");
+ default -> false;
+ };
+ }
+
+ private static JsonObject readJson(Path path) throws IOException {
+ try (var reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+ return new Gson().fromJson(reader, JsonObject.class);
+ }
+ }
+
+ private static void copyIfNeeded(Path source, Path destination) {
+ try {
+ if (!Files.exists(destination)
+ || !Objects.equals(Files.getLastModifiedTime(destination), Files.getLastModifiedTime(source))
+ || Files.size(destination) != Files.size(source)) {
+ Files.createDirectories(destination.getParent());
+ Files.copy(source, destination, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
+ }
+ } catch (IOException e) {
+ throw new GradleException("Failed to copy " + source + " to " + destination + ": " + e, e);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/e2e/RunProductionServer.java b/buildSrc/src/main/java/net/neoforged/neodev/e2e/RunProductionServer.java
new file mode 100644
index 0000000000..e65ed58ecb
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/e2e/RunProductionServer.java
@@ -0,0 +1,47 @@
+package net.neoforged.neodev.e2e;
+
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.process.ExecOperations;
+
+import javax.inject.Inject;
+
+/**
+ * Runs the {@code server.jar} installed by our installer using {@link InstallProductionServer}.
+ *
+ * This task has to extend from {@link JavaExec} instead of using {@link ExecOperations} internally
+ * to allow debugging the launched server with IntelliJ.
+ * (Technically, implementing {@link org.gradle.process.JavaForkOptions} would suffice).
+ */
+public abstract class RunProductionServer extends JavaExec {
+ private final ExecOperations execOperations;
+
+ /**
+ * The folder where the game was installed.
+ */
+ @InputDirectory
+ public abstract DirectoryProperty getInstallationDir();
+
+ @Inject
+ public RunProductionServer(ExecOperations execOperations) {
+ this.execOperations = execOperations;
+ }
+
+ @TaskAction
+ @Override
+ public void exec() {
+ var installDir = getInstallationDir().getAsFile().get().toPath();
+
+ execOperations.javaexec(spec -> {
+ // The JVM args at this point may include debugging options when started through IntelliJ
+ spec.jvmArgs(getJvmArguments().get());
+ spec.workingDir(installDir);
+
+ spec.environment(getEnvironment());
+ spec.classpath(installDir.resolve("server.jar"));
+ });
+
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/e2e/TestProductionClient.java b/buildSrc/src/main/java/net/neoforged/neodev/e2e/TestProductionClient.java
new file mode 100644
index 0000000000..0c7ec7fde3
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/e2e/TestProductionClient.java
@@ -0,0 +1,38 @@
+package net.neoforged.neodev.e2e;
+
+import org.gradle.api.GradleException;
+import org.gradle.process.ExecOperations;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Runs a production client using {@link RunProductionClient} and passes the environment variable
+ * to enable the {@link net.neoforged.neoforge.common.util.SelfTest self test}.
+ *
+ * Once the client exits, it validates that the self-test file was created, indicating the client successfully
+ * launched and started ticking.
+ */
+public abstract class TestProductionClient extends RunProductionClient {
+ @Inject
+ public TestProductionClient(ExecOperations execOperations) {
+ super(execOperations);
+
+ getTimeout().set(Duration.of(5, ChronoUnit.MINUTES));
+ }
+
+ @Override
+ public void exec() {
+ var selfTestReport = new File(getTemporaryDir(), "client_self_test.txt");
+
+ environment("NEOFORGE_CLIENT_SELFTEST", selfTestReport.getAbsolutePath());
+
+ super.exec();
+
+ if (!selfTestReport.exists()) {
+ throw new GradleException("Missing self test report file after running client: " + selfTestReport);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/e2e/TestProductionServer.java b/buildSrc/src/main/java/net/neoforged/neodev/e2e/TestProductionServer.java
new file mode 100644
index 0000000000..9fc6ed293c
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/e2e/TestProductionServer.java
@@ -0,0 +1,55 @@
+package net.neoforged.neodev.e2e;
+
+import org.gradle.api.GradleException;
+import org.gradle.process.ExecOperations;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Runs a production server using {@link RunProductionServer} and passes the environment variable
+ * to enable the {@link net.neoforged.neoforge.common.util.SelfTest self test}.
+ *
+ * Once the server exits, it validates that the self-test file was created, indicating the server successfully
+ * launched and started ticking.
+ */
+public abstract class TestProductionServer extends RunProductionServer {
+ @Inject
+ public TestProductionServer(ExecOperations execOperations) {
+ super(execOperations);
+
+ getTimeout().set(Duration.of(5, ChronoUnit.MINUTES));
+ }
+
+ @Override
+ public void exec() {
+ var selfTestReport = new File(getTemporaryDir(), "server_self_test.txt");
+
+ environment("NEOFORGE_DEDICATED_SERVER_SELFTEST", selfTestReport.getAbsolutePath());
+
+ var eulaFile = getInstallationDir().file("eula.txt").get().getAsFile().toPath();
+ try {
+ Files.writeString(eulaFile, "eula=true", StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new GradleException("Failed writing eula acceptable to eula.txt", e);
+ }
+
+ try {
+ super.exec();
+ } finally {
+ try {
+ Files.deleteIfExists(eulaFile);
+ } catch (IOException ignored) {
+ }
+ }
+
+ if (!selfTestReport.exists()) {
+ throw new GradleException("Missing self test report file after running server: " + selfTestReport);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateArgsFile.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateArgsFile.java
new file mode 100644
index 0000000000..cbae005d4a
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateArgsFile.java
@@ -0,0 +1,126 @@
+package net.neoforged.neodev.installer;
+
+import net.neoforged.neodev.utils.DependencyUtils;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.ArchiveOperations;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.ListProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Creates the JVM/program argument files used by the dedicated server launcher.
+ */
+public abstract class CreateArgsFile extends DefaultTask {
+ @Inject
+ public CreateArgsFile() {}
+
+ @InputFile
+ public abstract RegularFileProperty getTemplate();
+
+ @Input
+ public abstract Property getFmlVersion();
+
+ @Input
+ public abstract Property getMinecraftVersion();
+
+ @Input
+ public abstract Property getNeoForgeVersion();
+
+ @Input
+ public abstract Property getRawNeoFormVersion();
+
+ @Input
+ protected abstract Property getPathSeparator();
+
+ @Input
+ protected abstract Property getModules();
+
+ @Input
+ public abstract ListProperty getIgnoreList();
+
+ @Input
+ protected abstract Property getClasspath();
+
+ public void setLibraries(String separator, Configuration classpath, Configuration modulePath) {
+ getPathSeparator().set(separator);
+ getClasspath().set(DependencyUtils.configurationToClasspath(classpath, "libraries/", separator));
+ getModules().set(DependencyUtils.configurationToClasspath(modulePath, "libraries/", separator));
+ }
+
+ @InputFile
+ public abstract RegularFileProperty getRawServerJar();
+
+ @OutputFile
+ public abstract RegularFileProperty getArgsFile();
+
+ @Inject
+ protected abstract ArchiveOperations getArchiveOperations();
+
+ private String resolveClasspath() throws IOException {
+ var ourClasspath = getClasspath().get() + getPathSeparator().get()
+ + "libraries/net/minecraft/server/%s/server-%s-extra.jar".formatted(
+ getRawNeoFormVersion().get(), getRawNeoFormVersion().get());
+
+ // The raw server jar also contains its own classpath.
+ // We want to make sure that our versions of the libraries are used when there is a conflict.
+ var ourClasspathEntries = Stream.of(ourClasspath.split(getPathSeparator().get()))
+ .map(CreateArgsFile::stripVersionSuffix)
+ .collect(Collectors.toSet());
+
+ var serverClasspath = getArchiveOperations().zipTree(getRawServerJar())
+ .filter(spec -> spec.getPath().endsWith("META-INF" + File.separator + "classpath-joined"))
+ .getSingleFile();
+
+ var filteredServerClasspath = Stream.of(Files.readString(serverClasspath.toPath()).split(";"))
+ .filter(path -> !ourClasspathEntries.contains(stripVersionSuffix(path)))
+ // Exclude the actual MC server jar, which is under versions/
+ .filter(path -> path.startsWith("libraries/"))
+ .collect(Collectors.joining(getPathSeparator().get()));
+
+ return ourClasspath + getPathSeparator().get() + filteredServerClasspath;
+ }
+
+ // Example:
+ // Convert "libraries/com/github/oshi/oshi-core/6.4.10/oshi-core-6.4.10.jar"
+ // to "libraries/com/github/oshi/oshi-core".
+ private static String stripVersionSuffix(String classpathEntry) {
+ var parts = classpathEntry.split("/");
+ return String.join("/", List.of(parts).subList(0, parts.length - 2));
+ }
+
+ @TaskAction
+ public void createArgsFile() throws IOException {
+ var replacements = new HashMap();
+ replacements.put("@MODULE_PATH@", getModules().get());
+ replacements.put("@MODULES@", "ALL-MODULE-PATH");
+ replacements.put("@IGNORE_LIST@", String.join(",", getIgnoreList().get()));
+ replacements.put("@PLUGIN_LAYER_LIBRARIES@", "");
+ replacements.put("@GAME_LAYER_LIBRARIES@", "");
+ replacements.put("@CLASS_PATH@", resolveClasspath());
+ replacements.put("@TASK@", "neoforgeserver");
+ replacements.put("@FORGE_VERSION@", getNeoForgeVersion().get());
+ replacements.put("@FML_VERSION@", getFmlVersion().get());
+ replacements.put("@MC_VERSION@", getMinecraftVersion().get());
+ replacements.put("@MCP_VERSION@", getRawNeoFormVersion().get());
+
+ var contents = Files.readString(getTemplate().get().getAsFile().toPath());
+ for (var entry : replacements.entrySet()) {
+ contents = contents.replaceAll(entry.getKey(), entry.getValue());
+ }
+ Files.writeString(getArgsFile().get().getAsFile().toPath(), contents);
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateInstallerProfile.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateInstallerProfile.java
new file mode 100644
index 0000000000..c9da8c83ec
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateInstallerProfile.java
@@ -0,0 +1,230 @@
+package net.neoforged.neodev.installer;
+
+import com.google.gson.GsonBuilder;
+import net.neoforged.neodev.utils.FileUtils;
+import net.neoforged.neodev.utils.MavenIdentifier;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.ListProperty;
+import org.gradle.api.provider.MapProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.jetbrains.annotations.Nullable;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+/**
+ * Creates the JSON profile used by legacyinstaller for installing the client into the vanilla launcher,
+ * or installing a dedicated server.
+ */
+public abstract class CreateInstallerProfile extends DefaultTask {
+ @Inject
+ public CreateInstallerProfile() {}
+
+ @Input
+ public abstract Property getMinecraftVersion();
+
+ @Input
+ public abstract Property getNeoForgeVersion();
+
+ @Input
+ public abstract Property getMcAndNeoFormVersion();
+
+ @InputFile
+ public abstract RegularFileProperty getIcon();
+
+ @Nested
+ protected abstract ListProperty getLibraryFiles();
+
+ public void addLibraries(Configuration libraries) {
+ getLibraryFiles().addAll(IdentifiedFile.listFromConfiguration(getProject(), libraries));
+ }
+
+ @Input
+ public abstract ListProperty getRepositoryURLs();
+
+ @Input
+ public abstract MapProperty> getProcessorClasspaths();
+
+ @Input
+ public abstract MapProperty getProcessorGavs();
+
+ @InputFile
+ public abstract RegularFileProperty getUniversalJar();
+
+ @OutputFile
+ public abstract RegularFileProperty getInstallerProfile();
+
+ private void addProcessor(List processors, @Nullable List sides, InstallerProcessor processor, List args) {
+ var classpath = getProcessorClasspaths().get().get(processor);
+ var mainJar = getProcessorGavs().get().get(processor);
+ if (!classpath.contains(mainJar)) {
+ throw new IllegalStateException("Processor %s is not included in its own classpath %s".formatted(mainJar, classpath));
+ }
+ processors.add(new ProcessorEntry(sides, mainJar, classpath, args));
+ }
+
+ @TaskAction
+ public void createInstallerProfile() throws IOException {
+ var icon = "data:image/png;base64," + Base64.getEncoder().encodeToString(Files.readAllBytes(getIcon().getAsFile().get().toPath()));
+
+ var data = new LinkedHashMap();
+ var neoFormVersion = getMcAndNeoFormVersion().get();
+ data.put("MAPPINGS", new LauncherDataEntry(String.format("[net.neoforged:neoform:%s:mappings@txt]", neoFormVersion), String.format("[net.neoforged:neoform:%s:mappings@txt]", neoFormVersion)));
+ data.put("MOJMAPS", new LauncherDataEntry(String.format("[net.minecraft:client:%s:mappings@txt]", neoFormVersion), String.format("[net.minecraft:server:%s:mappings@txt]", neoFormVersion)));
+ data.put("MERGED_MAPPINGS", new LauncherDataEntry(String.format("[net.neoforged:neoform:%s:mappings-merged@txt]", neoFormVersion), String.format("[net.neoforged:neoform:%s:mappings-merged@txt]", neoFormVersion)));
+ data.put("BINPATCH", new LauncherDataEntry("/data/client.lzma", "/data/server.lzma"));
+ data.put("MC_UNPACKED", new LauncherDataEntry(String.format("[net.minecraft:client:%s:unpacked]", neoFormVersion), String.format("[net.minecraft:server:%s:unpacked]", neoFormVersion)));
+ data.put("MC_SLIM", new LauncherDataEntry(String.format("[net.minecraft:client:%s:slim]", neoFormVersion), String.format("[net.minecraft:server:%s:slim]", neoFormVersion)));
+ data.put("MC_EXTRA", new LauncherDataEntry(String.format("[net.minecraft:client:%s:extra]", neoFormVersion), String.format("[net.minecraft:server:%s:extra]", neoFormVersion)));
+ data.put("MC_SRG", new LauncherDataEntry(String.format("[net.minecraft:client:%s:srg]", neoFormVersion), String.format("[net.minecraft:server:%s:srg]", neoFormVersion)));
+ data.put("PATCHED", new LauncherDataEntry(String.format("[%s:%s:%s:client]", "net.neoforged", "neoforge", getNeoForgeVersion().get()), String.format("[%s:%s:%s:server]", "net.neoforged", "neoforge", getNeoForgeVersion().get())));
+ data.put("MCP_VERSION", new LauncherDataEntry(String.format("'%s'", neoFormVersion), String.format("'%s'", neoFormVersion)));
+
+ var processors = new ArrayList();
+ BiConsumer> commonProcessor = (processor, args) -> addProcessor(processors, null, processor, args);
+ BiConsumer> clientProcessor = (processor, args) -> addProcessor(processors, List.of("client"), processor, args);
+ BiConsumer> serverProcessor = (processor, args) -> addProcessor(processors, List.of("server"), processor, args);
+
+ serverProcessor.accept(InstallerProcessor.INSTALLERTOOLS,
+ List.of("--task", "EXTRACT_FILES", "--archive", "{INSTALLER}",
+
+ "--from", "data/run.sh", "--to", "{ROOT}/run.sh", "--exec", "{ROOT}/run.sh",
+
+ "--from", "data/run.bat", "--to", "{ROOT}/run.bat",
+
+ "--from", "data/user_jvm_args.txt", "--to", "{ROOT}/user_jvm_args.txt", "--optional", "{ROOT}/user_jvm_args.txt",
+
+ "--from", "data/win_args.txt", "--to", "{ROOT}/libraries/net/neoforged/neoforge/%s/win_args.txt".formatted(getNeoForgeVersion().get()),
+
+ "--from", "data/unix_args.txt", "--to", "{ROOT}/libraries/net/neoforged/neoforge/%s/unix_args.txt".formatted(getNeoForgeVersion().get()))
+ );
+ serverProcessor.accept(InstallerProcessor.INSTALLERTOOLS,
+ List.of("--task", "BUNDLER_EXTRACT", "--input", "{MINECRAFT_JAR}", "--output", "{ROOT}/libraries/", "--libraries")
+ );
+ serverProcessor.accept(InstallerProcessor.INSTALLERTOOLS,
+ List.of("--task", "BUNDLER_EXTRACT", "--input", "{MINECRAFT_JAR}", "--output", "{MC_UNPACKED}", "--jar-only")
+ );
+ var neoformDependency = "net.neoforged:neoform:" + getMcAndNeoFormVersion().get() + "@zip";;
+ commonProcessor.accept(InstallerProcessor.INSTALLERTOOLS,
+ List.of("--task", "MCP_DATA", "--input", String.format("[%s]", neoformDependency), "--output", "{MAPPINGS}", "--key", "mappings")
+ );
+ commonProcessor.accept(InstallerProcessor.INSTALLERTOOLS,
+ List.of("--task", "DOWNLOAD_MOJMAPS", "--version", getMinecraftVersion().get(), "--side", "{SIDE}", "--output", "{MOJMAPS}")
+ );
+ commonProcessor.accept(InstallerProcessor.INSTALLERTOOLS,
+ List.of("--task", "MERGE_MAPPING", "--left", "{MAPPINGS}", "--right", "{MOJMAPS}", "--output", "{MERGED_MAPPINGS}", "--classes", "--fields", "--methods", "--reverse-right")
+ );
+ clientProcessor.accept(InstallerProcessor.JARSPLITTER,
+ List.of("--input", "{MINECRAFT_JAR}", "--slim", "{MC_SLIM}", "--extra", "{MC_EXTRA}", "--srg", "{MERGED_MAPPINGS}")
+ );
+ serverProcessor.accept(InstallerProcessor.JARSPLITTER,
+ List.of("--input", "{MC_UNPACKED}", "--slim", "{MC_SLIM}", "--extra", "{MC_EXTRA}", "--srg", "{MERGED_MAPPINGS}")
+ );
+ // Note that the options supplied here have to match the ones used in the RemapJar task used to generate the binary patches
+ commonProcessor.accept(InstallerProcessor.FART,
+ List.of("--input", "{MC_SLIM}", "--output", "{MC_SRG}", "--names", "{MERGED_MAPPINGS}", "--ann-fix", "--ids-fix", "--src-fix", "--record-fix")
+ );
+ commonProcessor.accept(InstallerProcessor.BINPATCHER,
+ List.of("--clean", "{MC_SRG}", "--output", "{PATCHED}", "--apply", "{BINPATCH}")
+ );
+
+ getLogger().info("Collecting libraries for Installer Profile");
+ // Remove potential duplicates.
+ var libraryFilesToResolve = new LinkedHashMap(getLibraryFiles().get().size());
+ for (var libraryFile : getLibraryFiles().get()) {
+ var existingFile = libraryFilesToResolve.putIfAbsent(libraryFile.getIdentifier().get(), libraryFile);
+ if (existingFile != null) {
+ var existing = existingFile.getFile().getAsFile().get();
+ var duplicate = libraryFile.getFile().getAsFile().get();
+ if (!existing.equals(duplicate)) {
+ throw new IllegalArgumentException("Cannot resolve installer profile! Library %s has different files: %s and %s.".formatted(
+ libraryFile.getIdentifier().get(),
+ existing,
+ duplicate));
+ }
+ }
+ }
+ var libraries = new ArrayList<>(
+ LibraryCollector.resolveLibraries(getRepositoryURLs().get(), libraryFilesToResolve.values()));
+
+ var universalJar = getUniversalJar().getAsFile().get().toPath();
+ libraries.add(new Library(
+ "net.neoforged:neoforge:%s:universal".formatted(getNeoForgeVersion().get()),
+ new LibraryDownload(new LibraryArtifact(
+ LibraryCollector.sha1Hash(universalJar),
+ Files.size(universalJar),
+ "https://maven.neoforged.net/releases/net/neoforged/neoforge/%s/neoforge-%s-universal.jar".formatted(
+ getNeoForgeVersion().get(),
+ getNeoForgeVersion().get()),
+ "net/neoforged/neoforge/%s/neoforge-%s-universal.jar".formatted(
+ getNeoForgeVersion().get(),
+ getNeoForgeVersion().get())
+ ))));
+
+ var profile = new InstallerProfile(
+ 1,
+ "NeoForge",
+ "neoforge-%s".formatted(getNeoForgeVersion().get()),
+ icon,
+ getMinecraftVersion().get(),
+ "/version.json",
+ "/big_logo.png",
+ "Welcome to the simple NeoForge installer",
+ "https://mirrors.neoforged.net",
+ true,
+ data,
+ processors,
+ libraries,
+ "{LIBRARY_DIR}/net/minecraft/server/{MINECRAFT_VERSION}/server-{MINECRAFT_VERSION}.jar"
+ );
+
+ FileUtils.writeStringSafe(
+ getInstallerProfile().getAsFile().get().toPath(),
+ new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(profile),
+ StandardCharsets.UTF_8
+ );
+ }
+}
+
+record InstallerProfile(
+ int spec,
+ String profile,
+ String version,
+ String icon,
+ String minecraft,
+ String json,
+ String logo,
+ String welcome,
+ String mirrorList,
+ boolean hideExtract,
+ Map data,
+ List processors,
+ List libraries,
+ String serverJarPath) {}
+
+record LauncherDataEntry(
+ String client,
+ String server) {}
+
+record ProcessorEntry(
+ @Nullable
+ List sides,
+ String jar,
+ List classpath,
+ List args) {}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateLauncherProfile.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateLauncherProfile.java
new file mode 100644
index 0000000000..c81a547564
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/CreateLauncherProfile.java
@@ -0,0 +1,130 @@
+package net.neoforged.neodev.installer;
+
+import com.google.gson.GsonBuilder;
+import net.neoforged.neodev.utils.DependencyUtils;
+import net.neoforged.neodev.utils.FileUtils;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.ListProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Creates the JSON file for running NeoForge via the Vanilla launcher.
+ */
+public abstract class CreateLauncherProfile extends DefaultTask {
+ @Inject
+ public CreateLauncherProfile() {}
+
+ @Input
+ public abstract Property getFmlVersion();
+
+ @Input
+ public abstract Property getMinecraftVersion();
+
+ @Input
+ public abstract Property getNeoForgeVersion();
+
+ @Input
+ public abstract Property getRawNeoFormVersion();
+
+ @Nested
+ protected abstract ListProperty getLibraryFiles();
+
+ public void setLibraries(Configuration libraries) {
+ getLibraryFiles().set(IdentifiedFile.listFromConfiguration(getProject(), libraries));
+ }
+
+ @Input
+ public abstract ListProperty getRepositoryURLs();
+
+ @Input
+ public abstract ListProperty getIgnoreList();
+
+ @Input
+ protected abstract Property getModulePath();
+
+ public void setModules(Configuration modules) {
+ getModulePath().set(DependencyUtils.configurationToClasspath(modules, "${library_directory}/", "${classpath_separator}"));
+ }
+
+ @OutputFile
+ public abstract RegularFileProperty getLauncherProfile();
+
+ @TaskAction
+ public void createLauncherProfile() throws IOException {
+ var time = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
+
+ getLogger().info("Collecting libraries for Launcher Profile");
+ var libraries = LibraryCollector.resolveLibraries(getRepositoryURLs().get(), getLibraryFiles().get());
+
+ var gameArguments = new ArrayList<>(List.of(
+ "--fml.neoForgeVersion", getNeoForgeVersion().get(),
+ "--fml.fmlVersion", getFmlVersion().get(),
+ "--fml.mcVersion", getMinecraftVersion().get(),
+ "--fml.neoFormVersion", getRawNeoFormVersion().get(),
+ "--launchTarget", "neoforgeclient"));
+
+ var jvmArguments = new ArrayList<>(List.of(
+ "-Djava.net.preferIPv6Addresses=system",
+ "-DignoreList=" + String.join(",", getIgnoreList().get()),
+ "-DlibraryDirectory=${library_directory}"));
+
+ jvmArguments.add("-p");
+ jvmArguments.add(getModulePath().get());
+
+ jvmArguments.addAll(List.of(
+ "--add-modules", "ALL-MODULE-PATH",
+ "--add-opens", "java.base/java.util.jar=cpw.mods.securejarhandler",
+ "--add-opens", "java.base/java.lang.invoke=cpw.mods.securejarhandler",
+ "--add-exports", "java.base/sun.security.util=cpw.mods.securejarhandler",
+ "--add-exports", "jdk.naming.dns/com.sun.jndi.dns=java.naming"));
+
+ var arguments = new LinkedHashMap>();
+ arguments.put("game", gameArguments);
+ arguments.put("jvm", jvmArguments);
+
+ var profile = new LauncherProfile(
+ "neoforge-%s".formatted(getNeoForgeVersion().get()),
+ time,
+ time,
+ "release",
+ "cpw.mods.bootstraplauncher.BootstrapLauncher",
+ getMinecraftVersion().get(),
+ arguments,
+ libraries
+ );
+
+ FileUtils.writeStringSafe(
+ getLauncherProfile().getAsFile().get().toPath(),
+ new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(profile),
+ StandardCharsets.UTF_8
+ );
+ }
+}
+
+record LauncherProfile(
+ String id,
+ String time,
+ String releaseTime,
+ String type,
+ String mainClass,
+ String inheritsFrom,
+ Map> arguments,
+ List libraries) {}
+
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/IdentifiedFile.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/IdentifiedFile.java
new file mode 100644
index 0000000000..ff75e1ba75
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/IdentifiedFile.java
@@ -0,0 +1,48 @@
+package net.neoforged.neodev.installer;
+
+import net.neoforged.neodev.utils.DependencyUtils;
+import net.neoforged.neodev.utils.MavenIdentifier;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.result.ResolvedArtifactResult;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.provider.Provider;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.PathSensitive;
+import org.gradle.api.tasks.PathSensitivity;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.List;
+
+/**
+ * Combines a {@link File} and its {@link MavenIdentifier maven identifier},
+ * for usage as task inputs that will be passed to {@link LibraryCollector}.
+ */
+public abstract class IdentifiedFile {
+ public static Provider> listFromConfiguration(Project project, Configuration configuration) {
+ return configuration.getIncoming().getArtifacts().getResolvedArtifacts().map(
+ artifacts -> artifacts.stream()
+ .map(artifact -> IdentifiedFile.of(project, artifact))
+ .toList());
+ }
+
+ private static IdentifiedFile of(Project project, ResolvedArtifactResult resolvedArtifact) {
+ var identifiedFile = project.getObjects().newInstance(IdentifiedFile.class);
+ identifiedFile.getFile().set(resolvedArtifact.getFile());
+ identifiedFile.getIdentifier().set(DependencyUtils.guessMavenIdentifier(resolvedArtifact));
+ return identifiedFile;
+ }
+
+ @Inject
+ public IdentifiedFile() {}
+
+ @InputFile
+ @PathSensitive(PathSensitivity.NONE)
+ public abstract RegularFileProperty getFile();
+
+ @Input
+ public abstract Property getIdentifier();
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/InstallerProcessor.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/InstallerProcessor.java
new file mode 100644
index 0000000000..970fa6d1cb
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/InstallerProcessor.java
@@ -0,0 +1,19 @@
+package net.neoforged.neodev.installer;
+
+import net.neoforged.neodev.Tools;
+
+/**
+ * Identifies the tools used by the {@link InstallerProfile} to install NeoForge.
+ */
+public enum InstallerProcessor {
+ BINPATCHER(Tools.BINPATCHER),
+ FART(Tools.AUTO_RENAMING_TOOL),
+ INSTALLERTOOLS(Tools.INSTALLERTOOLS),
+ JARSPLITTER(Tools.JARSPLITTER);
+
+ public final Tools tool;
+
+ InstallerProcessor(Tools tool) {
+ this.tool = tool;
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/Library.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/Library.java
new file mode 100644
index 0000000000..46669967f4
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/Library.java
@@ -0,0 +1,3 @@
+package net.neoforged.neodev.installer;
+
+record Library(String name, LibraryDownload downloads) {}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryArtifact.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryArtifact.java
new file mode 100644
index 0000000000..1e345980e9
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryArtifact.java
@@ -0,0 +1,7 @@
+package net.neoforged.neodev.installer;
+
+record LibraryArtifact(
+ String sha1,
+ long size,
+ String url,
+ String path) {}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryCollector.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryCollector.java
new file mode 100644
index 0000000000..7417ac14c3
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryCollector.java
@@ -0,0 +1,174 @@
+package net.neoforged.neodev.installer;
+
+import net.neoforged.neodev.utils.MavenIdentifier;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+
+/**
+ * For each file in a collection, finds the repository that the file came from.
+ */
+class LibraryCollector {
+ public static List resolveLibraries(List repositoryUrls, Collection libraries) throws IOException {
+ var collector = new LibraryCollector(repositoryUrls);
+ for (var library : libraries) {
+ collector.addLibrary(library.getFile().getAsFile().get(), library.getIdentifier().get());
+ }
+
+ var result = collector.libraries.stream().map(future -> {
+ try {
+ return future.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }).toList();
+ LOGGER.info("Collected %d libraries".formatted(result.size()));
+ return result;
+ }
+
+ private static final Logger LOGGER = Logging.getLogger(LibraryCollector.class);
+ /**
+ * Hosts from which we allow the installer to download.
+ * We whitelist here to avoid redirecting player download traffic to anyone not affiliated with Mojang or us.
+ */
+ private static final List HOST_WHITELIST = List.of(
+ "minecraft.net",
+ "neoforged.net",
+ "mojang.com"
+ );
+
+ private static final URI MOJANG_MAVEN = URI.create("https://libraries.minecraft.net");
+ private static final URI NEOFORGED_MAVEN = URI.create("https://maven.neoforged.net/releases");
+
+ private final List repositoryUrls;
+
+ private final List> libraries = new ArrayList<>();
+
+ private final HttpClient httpClient = HttpClient.newBuilder().build();
+
+ private LibraryCollector(List repoUrl) {
+ this.repositoryUrls = new ArrayList<>(repoUrl);
+
+ // Only remote repositories make sense (no maven local)
+ repositoryUrls.removeIf(it -> {
+ var lowercaseScheme = it.getScheme().toLowerCase(Locale.ROOT);
+ return !lowercaseScheme.equals("https") && !lowercaseScheme.equals("http");
+ });
+ // Allow only URLs from whitelisted hosts
+ repositoryUrls.removeIf(uri -> {
+ var lowercaseHost = uri.getHost().toLowerCase(Locale.ROOT);
+ return HOST_WHITELIST.stream().noneMatch(it -> lowercaseHost.equals(it) || lowercaseHost.endsWith("." + it));
+ });
+ // Always try Mojang Maven first, then our installer Maven
+ repositoryUrls.removeIf(it -> it.getHost().equals(MOJANG_MAVEN.getHost()));
+ repositoryUrls.removeIf(it -> it.getHost().equals(NEOFORGED_MAVEN.getHost()) && it.getPath().startsWith(NEOFORGED_MAVEN.getPath()));
+ repositoryUrls.add(0, NEOFORGED_MAVEN);
+ repositoryUrls.add(0, MOJANG_MAVEN);
+
+ LOGGER.info("Collecting libraries from:");
+ for (var repo : repositoryUrls) {
+ LOGGER.info(" - " + repo);
+ }
+ }
+
+ private void addLibrary(File file, MavenIdentifier identifier) throws IOException {
+ final String name = identifier.artifactNotation();
+ final String path = identifier.repositoryPath();
+
+ var sha1 = sha1Hash(file.toPath());
+ var fileSize = Files.size(file.toPath());
+
+ // Try each configured repository in-order to find the file
+ CompletableFuture libraryFuture = null;
+ for (var repositoryUrl : repositoryUrls) {
+ var artifactUri = joinUris(repositoryUrl, path);
+ var request = HttpRequest.newBuilder(artifactUri)
+ .method("HEAD", HttpRequest.BodyPublishers.noBody())
+ .build();
+
+ Function> makeRequest = (String previousError) -> {
+ return httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding())
+ .thenApply(response -> {
+ if (response.statusCode() != 200) {
+ LOGGER.info(" Got %d for %s".formatted(response.statusCode(), artifactUri));
+ String message = "Could not find %s: %d".formatted(artifactUri, response.statusCode());
+ // Prepend error message from previous repo if they all fail
+ if (previousError != null) {
+ message = previousError + "\n" + message;
+ }
+ throw new RuntimeException(message);
+ }
+ LOGGER.info(" Found %s -> %s".formatted(name, artifactUri));
+ return new Library(
+ name,
+ new LibraryDownload(new LibraryArtifact(
+ sha1,
+ fileSize,
+ artifactUri.toString(),
+ path)));
+ });
+ };
+
+ if (libraryFuture == null) {
+ libraryFuture = makeRequest.apply(null);
+ } else {
+ libraryFuture = libraryFuture.exceptionallyCompose(error -> {
+ return makeRequest.apply(error.getMessage());
+ });
+ }
+ }
+
+ libraries.add(libraryFuture);
+ }
+
+ static String sha1Hash(Path path) throws IOException {
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+
+ try (var in = Files.newInputStream(path);
+ var din = new DigestInputStream(in, digest)) {
+ byte[] buffer = new byte[8192];
+ while (din.read(buffer) != -1) {
+ }
+ }
+
+ return HexFormat.of().formatHex(digest.digest());
+ }
+
+ private static URI joinUris(URI repositoryUrl, String path) {
+ var baseUrl = repositoryUrl.toString();
+ if (baseUrl.endsWith("/") && path.startsWith("/")) {
+ while (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ return URI.create(baseUrl + path);
+ } else if (!baseUrl.endsWith("/") && !path.startsWith("/")) {
+ return URI.create(baseUrl + "/" + path);
+ } else {
+ return URI.create(baseUrl + path);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryDownload.java b/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryDownload.java
new file mode 100644
index 0000000000..38afe5d057
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/installer/LibraryDownload.java
@@ -0,0 +1,4 @@
+package net.neoforged.neodev.installer;
+
+record LibraryDownload(
+ LibraryArtifact artifact) {}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/utils/DependencyUtils.java b/buildSrc/src/main/java/net/neoforged/neodev/utils/DependencyUtils.java
new file mode 100644
index 0000000000..ee1bb65175
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/utils/DependencyUtils.java
@@ -0,0 +1,84 @@
+package net.neoforged.neodev.utils;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.result.ResolvedArtifactResult;
+import org.gradle.api.provider.Provider;
+import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public final class DependencyUtils {
+ private DependencyUtils() {
+ }
+
+ /**
+ * Given a resolved artifact, try to guess which Maven GAV it was resolved from.
+ */
+ public static MavenIdentifier guessMavenIdentifier(ResolvedArtifactResult result) {
+ String group;
+ String artifact;
+ String version;
+ String ext = "";
+ String classifier = "";
+
+ var filename = result.getFile().getName();
+ var startOfExt = filename.lastIndexOf('.');
+ if (startOfExt != -1) {
+ ext = filename.substring(startOfExt + 1);
+ filename = filename.substring(0, startOfExt);
+ }
+
+ if (result.getId() instanceof ModuleComponentArtifactIdentifier moduleId) {
+ group = moduleId.getComponentIdentifier().getGroup();
+ artifact = moduleId.getComponentIdentifier().getModule();
+ version = moduleId.getComponentIdentifier().getVersion();
+ var expectedBasename = artifact + "-" + version;
+
+ if (filename.startsWith(expectedBasename + "-")) {
+ classifier = filename.substring((expectedBasename + "-").length());
+ }
+ } else {
+ // When we encounter a project reference, the component identifier does not expose the group or module name.
+ // But we can access the list of capabilities associated with the published variant the artifact originates from.
+ // If the capability was not overridden, this will be the project GAV. If it is *not* the project GAV,
+ // it will be at least in valid GAV format, not crashing NFRT when it parses the manifest. It will just be ignored.
+ var capabilities = result.getVariant().getCapabilities();
+ if (capabilities.size() == 1) {
+ var capability = capabilities.get(0);
+ group = capability.getGroup();
+ artifact = capability.getName();
+ version = capability.getVersion();
+ } else {
+ throw new IllegalArgumentException("Don't know how to break " + result.getId().getComponentIdentifier() + " into Maven components.");
+ }
+ }
+ return new MavenIdentifier(group, artifact, version, classifier, ext);
+ }
+
+ /**
+ * Turns a configuration into a list of GAV entries.
+ */
+ public static Provider> configurationToGavList(Configuration configuration) {
+ return configuration.getIncoming().getArtifacts().getResolvedArtifacts().map(results -> {
+ // Using .toList() fails with the configuration cache - looks like Gradle can't deserialize the resulting list?
+ return results.stream().map(artifact -> guessMavenIdentifier(artifact).artifactNotation()).collect(Collectors.toCollection(ArrayList::new));
+ });
+ }
+
+ /**
+ * Turns a configuration into a classpath string,
+ * assuming that the contents of the configuration are installed following the Maven directory layout.
+ *
+ * @param prefix string to add in front of each classpath entry
+ * @param separator separator to add between each classpath entry
+ */
+ public static Provider configurationToClasspath(Configuration configuration, String prefix, String separator) {
+ return configuration.getIncoming().getArtifacts().getResolvedArtifacts().map(
+ results -> results.stream()
+ .map(artifact -> prefix + guessMavenIdentifier(artifact).repositoryPath())
+ .collect(Collectors.joining(separator))
+ );
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/utils/FileUtils.java b/buildSrc/src/main/java/net/neoforged/neodev/utils/FileUtils.java
new file mode 100644
index 0000000000..d91996d32d
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/utils/FileUtils.java
@@ -0,0 +1,72 @@
+package net.neoforged.neodev.utils;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+
+public final class FileUtils {
+ private FileUtils() {
+ }
+
+ public static void writeStringSafe(Path destination, String content, Charset charset) throws IOException {
+ if (!charset.newEncoder().canEncode(content)) {
+ throw new IllegalArgumentException("The given character set " + charset
+ + " cannot represent this string: " + content);
+ }
+
+ try (var out = newSafeFileOutputStream(destination)) {
+ var encodedContent = content.getBytes(charset);
+ out.write(encodedContent);
+ }
+ }
+
+ public static void writeLinesSafe(Path destination, List lines, Charset charset) throws IOException {
+ writeStringSafe(destination, String.join("\n", lines), charset);
+ }
+
+ public static OutputStream newSafeFileOutputStream(Path destination) throws IOException {
+ var uniqueId = ProcessHandle.current().pid() + "." + Thread.currentThread().getId();
+
+ var tempFile = destination.resolveSibling(destination.getFileName().toString() + "." + uniqueId + ".tmp");
+ var closed = new boolean[1];
+ return new FilterOutputStream(Files.newOutputStream(tempFile)) {
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ if (!closed[0]) {
+ atomicMoveIfPossible(tempFile, destination);
+ }
+ } finally {
+ try {
+ Files.deleteIfExists(tempFile);
+ } catch (IOException ignored) {
+ }
+ closed[0] = true;
+ }
+ }
+ };
+ }
+
+ /**
+ * Atomically moves the given source file to the given destination file.
+ * If the atomic move is not supported, the file will be moved normally.
+ *
+ * @param source The source file
+ * @param destination The destination file
+ * @throws IOException If an I/O error occurs
+ */
+ private static void atomicMoveIfPossible(final Path source, final Path destination) throws IOException {
+ try {
+ Files.move(source, destination, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+ } catch (AtomicMoveNotSupportedException ex) {
+ Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/net/neoforged/neodev/utils/MavenIdentifier.java b/buildSrc/src/main/java/net/neoforged/neodev/utils/MavenIdentifier.java
new file mode 100644
index 0000000000..21dc04655b
--- /dev/null
+++ b/buildSrc/src/main/java/net/neoforged/neodev/utils/MavenIdentifier.java
@@ -0,0 +1,53 @@
+package net.neoforged.neodev.utils;
+
+import java.io.Serializable;
+
+public record MavenIdentifier(String group, String artifact, String version, String classifier, String extension) implements Serializable {
+ public String artifactNotation() {
+ return group + ":" + artifact + ":" + version + (classifier.isEmpty() ? "" : ":" + classifier) + ("jar".equals(extension) ? "" : "@" + extension);
+ }
+
+ public String repositoryPath() {
+ return group.replace(".", "/") + "/" + artifact + "/" + version + "/" + artifact + "-" + version + (classifier.isEmpty() ? "" : "-" + classifier) + "." + extension;
+ }
+
+ /**
+ * Valid forms:
+ *
+ * - {@code groupId:artifactId:version}
+ * - {@code groupId:artifactId:version:classifier}
+ * - {@code groupId:artifactId:version:classifier@extension}
+ * - {@code groupId:artifactId:version@extension}
+ *
+ */
+ public static MavenIdentifier parse(String coordinate) {
+ var coordinateAndExt = coordinate.split("@");
+ String extension = "jar";
+ if (coordinateAndExt.length > 2) {
+ throw new IllegalArgumentException("Malformed Maven coordinate: " + coordinate);
+ } else if (coordinateAndExt.length == 2) {
+ extension = coordinateAndExt[1];
+ coordinate = coordinateAndExt[0];
+ }
+
+ var parts = coordinate.split(":");
+ if (parts.length != 3 && parts.length != 4) {
+ throw new IllegalArgumentException("Malformed Maven coordinate: " + coordinate);
+ }
+
+ var groupId = parts[0];
+ var artifactId = parts[1];
+ var version = parts[2];
+ var classifier = parts.length == 4 ? parts[3] : "";
+ return new MavenIdentifier(groupId, artifactId, version, classifier, extension);
+ }
+
+ @Override
+ public String toString() {
+ if (classifier != null) {
+ return group + ":" + artifact + ":" + version + ":" + classifier + "@" + extension;
+ } else {
+ return group + ":" + artifact + ":" + version + "@" + extension;
+ }
+ }
+}
diff --git a/coremods/build.gradle b/coremods/build.gradle
index 3d942a4701..127e7ec9df 100644
--- a/coremods/build.gradle
+++ b/coremods/build.gradle
@@ -5,15 +5,6 @@ plugins {
id 'neoforge.formatting-conventions'
}
-repositories {
- maven { url = 'https://maven.neoforged.net/releases' }
- maven {
- name 'Mojang'
- url 'https://libraries.minecraft.net'
- }
- mavenCentral()
-}
-
jar {
manifest {
attributes(
diff --git a/coremods/settings.gradle b/coremods/settings.gradle
new file mode 100644
index 0000000000..06c2cf68e4
--- /dev/null
+++ b/coremods/settings.gradle
@@ -0,0 +1,8 @@
+repositories {
+ maven { url = 'https://maven.neoforged.net/releases' }
+ maven {
+ name 'Mojang'
+ url 'https://libraries.minecraft.net'
+ }
+ mavenCentral()
+}
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 80f55cebf7..00f2af575d 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -19,8 +19,11 @@ Contributing to NeoForge
8. Modify the patched Minecraft sources in `projects/neoforge/src/main/java` as needed. The unmodified sources are available in `projects/base/src/main/java` for your reference. Do not modify these.
9. Test your changes
- Run the game (Runs are available in the IDE)
- - Run `gradlew :tests:runGameTestServer` or `Tests: GameTestServer` from IDE
- - Run `gradlew :tests:runGameTestClient` or `Tests: GameTestClient` from IDE
+ - Runs starting with `base -` run Vanilla without NeoForge or its patches.
+ - Runs starting with `neoforge -` run NeoForge.
+ - Runs starting with `tests -` run NeoForge along with the test mods in the `tests` project.
+ - Run `gradlew :tests:runGameTestServer` or `tests - GameTestServer` from IDE
+ - Run `gradlew :tests:runClient` or `tests - Client` from IDE
- If possible, write an automated test under the tests project. See [NEOGAMETESTS.md](NEOGAMETESTS.md) for more info.
10. Run `gradlew genPatches` to generate patch-files from the patched sources
11. Run `gradlew applyAllFormatting` to automatically format sources
diff --git a/gradle.properties b/gradle.properties
index ee9ba5e243..3ab3c77e91 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,15 +4,29 @@ org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
-org.gradle.configuration-cache=false
+org.gradle.configuration-cache=true
org.gradle.debug=false
+#org.gradle.warning.mode=fail
+
+# renovate: net.neoforged:moddev-gradle
+moddevgradle_plugin_version=2.0.48-beta
+# renovate: io.codechicken:DiffPatch
+diffpatch_version=2.0.0.35
java_version=21
-minecraft_version=1.21.3
-neoform_version=20241023.131943
+minecraft_version=1.21.4
+neoform_version=20241203.161809
# on snapshot versions, used to prefix the version
-neoforge_snapshot_next_stable=21.4
+neoforge_snapshot_next_stable=21.5
+
+# renovate: net.neoforged.jst:jst-cli-bundle
+jst_version=1.0.45
+legacyinstaller_version=3.0.+
+# renovate: net.neoforged:AutoRenamingTool
+art_version=2.0.3
+# renovate: net.neoforged.installertools:installertools
+installertools_version=2.1.2
mergetool_version=2.0.0
accesstransformers_version=11.0.1
@@ -22,7 +36,6 @@ modlauncher_version=11.0.4
securejarhandler_version=3.0.8
bootstraplauncher_version=2.0.2
asm_version=9.7
-installer_version=2.1.+
mixin_version=0.15.2+mixin.0.8.7
terminalconsoleappender_version=1.3.0
nightconfig_version=3.8.0
@@ -30,7 +43,7 @@ jetbrains_annotations_version=24.0.1
slf4j_api_version=2.0.7
apache_maven_artifact_version=3.8.5
jarjar_version=0.4.1
-fancy_mod_loader_version=5.0.1
+fancy_mod_loader_version=6.0.4
mojang_logging_version=1.1.1
log4j_version=2.22.1
guava_version=31.1.2-jre
@@ -43,12 +56,8 @@ nashorn_core_version=15.3
lwjgl_glfw_version=3.3.2
mixin_extras_version=0.4.1
-jupiter_api_version=5.7.0
+jupiter_api_version=5.10.2
vintage_engine_version=5.+
assertj_core=3.25.1
neogradle.runtime.platform.installer.debug=true
-# We want to be able to have a junit run disconnected from the test and main sourcesets
-neogradle.subsystems.conventions.sourcesets.automatic-inclusion=false
-neogradle.subsystems.conventions.enabled=false
-neogradle.subsystems.tools.jst=net.neoforged.jst:jst-cli-bundle:1.0.45
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9355b41557..0aaefbcaf0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/patches/com/mojang/blaze3d/platform/Window.java.patch b/patches/com/mojang/blaze3d/platform/Window.java.patch
index f211b35fad..043b11d2cb 100644
--- a/patches/com/mojang/blaze3d/platform/Window.java.patch
+++ b/patches/com/mojang/blaze3d/platform/Window.java.patch
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/platform/Window.java
+++ b/com/mojang/blaze3d/platform/Window.java
-@@ -90,7 +_,8 @@
+@@ -91,7 +_,8 @@
GLFW.glfwWindowHint(139267, 2);
GLFW.glfwWindowHint(139272, 204801);
GLFW.glfwWindowHint(139270, 1);
@@ -10,7 +10,7 @@
if (monitor != null) {
VideoMode videomode = monitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
this.windowedX = this.x = monitor.getX() + videomode.getWidth() / 2 - this.width / 2;
-@@ -102,6 +_,7 @@
+@@ -103,6 +_,7 @@
this.windowedX = this.x = aint1[0];
this.windowedY = this.y = aint[0];
}
@@ -18,7 +18,7 @@
GLFW.glfwMakeContextCurrent(this.window);
GL.createCapabilities();
-@@ -269,6 +_,7 @@
+@@ -273,6 +_,7 @@
GLFW.glfwGetFramebufferSize(this.window, aint, aint1);
this.framebufferWidth = aint[0] > 0 ? aint[0] : 1;
this.framebufferHeight = aint1[0] > 0 ? aint1[0] : 1;
diff --git a/patches/com/mojang/blaze3d/systems/RenderSystem.java.patch b/patches/com/mojang/blaze3d/systems/RenderSystem.java.patch
index 756e40c7c9..28061afef1 100644
--- a/patches/com/mojang/blaze3d/systems/RenderSystem.java.patch
+++ b/patches/com/mojang/blaze3d/systems/RenderSystem.java.patch
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/systems/RenderSystem.java
+++ b/com/mojang/blaze3d/systems/RenderSystem.java
-@@ -822,4 +_,14 @@
+@@ -818,4 +_,14 @@
void accept(it.unimi.dsi.fastutil.ints.IntConsumer p_157488_, int p_157489_);
}
}
diff --git a/patches/net/minecraft/Util.java.patch b/patches/net/minecraft/Util.java.patch
index 06262a76e0..96bc929990 100644
--- a/patches/net/minecraft/Util.java.patch
+++ b/patches/net/minecraft/Util.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/Util.java
+++ b/net/minecraft/Util.java
-@@ -264,8 +_,8 @@
+@@ -268,8 +_,8 @@
.getSchema(DataFixUtils.makeKey(SharedConstants.getCurrentVersion().getDataVersion().getVersion()))
.getChoiceType(p_137552_, p_137553_);
} catch (IllegalArgumentException illegalargumentexception) {
@@ -11,7 +11,7 @@
throw illegalargumentexception;
}
}
-@@ -630,20 +_,20 @@
+@@ -634,20 +_,20 @@
public static void logAndPauseIfInIde(String p_143786_) {
LOGGER.error(p_143786_);
diff --git a/patches/net/minecraft/client/KeyMapping.java.patch b/patches/net/minecraft/client/KeyMapping.java.patch
index 05cdbda228..3fb1afaedf 100644
--- a/patches/net/minecraft/client/KeyMapping.java.patch
+++ b/patches/net/minecraft/client/KeyMapping.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/KeyMapping.java
+++ b/net/minecraft/client/KeyMapping.java
-@@ -14,9 +_,9 @@
+@@ -15,9 +_,9 @@
import net.neoforged.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
@@ -12,7 +12,7 @@
private static final Set CATEGORIES = Sets.newHashSet();
public static final String CATEGORY_MOVEMENT = "key.categories.movement";
public static final String CATEGORY_MISC = "key.categories.misc";
-@@ -42,17 +_,17 @@
+@@ -43,17 +_,17 @@
private int clickCount;
public static void click(InputConstants.Key p_90836_) {
@@ -35,7 +35,7 @@
}
public static void setAll() {
-@@ -100,7 +_,7 @@
+@@ -101,7 +_,7 @@
}
public boolean isDown() {
@@ -44,7 +44,7 @@
}
public String getCategory() {
-@@ -134,9 +_,13 @@
+@@ -135,9 +_,13 @@
}
public int compareTo(KeyMapping p_90841_) {
@@ -61,7 +61,7 @@
}
public static Supplier createNameSupplier(String p_90843_) {
-@@ -145,6 +_,20 @@
+@@ -146,6 +_,20 @@
}
public boolean same(KeyMapping p_90851_) {
@@ -82,7 +82,7 @@
return this.key.equals(p_90851_.key);
}
-@@ -163,11 +_,13 @@
+@@ -164,11 +_,13 @@
}
public Component getTranslatedKeyMessage() {
@@ -97,10 +97,10 @@
}
public String saveString() {
-@@ -176,5 +_,86 @@
-
- public void setDown(boolean p_90846_) {
- this.isDown = p_90846_;
+@@ -182,5 +_,86 @@
+ @Nullable
+ public static KeyMapping get(String p_389468_) {
+ return ALL.get(p_389468_);
+ }
+
+ // Neo: Injected Key Mapping controls
diff --git a/patches/net/minecraft/client/Minecraft.java.patch b/patches/net/minecraft/client/Minecraft.java.patch
index d73618ac55..c43825a350 100644
--- a/patches/net/minecraft/client/Minecraft.java.patch
+++ b/patches/net/minecraft/client/Minecraft.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/Minecraft.java
+++ b/net/minecraft/client/Minecraft.java
-@@ -253,7 +_,7 @@
+@@ -250,7 +_,7 @@
import org.slf4j.Logger;
@OnlyIn(Dist.CLIENT)
@@ -9,7 +9,7 @@
static Minecraft instance;
private static final Logger LOGGER = LogUtils.getLogger();
public static final boolean ON_OSX = Util.getPlatform() == Util.OS.OSX;
-@@ -437,7 +_,6 @@
+@@ -432,7 +_,6 @@
}
}, Util.nonCriticalIoPool());
LOGGER.info("Setting user: {}", this.user.getName());
@@ -17,7 +17,7 @@
this.demo = p_91084_.game.demo;
this.allowsMultiplayer = !p_91084_.game.disableMultiplayer;
this.allowsChat = !p_91084_.game.disableChat;
-@@ -488,15 +_,15 @@
+@@ -483,15 +_,17 @@
LOGGER.error("Couldn't set icon", (Throwable)ioexception);
}
@@ -32,18 +32,20 @@
this.mainRenderTarget.clear();
this.resourceManager = new ReloadableResourceManager(PackType.CLIENT_RESOURCES);
+ net.neoforged.neoforge.client.loading.ClientModLoader.begin(this, this.resourcePackRepository, this.resourceManager);
++ //Move client bootstrap to after mod loading so that events can be fired for it.
++ ClientBootstrap.bootstrap();
this.resourcePackRepository.reload();
this.options.loadSelectedResourcePacks(this.resourcePackRepository);
this.languageManager = new LanguageManager(this.options.languageCode, p_344151_ -> {
-@@ -582,6 +_,7 @@
+@@ -571,6 +_,7 @@
);
- this.resourceManager.registerReloadListener(this.entityRenderDispatcher);
+ this.resourceManager.registerReloadListener(this.blockEntityRenderDispatcher);
this.particleEngine = new ParticleEngine(this.level, this.textureManager);
+ net.neoforged.neoforge.client.ClientHooks.onRegisterParticleProviders(this.particleEngine);
this.resourceManager.registerReloadListener(this.particleEngine);
this.paintingTextures = new PaintingTextureManager(this.textureManager);
this.resourceManager.registerReloadListener(this.paintingTextures);
-@@ -591,11 +_,15 @@
+@@ -580,11 +_,15 @@
this.resourceManager.registerReloadListener(this.guiSprites);
this.gameRenderer = new GameRenderer(this, this.entityRenderDispatcher.getItemInHandRenderer(), this.resourceManager, this.renderBuffers);
this.levelRenderer = new LevelRenderer(this, this.entityRenderDispatcher, this.blockEntityRenderDispatcher, this.renderBuffers);
@@ -59,7 +61,7 @@
this.gui = new Gui(this);
this.debugRenderer = new DebugRenderer(this);
RealmsClient realmsclient = RealmsClient.create(this);
-@@ -620,6 +_,7 @@
+@@ -609,6 +_,7 @@
this.options.fullscreen().set(this.window.isFullscreen());
}
@@ -67,7 +69,7 @@
this.window.updateVsync(this.options.enableVsync().get());
this.window.updateRawMouseInput(this.options.rawMouseInput().get());
this.window.setDefaultErrorCallback();
-@@ -641,16 +_,18 @@
+@@ -631,16 +_,18 @@
GameLoadTimesEvent.INSTANCE.beginStep(TelemetryProperty.LOAD_TIME_LOADING_OVERLAY_MS);
Minecraft.GameLoadCookie minecraft$gameloadcookie = new Minecraft.GameLoadCookie(realmsclient, p_91084_.quickPlay);
this.setOverlay(
@@ -90,7 +92,7 @@
);
this.quickPlayLog = QuickPlayLog.of(p_91084_.quickPlay.path());
this.framerateLimitTracker = new FramerateLimitTracker(this.options, this);
-@@ -696,6 +_,8 @@
+@@ -686,6 +_,8 @@
runnable = () -> this.setScreen(screen);
}
@@ -99,7 +101,7 @@
return runnable;
}
-@@ -744,7 +_,7 @@
+@@ -734,7 +_,7 @@
private String createTitle() {
StringBuilder stringbuilder = new StringBuilder("Minecraft");
if (checkModStatus().shouldReportAsModified()) {
@@ -108,7 +110,7 @@
}
stringbuilder.append(" ");
-@@ -776,7 +_,7 @@
+@@ -766,7 +_,7 @@
}
private void rollbackResourcePacks(Throwable p_91240_, @Nullable Minecraft.GameLoadCookie p_299846_) {
@@ -117,7 +119,7 @@
this.clearResourcePacksOnError(p_91240_, null, p_299846_);
} else {
Util.throwAsRuntime(p_91240_);
-@@ -935,7 +_,7 @@
+@@ -925,7 +_,7 @@
p_307414_.soundManager.emergencyShutdown();
}
@@ -126,7 +128,7 @@
}
public boolean isEnforceUnicode() {
-@@ -1062,9 +_,7 @@
+@@ -1052,9 +_,7 @@
LOGGER.error("setScreen called from non-game thread");
}
@@ -137,7 +139,7 @@
this.setLastInputType(InputType.NONE);
}
-@@ -1081,6 +_,19 @@
+@@ -1071,6 +_,19 @@
}
}
@@ -157,7 +159,7 @@
this.screen = p_91153_;
if (this.screen != null) {
this.screen.added();
-@@ -1234,9 +_,11 @@
+@@ -1224,9 +_,11 @@
this.mouseHandler.handleAccumulatedMovement();
profilerfiller.pop();
if (!this.noRender) {
@@ -169,7 +171,7 @@
}
profilerfiller.push("blit");
-@@ -1264,9 +_,13 @@
+@@ -1257,9 +_,13 @@
profilerfiller.pop();
this.window.setErrorSection("Post render");
this.frames++;
@@ -184,7 +186,7 @@
this.deltaTracker.updatePauseState(this.pause);
this.deltaTracker.updateFrozenState(!this.isLevelRunningNormally());
long l = Util.getNanos();
-@@ -1358,10 +_,12 @@
+@@ -1351,10 +_,12 @@
this.window.setGuiScale((double)i);
if (this.screen != null) {
this.screen.resize(this, this.window.getGuiScaledWidth(), this.window.getGuiScaledHeight());
@@ -197,7 +199,7 @@
this.gameRenderer.resize(this.window.getWidth(), this.window.getHeight());
this.mouseHandler.setIgnoreFirstMove();
}
-@@ -1502,6 +_,7 @@
+@@ -1495,6 +_,7 @@
}
public void stop() {
@@ -205,7 +207,7 @@
this.running = false;
}
-@@ -1531,9 +_,17 @@
+@@ -1524,9 +_,17 @@
BlockHitResult blockhitresult = (BlockHitResult)this.hitResult;
BlockPos blockpos = blockhitresult.getBlockPos();
if (!this.level.getBlockState(blockpos).isAir()) {
@@ -225,7 +227,7 @@
this.player.swing(InteractionHand.MAIN_HAND);
}
}
-@@ -1561,6 +_,8 @@
+@@ -1554,6 +_,8 @@
return false;
} else {
boolean flag = false;
@@ -234,7 +236,7 @@
switch (this.hitResult.getType()) {
case ENTITY:
this.gameMode.attack(this.player, ((EntityHitResult)this.hitResult).getEntity());
-@@ -1581,8 +_,10 @@
+@@ -1574,8 +_,10 @@
}
this.player.resetAttackStrengthTicker();
@@ -245,7 +247,7 @@
this.player.swing(InteractionHand.MAIN_HAND);
return flag;
}
-@@ -1598,6 +_,11 @@
+@@ -1591,6 +_,11 @@
}
for (InteractionHand interactionhand : InteractionHand.values()) {
@@ -257,7 +259,7 @@
ItemStack itemstack = this.player.getItemInHand(interactionhand);
if (!itemstack.isItemEnabled(this.level.enabledFeatures())) {
return;
-@@ -1618,7 +_,7 @@
+@@ -1611,7 +_,7 @@
}
if (interactionresult instanceof InteractionResult.Success interactionresult$success2) {
@@ -266,7 +268,7 @@
this.player.swing(interactionhand);
}
-@@ -1630,7 +_,7 @@
+@@ -1623,7 +_,7 @@
int i = itemstack.getCount();
InteractionResult interactionresult1 = this.gameMode.useItemOn(this.player, interactionhand, blockhitresult);
if (interactionresult1 instanceof InteractionResult.Success interactionresult$success) {
@@ -275,7 +277,7 @@
this.player.swing(interactionhand);
if (!itemstack.isEmpty() && (itemstack.getCount() != i || this.gameMode.hasInfiniteItems())) {
this.gameRenderer.itemInHandRenderer.itemUsed(interactionhand);
-@@ -1646,6 +_,9 @@
+@@ -1639,6 +_,9 @@
}
}
@@ -285,7 +287,7 @@
if (!itemstack.isEmpty()
&& this.gameMode.useItem(this.player, interactionhand) instanceof InteractionResult.Success interactionresult$success1) {
if (interactionresult$success1.swingSource() == InteractionResult.SwingSource.CLIENT) {
-@@ -1666,6 +_,8 @@
+@@ -1659,6 +_,8 @@
public void tick() {
this.clientTickCount++;
@@ -294,7 +296,7 @@
if (this.level != null && !this.pause) {
this.level.tickRateManager().tick();
}
-@@ -1764,6 +_,7 @@
+@@ -1757,6 +_,7 @@
this.tutorial.tick();
@@ -302,7 +304,7 @@
try {
this.level.tick(() -> true);
} catch (Throwable throwable1) {
-@@ -1777,6 +_,7 @@
+@@ -1770,6 +_,7 @@
throw new ReportedException(crashreport1);
}
@@ -310,7 +312,7 @@
}
profilerfiller.popPush("animateTick");
-@@ -1801,6 +_,8 @@
+@@ -1794,6 +_,8 @@
profilerfiller.popPush("keyboard");
this.keyboardHandler.tick();
profilerfiller.pop();
@@ -319,7 +321,7 @@
}
private boolean isLevelRunningNormally() {
-@@ -2000,7 +_,8 @@
+@@ -1993,7 +_,8 @@
}
public void setLevel(ClientLevel p_91157_, ReceivingLevelScreen.Reason p_341652_) {
@@ -329,7 +331,7 @@
this.level = p_91157_;
this.updateLevelInEngines(p_91157_);
if (!this.isLocalServer) {
-@@ -2037,6 +_,7 @@
+@@ -2030,6 +_,7 @@
IntegratedServer integratedserver = this.singleplayerServer;
this.singleplayerServer = null;
this.gameRenderer.resetData();
@@ -337,7 +339,7 @@
this.gameMode = null;
this.narrator.clear();
this.clientLevelTeardownInProgress = true;
-@@ -2044,6 +_,7 @@
+@@ -2037,6 +_,7 @@
try {
this.updateScreenAndTick(p_320248_);
if (this.level != null) {
@@ -345,40 +347,11 @@
if (integratedserver != null) {
ProfilerFiller profilerfiller = Profiler.get();
profilerfiller.push("waitForServer");
-@@ -2204,6 +_,7 @@
+@@ -2197,6 +_,7 @@
private void pickBlock() {
if (this.hitResult != null && this.hitResult.getType() != HitResult.Type.MISS) {
+ if (net.neoforged.neoforge.client.ClientHooks.onClickInput(2, this.options.keyPickItem, InteractionHand.MAIN_HAND).isCanceled()) return;
- boolean flag = this.player.getAbilities().instabuild;
- BlockEntity blockentity = null;
- HitResult.Type hitresult$type = this.hitResult.getType();
-@@ -2216,7 +_,7 @@
- }
-
- Block block = blockstate.getBlock();
-- itemstack = block.getCloneItemStack(this.level, blockpos, blockstate);
-+ itemstack = blockstate.getCloneItemStack(this.hitResult, this.level, blockpos, this.player);
- if (itemstack.isEmpty()) {
- return;
- }
-@@ -2230,7 +_,7 @@
- }
-
- Entity entity = ((EntityHitResult)this.hitResult).getEntity();
-- itemstack = entity.getPickResult();
-+ itemstack = entity.getPickedResult(this.hitResult);
- if (itemstack == null) {
- return;
- }
-@@ -2757,6 +_,10 @@
-
- public void updateMaxMipLevel(int p_91313_) {
- this.modelManager.updateMaxMipLevel(p_91313_);
-+ }
-+
-+ public ItemColors getItemColors() {
-+ return this.itemColors;
- }
-
- public EntityModelSet getEntityModels() {
+ boolean flag = Screen.hasControlDown();
+ HitResult hitresult = this.hitResult;
+ Objects.requireNonNull(this.hitResult);
diff --git a/patches/net/minecraft/client/color/block/BlockColors.java.patch b/patches/net/minecraft/client/color/block/BlockColors.java.patch
index 32c2a2e76b..9305fe8d82 100644
--- a/patches/net/minecraft/client/color/block/BlockColors.java.patch
+++ b/patches/net/minecraft/client/color/block/BlockColors.java.patch
@@ -1,16 +1,16 @@
--- a/net/minecraft/client/color/block/BlockColors.java
+++ b/net/minecraft/client/color/block/BlockColors.java
-@@ -29,7 +_,8 @@
- @OnlyIn(Dist.CLIENT)
- public class BlockColors {
+@@ -30,7 +_,8 @@
private static final int DEFAULT = -1;
+ public static final int LILY_PAD_IN_WORLD = -14647248;
+ public static final int LILY_PAD_DEFAULT = -9321636;
- private final IdMapper blockColors = new IdMapper<>(32);
+ // Neo: Use the block instance directly as non-Vanilla block ids are not constant
+ private final java.util.Map blockColors = new java.util.IdentityHashMap<>();
private final Map>> coloringStates = Maps.newHashMap();
public static BlockColors createDefault() {
-@@ -94,11 +_,12 @@
+@@ -95,11 +_,12 @@
}, Blocks.MELON_STEM, Blocks.PUMPKIN_STEM);
blockcolors.addColoringState(StemBlock.AGE, Blocks.MELON_STEM, Blocks.PUMPKIN_STEM);
blockcolors.register((p_92596_, p_92597_, p_92598_, p_92599_) -> p_92597_ != null && p_92598_ != null ? -14647248 : -9321636, Blocks.LILY_PAD);
@@ -24,7 +24,7 @@
if (blockcolor != null) {
return blockcolor.getColor(p_92583_, null, null, 0);
} else {
-@@ -108,13 +_,15 @@
+@@ -109,13 +_,15 @@
}
public int getColor(BlockState p_92578_, @Nullable BlockAndTintGetter p_92579_, @Nullable BlockPos p_92580_, int p_92581_) {
diff --git a/patches/net/minecraft/client/color/item/ItemColors.java.patch b/patches/net/minecraft/client/color/item/ItemColors.java.patch
deleted file mode 100644
index afe4aac398..0000000000
--- a/patches/net/minecraft/client/color/item/ItemColors.java.patch
+++ /dev/null
@@ -1,35 +0,0 @@
---- a/net/minecraft/client/color/item/ItemColors.java
-+++ b/net/minecraft/client/color/item/ItemColors.java
-@@ -26,7 +_,8 @@
- @OnlyIn(Dist.CLIENT)
- public class ItemColors {
- private static final int DEFAULT = -1;
-- private final IdMapper itemColors = new IdMapper<>(32);
-+ // Neo: Use the item instance directly as non-Vanilla item ids are not constant
-+ private final java.util.Map- itemColors = new java.util.IdentityHashMap<>();
-
- public static ItemColors createDefault(BlockColors p_92684_) {
- ItemColors itemcolors = new ItemColors();
-@@ -101,17 +_,20 @@
- (p_359075_, p_359076_) -> p_359076_ == 0 ? -1 : ARGB.opaque(p_359075_.getOrDefault(DataComponents.MAP_COLOR, MapItemColor.DEFAULT).rgb()),
- Items.FILLED_MAP
- );
-+ net.neoforged.neoforge.client.ClientHooks.onItemColorsInit(itemcolors, p_92684_);
- return itemcolors;
- }
-
- public int getColor(ItemStack p_92677_, int p_92678_) {
-- ItemColor itemcolor = this.itemColors.byId(BuiltInRegistries.ITEM.getId(p_92677_.getItem()));
-+ ItemColor itemcolor = this.itemColors.get(p_92677_.getItem());
- return itemcolor == null ? -1 : itemcolor.getColor(p_92677_, p_92678_);
- }
-
-+ /** @deprecated Register via {@link net.neoforged.neoforge.client.event.RegisterColorHandlersEvent.Item} */
-+ @Deprecated
- public void register(ItemColor p_92690_, ItemLike... p_92691_) {
- for (ItemLike itemlike : p_92691_) {
-- this.itemColors.addMapping(p_92690_, Item.getId(itemlike.asItem()));
-+ this.itemColors.put(itemlike.asItem(), p_92690_);
- }
- }
- }
diff --git a/patches/net/minecraft/client/color/item/ItemTintSources.java.patch b/patches/net/minecraft/client/color/item/ItemTintSources.java.patch
new file mode 100644
index 0000000000..d240b059e1
--- /dev/null
+++ b/patches/net/minecraft/client/color/item/ItemTintSources.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/client/color/item/ItemTintSources.java
++++ b/net/minecraft/client/color/item/ItemTintSources.java
+@@ -21,5 +_,7 @@
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("potion"), Potion.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("map_color"), MapColor.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("team"), TeamColor.MAP_CODEC);
++
++ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.RegisterColorHandlersEvent.ItemTintSources(ID_MAPPER));
+ }
+ }
diff --git a/patches/net/minecraft/client/data/Main.java.patch b/patches/net/minecraft/client/data/Main.java.patch
new file mode 100644
index 0000000000..5aa2dc3ded
--- /dev/null
+++ b/patches/net/minecraft/client/data/Main.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/client/data/Main.java
++++ b/net/minecraft/client/data/Main.java
+@@ -30,16 +_,33 @@
+ OptionSpec optionspec1 = optionparser.accepts("client", "Include client generators");
+ OptionSpec optionspec2 = optionparser.accepts("all", "Include all generators");
+ OptionSpec optionspec3 = optionparser.accepts("output", "Output folder").withRequiredArg().defaultsTo("generated");
++ OptionSpec existing = optionparser.accepts("existing", "Existing resource packs that generated resources can reference").withRequiredArg();
++ OptionSpec existingMod = optionparser.accepts("existing-mod", "Existing mods that generated resources can reference the resource packs of").withRequiredArg();
++ OptionSpec gameDir = optionparser.accepts("gameDir").withRequiredArg().ofType(java.io.File.class).defaultsTo(new java.io.File(".")).required(); //Need by modlauncher, so lets just eat it
++ OptionSpec mod = optionparser.accepts("mod", "A modid to dump").withRequiredArg().withValuesSeparatedBy(",");
++ OptionSpec flat = optionparser.accepts("flat", "Do not append modid prefix to output directory when generating for multiple mods");
++ OptionSpec assetIndex = optionparser.accepts("assetIndex").withRequiredArg();
++ OptionSpec assetsDir = optionparser.accepts("assetsDir").withRequiredArg().ofType(java.io.File.class);
++ OptionSpec validateSpec = optionparser.accepts("validate", "Validate inputs");
+ OptionSet optionset = optionparser.parse(p_388033_);
+ if (!optionset.has(optionspec) && optionset.hasOptions()) {
+ Path path = Paths.get(optionspec3.value(optionset));
+ boolean flag = optionset.has(optionspec2);
+ boolean flag1 = flag || optionset.has(optionspec1);
+- Bootstrap.bootStrap();
+- ClientBootstrap.bootstrap();
+- DataGenerator datagenerator = new DataGenerator(path, SharedConstants.getCurrentVersion(), true);
++ java.util.Collection existingPacks = optionset.valuesOf(existing).stream().map(Paths::get).toList();
++ java.util.Set existingMods = new java.util.HashSet<>(optionset.valuesOf(existingMod));
++ java.util.Set mods = new java.util.HashSet<>(optionset.valuesOf(mod));
++ boolean isFlat = mods.isEmpty() || optionset.has(flat);
++ boolean validate = optionset.has(validateSpec);
++ DataGenerator datagenerator = new DataGenerator(isFlat ? path : path.resolve("minecraft"), SharedConstants.getCurrentVersion(), true);
++ if (mods.contains("minecraft") || mods.isEmpty()) {
+ addClientProviders(datagenerator, flag1);
+- datagenerator.run();
++ }
++ net.neoforged.neoforge.data.loading.DatagenModLoader.begin(mods, path, java.util.List.of(), existingPacks, existingMods, false, false, validate, isFlat, optionset.valueOf(assetIndex), optionset.valueOf(assetsDir), () -> {
++ ClientBootstrap.bootstrap();
++ net.neoforged.neoforge.client.ClientHooks.registerSpriteSourceTypes();
++ net.neoforged.neoforge.client.entity.animation.json.AnimationTypeManager.init();
++ }, net.neoforged.neoforge.data.event.GatherDataEvent.Client::new, datagenerator);
+ } else {
+ optionparser.printHelpOn(System.out);
+ }
diff --git a/patches/net/minecraft/client/gui/Font.java.patch b/patches/net/minecraft/client/gui/Font.java.patch
index 92bc34f85a..f147a8bcdc 100644
--- a/patches/net/minecraft/client/gui/Font.java.patch
+++ b/patches/net/minecraft/client/gui/Font.java.patch
@@ -1,20 +1,56 @@
--- a/net/minecraft/client/gui/Font.java
+++ b/net/minecraft/client/gui/Font.java
-@@ -33,7 +_,7 @@
- import org.joml.Vector3f;
+@@ -32,7 +_,7 @@
+ import org.joml.Matrix4f;
@OnlyIn(Dist.CLIENT)
-public class Font {
+public class Font implements net.neoforged.neoforge.client.extensions.IFontExtension {
private static final float EFFECT_DEPTH = 0.01F;
- private static final Vector3f SHADOW_OFFSET = new Vector3f(0.0F, 0.0F, 0.03F);
- public static final int ALPHA_CUTOFF = 8;
-@@ -309,6 +_,8 @@
- public StringSplitter getSplitter() {
+ public static final float SHADOW_DEPTH = 0.03F;
+ public static final int NO_SHADOW = 0;
+@@ -42,6 +_,8 @@
+ private final Function fonts;
+ final boolean filterFishyGlyphs;
+ private final StringSplitter splitter;
++ /** Neo: enables linear filtering on text */
++ public boolean enableTextTextureLinearFiltering = false;
+
+ public Font(Function p_243253_, boolean p_243245_) {
+ this.fonts = p_243253_;
+@@ -298,6 +_,8 @@
return this.splitter;
}
-+
-+ @Override public Font self() { return this; }
++ @Override public Font self() { return this; }
++
@OnlyIn(Dist.CLIENT)
public static enum DisplayMode {
+ NORMAL,
+@@ -403,7 +_,7 @@
+ p_381032_ - 1.0F, this.y + 9.0F, this.x, this.y - 1.0F, this.getUnderTextEffectDepth(), this.backgroundColor
+ );
+ bakedglyph = Font.this.getFontSet(Style.DEFAULT_FONT).whiteGlyph();
+- VertexConsumer vertexconsumer = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode));
++ VertexConsumer vertexconsumer = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode, Font.this.enableTextTextureLinearFiltering));
+ bakedglyph.renderEffect(bakedglyph$effect, this.pose, vertexconsumer, this.packedLightCoords);
+ }
+
+@@ -413,7 +_,7 @@
+ bakedglyph = Font.this.getFontSet(Style.DEFAULT_FONT).whiteGlyph();
+ }
+
+- VertexConsumer vertexconsumer1 = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode));
++ VertexConsumer vertexconsumer1 = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode, Font.this.enableTextTextureLinearFiltering));
+
+ for (BakedGlyph.Effect bakedglyph$effect1 : this.effects) {
+ bakedglyph.renderEffect(bakedglyph$effect1, this.pose, vertexconsumer1, this.packedLightCoords);
+@@ -447,7 +_,7 @@
+ void renderCharacters() {
+ for (BakedGlyph.GlyphInstance bakedglyph$glyphinstance : this.glyphInstances) {
+ BakedGlyph bakedglyph = bakedglyph$glyphinstance.glyph();
+- VertexConsumer vertexconsumer = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode));
++ VertexConsumer vertexconsumer = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode, Font.this.enableTextTextureLinearFiltering));
+ bakedglyph.renderChar(bakedglyph$glyphinstance, this.pose, vertexconsumer, this.packedLightCoords);
+ }
+ }
diff --git a/patches/net/minecraft/client/gui/Gui.java.patch b/patches/net/minecraft/client/gui/Gui.java.patch
index 57ea34601f..333bdeda69 100644
--- a/patches/net/minecraft/client/gui/Gui.java.patch
+++ b/patches/net/minecraft/client/gui/Gui.java.patch
@@ -386,7 +386,7 @@
this.toolHighlightTimer = (int)(40.0 * this.minecraft.options.notificationDisplayTime().get());
} else if (this.toolHighlightTimer > 0) {
this.toolHighlightTimer--;
-@@ -1319,8 +_,17 @@
+@@ -1321,8 +_,17 @@
}
}
@@ -405,7 +405,7 @@
CONTAINER(
ResourceLocation.withDefaultNamespace("hud/heart/container"),
ResourceLocation.withDefaultNamespace("hud/heart/container_blinking"),
-@@ -1436,8 +_,13 @@
+@@ -1438,8 +_,13 @@
} else {
gui$hearttype = NORMAL;
}
diff --git a/patches/net/minecraft/client/gui/GuiGraphics.java.patch b/patches/net/minecraft/client/gui/GuiGraphics.java.patch
index caca78e363..472cc1b836 100644
--- a/patches/net/minecraft/client/gui/GuiGraphics.java.patch
+++ b/patches/net/minecraft/client/gui/GuiGraphics.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/GuiGraphics.java
+++ b/net/minecraft/client/gui/GuiGraphics.java
-@@ -53,7 +_,7 @@
+@@ -52,7 +_,7 @@
import org.joml.Vector2ic;
@OnlyIn(Dist.CLIENT)
@@ -9,7 +9,7 @@
public static final float MAX_GUI_Z = 10000.0F;
public static final float MIN_GUI_Z = -10000.0F;
private static final int EXTRA_SPACE_AFTER_FIRST_TOOLTIP_LINE = 2;
-@@ -227,6 +_,11 @@
+@@ -229,6 +_,11 @@
}
public int drawString(Font p_283343_, @Nullable String p_281896_, int p_283569_, int p_283418_, int p_281560_, boolean p_282130_) {
@@ -21,7 +21,7 @@
return p_281896_ == null
? 0
: p_283343_.drawInBatch(
-@@ -248,6 +_,11 @@
+@@ -250,6 +_,11 @@
}
public int drawString(Font p_282636_, FormattedCharSequence p_281596_, int p_281586_, int p_282816_, int p_281743_, boolean p_282394_) {
@@ -33,7 +33,7 @@
return p_282636_.drawInBatch(
p_281596_,
(float)p_281586_,
-@@ -870,10 +_,15 @@
+@@ -859,10 +_,15 @@
this.renderItemCount(p_282005_, p_283349_, p_282641_, p_282146_, p_282803_);
this.renderItemCooldown(p_283349_, p_282641_, p_282146_);
this.pose.popPose();
@@ -49,7 +49,7 @@
this.renderTooltip(
p_282308_,
Screen.getTooltipFromItem(this.minecraft, p_282781_),
-@@ -882,6 +_,17 @@
+@@ -871,6 +_,17 @@
p_282292_,
p_282781_.get(DataComponents.TOOLTIP_STYLE)
);
@@ -67,7 +67,7 @@
}
public void renderTooltip(Font p_283128_, List p_282716_, Optional p_281682_, int p_283678_, int p_281696_) {
-@@ -891,11 +_,7 @@
+@@ -880,11 +_,7 @@
public void renderTooltip(
Font p_371715_, List p_371741_, Optional p_371604_, int p_371500_, int p_371755_, @Nullable ResourceLocation p_371766_
) {
@@ -80,7 +80,7 @@
this.renderTooltipInternal(p_371715_, list, p_371500_, p_371755_, DefaultTooltipPositioner.INSTANCE, p_371766_);
}
-@@ -908,13 +_,14 @@
+@@ -897,13 +_,14 @@
}
public void renderComponentTooltip(Font p_282739_, List p_281832_, int p_282191_, int p_282446_) {
@@ -97,7 +97,7 @@
p_371314_,
p_371389_,
DefaultTooltipPositioner.INSTANCE,
-@@ -922,6 +_,28 @@
+@@ -911,6 +_,28 @@
);
}
@@ -126,7 +126,7 @@
public void renderTooltip(Font p_282192_, List extends FormattedCharSequence> p_282297_, int p_281680_, int p_283325_) {
this.renderTooltip(p_282192_, p_282297_, p_281680_, p_283325_, null);
}
-@@ -954,41 +_,45 @@
+@@ -943,41 +_,45 @@
@Nullable ResourceLocation p_371327_
) {
if (!p_282615_.isEmpty()) {
diff --git a/patches/net/minecraft/client/gui/components/AbstractWidget.java.patch b/patches/net/minecraft/client/gui/components/AbstractWidget.java.patch
index 256cef46f4..6022c53dcf 100644
--- a/patches/net/minecraft/client/gui/components/AbstractWidget.java.patch
+++ b/patches/net/minecraft/client/gui/components/AbstractWidget.java.patch
@@ -19,7 +19,7 @@
}
@@ -139,7 +_,7 @@
- boolean flag = this.clicked(p_93641_, p_93642_);
+ boolean flag = this.isMouseOver(p_93641_, p_93642_);
if (flag) {
this.playDownSound(Minecraft.getInstance().getSoundManager());
- this.onClick(p_93641_, p_93642_);
@@ -27,7 +27,7 @@
return true;
}
}
-@@ -257,6 +_,19 @@
+@@ -248,6 +_,19 @@
@Override
public void setFocused(boolean p_93693_) {
this.focused = p_93693_;
diff --git a/patches/net/minecraft/client/gui/font/GlyphRenderTypes.java.patch b/patches/net/minecraft/client/gui/font/GlyphRenderTypes.java.patch
new file mode 100644
index 0000000000..41aedd3241
--- /dev/null
+++ b/patches/net/minecraft/client/gui/font/GlyphRenderTypes.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/client/gui/font/GlyphRenderTypes.java
++++ b/net/minecraft/client/gui/font/GlyphRenderTypes.java
+@@ -7,22 +_,42 @@
+ import net.neoforged.api.distmarker.OnlyIn;
+
+ @OnlyIn(Dist.CLIENT)
+-public record GlyphRenderTypes(RenderType normal, RenderType seeThrough, RenderType polygonOffset) {
++public record GlyphRenderTypes(RenderType normal, RenderType seeThrough, RenderType polygonOffset, RenderType normalBlur, RenderType seeThroughBlur, RenderType polygonOffsetBlur) {
++ /** @deprecated Neo: Use {@link GlyphRenderTypes(RenderType,RenderType,RenderType,RenderType,RenderType,RenderType)} instead */
++ @Deprecated
++ public GlyphRenderTypes(RenderType normal, RenderType seeThrough, RenderType polygonOffset) {
++ this(normal, seeThrough, polygonOffset, normal, seeThrough, polygonOffset);
++ }
++
+ public static GlyphRenderTypes createForIntensityTexture(ResourceLocation p_285411_) {
+ return new GlyphRenderTypes(
+ RenderType.textIntensity(p_285411_), RenderType.textIntensitySeeThrough(p_285411_), RenderType.textIntensityPolygonOffset(p_285411_)
++ , net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextIntensityFiltered(p_285411_),
++ net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextIntensitySeeThroughFiltered(p_285411_),
++ net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextIntensityPolygonOffsetFiltered(p_285411_)
+ );
+ }
+
+ public static GlyphRenderTypes createForColorTexture(ResourceLocation p_285486_) {
+- return new GlyphRenderTypes(RenderType.text(p_285486_), RenderType.textSeeThrough(p_285486_), RenderType.textPolygonOffset(p_285486_));
++ return new GlyphRenderTypes(RenderType.text(p_285486_), RenderType.textSeeThrough(p_285486_), RenderType.textPolygonOffset(p_285486_),
++ net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextFiltered(p_285486_),
++ net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextSeeThroughFiltered(p_285486_),
++ net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextPolygonOffsetFiltered(p_285486_)
++ );
+ }
+
+ public RenderType select(Font.DisplayMode p_285259_) {
++ return this.select(p_285259_, false);
++ }
++
++ /**
++ * Neo: returns the {@link RenderType} to use for the given {@link Font.DisplayMode} and blur setting
++ */
++ public RenderType select(Font.DisplayMode p_285259_, boolean blur) {
+ return switch (p_285259_) {
+- case NORMAL -> this.normal;
+- case SEE_THROUGH -> this.seeThrough;
+- case POLYGON_OFFSET -> this.polygonOffset;
++ case NORMAL -> blur ? this.normalBlur : this.normal;
++ case SEE_THROUGH -> blur ? this.seeThroughBlur : this.seeThrough;
++ case POLYGON_OFFSET -> blur ? this.polygonOffsetBlur : this.polygonOffset;
+ };
+ }
+ }
diff --git a/patches/net/minecraft/client/gui/font/glyphs/BakedGlyph.java.patch b/patches/net/minecraft/client/gui/font/glyphs/BakedGlyph.java.patch
new file mode 100644
index 0000000000..207270ffd3
--- /dev/null
+++ b/patches/net/minecraft/client/gui/font/glyphs/BakedGlyph.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/client/gui/font/glyphs/BakedGlyph.java
++++ b/net/minecraft/client/gui/font/glyphs/BakedGlyph.java
+@@ -139,6 +_,15 @@
+ .setLight(p_382874_);
+ }
+
++ /**
++ * Neo: returns the {@link RenderType} to use for the given {@link Font.DisplayMode} and blur setting
++ */
++ public RenderType renderType(Font.DisplayMode p_181388_, boolean blur) {
++ return this.renderTypes.select(p_181388_, blur);
++ }
++
++ /** @deprecated Neo: Use {@link #renderType(Font.DisplayMode, boolean)} instead */
++ @Deprecated
+ public RenderType renderType(Font.DisplayMode p_181388_) {
+ return this.renderTypes.select(p_181388_);
+ }
diff --git a/patches/net/minecraft/client/gui/screens/LoadingOverlay.java.patch b/patches/net/minecraft/client/gui/screens/LoadingOverlay.java.patch
index b607643c50..d0c1f86d70 100644
--- a/patches/net/minecraft/client/gui/screens/LoadingOverlay.java.patch
+++ b/patches/net/minecraft/client/gui/screens/LoadingOverlay.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/LoadingOverlay.java
+++ b/net/minecraft/client/gui/screens/LoadingOverlay.java
-@@ -123,6 +_,7 @@
+@@ -122,6 +_,7 @@
}
if (this.fadeOutStart == -1L && this.reload.isDone() && (!this.fadeIn || f1 >= 2.0F)) {
@@ -8,7 +8,7 @@
try {
this.reload.checkExceptions();
this.onFinish.accept(Optional.empty());
-@@ -130,7 +_,6 @@
+@@ -129,7 +_,6 @@
this.onFinish.accept(Optional.of(throwable));
}
diff --git a/patches/net/minecraft/client/gui/screens/Screen.java.patch b/patches/net/minecraft/client/gui/screens/Screen.java.patch
index e7d7751d22..8f599df40b 100644
--- a/patches/net/minecraft/client/gui/screens/Screen.java.patch
+++ b/patches/net/minecraft/client/gui/screens/Screen.java.patch
@@ -69,7 +69,7 @@
private void scheduleNarration(long p_169381_, boolean p_169382_) {
this.nextNarrationTime = Util.getMillis() + p_169381_;
if (p_169382_) {
-@@ -642,5 +_,13 @@
+@@ -643,5 +_,13 @@
this.index = p_169425_;
this.priority = p_169426_;
}
diff --git a/patches/net/minecraft/client/gui/screens/TitleScreen.java.patch b/patches/net/minecraft/client/gui/screens/TitleScreen.java.patch
index 2038b23682..7ded730c48 100644
--- a/patches/net/minecraft/client/gui/screens/TitleScreen.java.patch
+++ b/patches/net/minecraft/client/gui/screens/TitleScreen.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/TitleScreen.java
+++ b/net/minecraft/client/gui/screens/TitleScreen.java
-@@ -114,11 +_,17 @@
+@@ -110,11 +_,17 @@
int i = this.font.width(COPYRIGHT_TEXT);
int j = this.width - i - 2;
int k = 24;
@@ -19,7 +19,7 @@
}
l = this.createTestWorldButton(l, 24);
-@@ -160,7 +_,7 @@
+@@ -156,7 +_,7 @@
if (SharedConstants.IS_RUNNING_IN_IDE) {
this.addRenderableWidget(
Button.builder(Component.literal("Create Test World"), p_372504_ -> CreateWorldScreen.testWorld(this.minecraft, this))
@@ -28,7 +28,7 @@
.build()
);
}
-@@ -304,6 +_,7 @@
+@@ -300,6 +_,7 @@
if ((i & -67108864) != 0) {
super.render(p_282860_, p_281753_, p_283539_, p_282628_);
this.logoRenderer.renderLogo(p_282860_, this.width, f);
@@ -36,7 +36,7 @@
if (this.splash != null && !this.minecraft.options.hideSplashTexts().get()) {
this.splash.render(p_282860_, this.width, this.font, i);
}
-@@ -319,7 +_,13 @@
+@@ -315,7 +_,13 @@
s = s + I18n.get("menu.modded");
}
diff --git a/patches/net/minecraft/client/gui/screens/inventory/AbstractContainerScreen.java.patch b/patches/net/minecraft/client/gui/screens/inventory/AbstractContainerScreen.java.patch
index be4ec4baf1..872e89fb06 100644
--- a/patches/net/minecraft/client/gui/screens/inventory/AbstractContainerScreen.java.patch
+++ b/patches/net/minecraft/client/gui/screens/inventory/AbstractContainerScreen.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/inventory/AbstractContainerScreen.java
+++ b/net/minecraft/client/gui/screens/inventory/AbstractContainerScreen.java
-@@ -107,7 +_,12 @@
+@@ -105,7 +_,12 @@
public void render(GuiGraphics p_283479_, int p_283661_, int p_281248_, float p_281886_) {
int i = this.leftPos;
int j = this.topPos;
@@ -14,7 +14,7 @@
p_283479_.pose().pushPose();
p_283479_.pose().translate((float)i, (float)j, 0.0F);
Slot slot = this.hoveredSlot;
-@@ -120,6 +_,7 @@
+@@ -118,6 +_,7 @@
}
this.renderLabels(p_283479_, p_283661_, p_281248_);
@@ -22,7 +22,7 @@
ItemStack itemstack = this.draggingItem.isEmpty() ? this.menu.getCarried() : this.draggingItem;
if (!itemstack.isEmpty()) {
int k = 8;
-@@ -202,6 +_,7 @@
+@@ -200,6 +_,7 @@
this.font,
this.getTooltipFromContainerItem(itemstack),
itemstack.getTooltipImage(),
@@ -30,7 +30,7 @@
p_282171_,
p_281909_,
itemstack.get(DataComponents.TOOLTIP_STYLE)
-@@ -222,7 +_,8 @@
+@@ -220,7 +_,8 @@
p_282567_.pose().pushPose();
p_282567_.pose().translate(0.0F, 0.0F, 232.0F);
p_282567_.renderItem(p_281330_, p_281772_, p_281689_);
@@ -40,7 +40,7 @@
p_282567_.pose().popPose();
}
-@@ -281,6 +_,14 @@
+@@ -278,6 +_,14 @@
p_281607_.fill(i, j, i + 16, j + 16, -2130706433);
}
@@ -55,7 +55,7 @@
int j1 = p_282613_.x + p_282613_.y * this.imageWidth;
if (p_282613_.isFake()) {
p_281607_.renderFakeItem(itemstack, i, j, j1);
-@@ -289,9 +_,6 @@
+@@ -286,9 +_,6 @@
}
p_281607_.renderItemDecorations(this.font, itemstack, i, j, s);
@@ -65,7 +65,7 @@
}
private void recalculateQuickCraftRemaining() {
-@@ -329,7 +_,8 @@
+@@ -326,7 +_,8 @@
if (super.mouseClicked(p_97748_, p_97749_, p_97750_)) {
return true;
} else {
@@ -75,7 +75,7 @@
Slot slot = this.getHoveredSlot(p_97748_, p_97749_);
long i = Util.getMillis();
this.doubleclick = this.lastClickSlot == slot && i - this.lastClickTime < 250L && this.lastClickButton == p_97750_;
-@@ -340,6 +_,7 @@
+@@ -337,6 +_,7 @@
int j = this.leftPos;
int k = this.topPos;
boolean flag1 = this.hasClickedOutside(p_97748_, p_97749_, j, k, p_97750_);
@@ -83,7 +83,7 @@
int l = -1;
if (slot != null) {
l = slot.index;
-@@ -365,7 +_,7 @@
+@@ -362,7 +_,7 @@
}
} else if (!this.isQuickCrafting) {
if (this.menu.getCarried().isEmpty()) {
@@ -92,7 +92,7 @@
this.slotClicked(slot, l, p_97750_, ClickType.CLONE);
} else {
boolean flag2 = l != -999
-@@ -393,7 +_,7 @@
+@@ -390,7 +_,7 @@
this.quickCraftingType = 0;
} else if (p_97750_ == 1) {
this.quickCraftingType = 1;
@@ -101,7 +101,7 @@
this.quickCraftingType = 2;
}
}
-@@ -472,10 +_,13 @@
+@@ -469,10 +_,13 @@
@Override
public boolean mouseReleased(double p_97812_, double p_97813_, int p_97814_) {
@@ -115,7 +115,7 @@
int k = -1;
if (slot != null) {
k = slot.index;
-@@ -492,7 +_,7 @@
+@@ -489,7 +_,7 @@
if (slot2 != null
&& slot2.mayPickup(this.minecraft.player)
&& slot2.hasItem()
@@ -124,7 +124,7 @@
&& AbstractContainerMenu.canItemQuickReplace(slot2, this.lastQuickMoved, true)) {
this.slotClicked(slot2, slot2.index, p_97814_, ClickType.QUICK_MOVE);
}
-@@ -556,7 +_,7 @@
+@@ -553,7 +_,7 @@
this.slotClicked(null, -999, AbstractContainerMenu.getQuickcraftMask(2, this.quickCraftingType), ClickType.QUICK_CRAFT);
} else if (!this.menu.getCarried().isEmpty()) {
@@ -133,7 +133,7 @@
this.slotClicked(slot, k, p_97814_, ClickType.CLONE);
} else {
boolean flag1 = k != -999
-@@ -636,34 +_,39 @@
+@@ -633,34 +_,39 @@
@Override
public boolean keyPressed(int p_97765_, int p_97766_, int p_97767_) {
@@ -180,7 +180,7 @@
this.slotClicked(this.hoveredSlot, this.hoveredSlot.index, i, ClickType.SWAP);
return true;
}
-@@ -701,6 +_,18 @@
+@@ -698,6 +_,18 @@
@Override
public T getMenu() {
return this.menu;
diff --git a/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch b/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch
index 37ccae8839..309b303ed2 100644
--- a/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch
+++ b/patches/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java
+++ b/net/minecraft/client/gui/screens/inventory/CreativeModeInventoryScreen.java
-@@ -115,6 +_,8 @@
+@@ -114,6 +_,8 @@
private final Set> visibleTags = new HashSet<>();
private final boolean displayOperatorCreativeTab;
private final EffectsInInventory effects;
@@ -9,7 +9,7 @@
public CreativeModeInventoryScreen(LocalPlayer p_346290_, FeatureFlagSet p_260074_, boolean p_259569_) {
super(new CreativeModeInventoryScreen.ItemPickerMenu(p_346290_), p_346290_.getInventory(), CommonComponents.EMPTY);
-@@ -151,9 +_,11 @@
+@@ -150,9 +_,11 @@
return false;
} else {
if (p_345591_ != null) {
@@ -24,7 +24,7 @@
}
return true;
-@@ -163,7 +_,7 @@
+@@ -162,7 +_,7 @@
private void refreshCurrentTabContents(Collection p_261591_) {
int i = this.menu.getRowIndexForScroll(this.scrollOffs);
this.menu.items.clear();
@@ -33,14 +33,14 @@
this.refreshSearchResults();
} else {
this.menu.items.addAll(p_261591_);
-@@ -337,6 +_,34 @@
+@@ -336,6 +_,34 @@
protected void init() {
if (this.minecraft.gameMode.hasInfiniteItems()) {
super.init();
+ this.pages.clear();
+ int tabIndex = 0;
+ List currentPage = new java.util.ArrayList<>();
-+ for (CreativeModeTab sortedCreativeModeTab : net.neoforged.neoforge.common.CreativeModeTabRegistry.getSortedCreativeModeTabs()) {
++ for (CreativeModeTab sortedCreativeModeTab : net.neoforged.neoforge.common.CreativeModeTabRegistry.getSortedCreativeModeTabs().stream().filter(CreativeModeTab::hasAnyItems).toList()) {
+ currentPage.add(sortedCreativeModeTab);
+ tabIndex++;
+ if (tabIndex == 10) {
@@ -68,7 +68,7 @@
this.searchBox = new EditBox(this.font, this.leftPos + 82, this.topPos + 6, 80, 9, Component.translatable("itemGroup.search"));
this.searchBox.setMaxLength(50);
this.searchBox.setBordered(false);
-@@ -383,7 +_,7 @@
+@@ -382,7 +_,7 @@
public boolean charTyped(char p_98521_, int p_98522_) {
if (this.ignoreTextInput) {
return false;
@@ -77,7 +77,7 @@
return false;
} else {
String s = this.searchBox.getValue();
-@@ -402,7 +_,7 @@
+@@ -401,7 +_,7 @@
@Override
public boolean keyPressed(int p_98547_, int p_98548_, int p_98549_) {
this.ignoreTextInput = false;
@@ -86,7 +86,7 @@
if (this.minecraft.options.keyChat.matches(p_98547_, p_98548_)) {
this.ignoreTextInput = true;
this.selectTab(CreativeModeTabs.searchTab());
-@@ -438,6 +_,7 @@
+@@ -437,6 +_,7 @@
}
private void refreshSearchResults() {
@@ -94,7 +94,7 @@
this.menu.items.clear();
this.visibleTags.clear();
String s = this.searchBox.getValue();
-@@ -450,10 +_,10 @@
+@@ -449,10 +_,10 @@
SearchTree searchtree;
if (s.startsWith("#")) {
s = s.substring(1);
@@ -107,7 +107,7 @@
}
this.menu.items.addAll(searchtree.search(s.toLowerCase(Locale.ROOT)));
-@@ -481,7 +_,8 @@
+@@ -480,7 +_,8 @@
@Override
protected void renderLabels(GuiGraphics p_283168_, int p_281774_, int p_281466_) {
if (selectedTab.showTitle()) {
@@ -117,7 +117,7 @@
}
}
-@@ -491,7 +_,7 @@
+@@ -490,7 +_,7 @@
double d0 = p_98531_ - (double)this.leftPos;
double d1 = p_98532_ - (double)this.topPos;
@@ -126,7 +126,7 @@
if (this.checkTabClicked(creativemodetab, d0, d1)) {
return true;
}
-@@ -513,7 +_,7 @@
+@@ -512,7 +_,7 @@
double d1 = p_98623_ - (double)this.topPos;
this.scrolling = false;
@@ -135,7 +135,7 @@
if (this.checkTabClicked(creativemodetab, d0, d1)) {
this.selectTab(creativemodetab);
return true;
-@@ -531,6 +_,7 @@
+@@ -530,6 +_,7 @@
private void selectTab(CreativeModeTab p_98561_) {
CreativeModeTab creativemodetab = selectedTab;
selectedTab = p_98561_;
@@ -143,7 +143,7 @@
this.quickCraftSlots.clear();
this.menu.items.clear();
this.clearDraggingState();
-@@ -607,13 +_,15 @@
+@@ -606,13 +_,15 @@
this.originalSlots = null;
}
@@ -160,7 +160,7 @@
this.refreshSearchResults();
} else {
-@@ -679,18 +_,27 @@
+@@ -678,18 +_,27 @@
super.render(p_283000_, p_281317_, p_282770_, p_281295_);
this.effects.render(p_283000_, p_281317_, p_282770_, p_281295_);
@@ -194,7 +194,7 @@
this.renderTooltip(p_283000_, p_281317_, p_282770_);
}
-@@ -703,10 +_,10 @@
+@@ -702,10 +_,10 @@
public List getTooltipFromContainerItem(ItemStack p_281769_) {
boolean flag = this.hoveredSlot != null && this.hoveredSlot instanceof CreativeModeInventoryScreen.CustomCreativeSlot;
boolean flag1 = selectedTab.getType() == CreativeModeTab.Type.CATEGORY;
@@ -207,7 +207,7 @@
if (flag1 && flag) {
return list;
} else {
-@@ -722,7 +_,7 @@
+@@ -721,7 +_,7 @@
int i = 1;
for (CreativeModeTab creativemodetab : CreativeModeTabs.tabs()) {
@@ -216,7 +216,7 @@
list1.add(i++, creativemodetab.getDisplayName().copy().withStyle(ChatFormatting.BLUE));
}
}
-@@ -733,7 +_,7 @@
+@@ -732,7 +_,7 @@
@Override
protected void renderBg(GuiGraphics p_282663_, float p_282504_, int p_282089_, int p_282249_) {
@@ -225,7 +225,7 @@
if (creativemodetab != selectedTab) {
this.renderTabButton(p_282663_, creativemodetab);
}
-@@ -747,10 +_,11 @@
+@@ -746,10 +_,11 @@
int k = this.topPos + 18;
int i = k + 112;
if (selectedTab.canScroll()) {
@@ -238,7 +238,7 @@
this.renderTabButton(p_282663_, selectedTab);
if (selectedTab.getType() == CreativeModeTab.Type.INVENTORY) {
InventoryScreen.renderEntityInInventoryFollowsMouse(
-@@ -769,7 +_,7 @@
+@@ -768,7 +_,7 @@
}
private int getTabX(CreativeModeTab p_260136_) {
@@ -247,7 +247,7 @@
int j = 27;
int k = 27 * i;
if (p_260136_.isAlignedRight()) {
-@@ -781,7 +_,7 @@
+@@ -780,7 +_,7 @@
private int getTabY(CreativeModeTab p_260181_) {
int i = 0;
@@ -256,7 +256,7 @@
i -= 32;
} else {
i += this.imageHeight;
-@@ -809,8 +_,8 @@
+@@ -808,8 +_,8 @@
protected void renderTabButton(GuiGraphics p_283590_, CreativeModeTab p_283489_) {
boolean flag = p_283489_ == selectedTab;
@@ -267,7 +267,7 @@
int j = this.leftPos + this.getTabX(p_283489_);
int k = this.topPos - (flag1 ? 28 : -(this.imageHeight - 4));
ResourceLocation[] aresourcelocation;
-@@ -820,6 +_,7 @@
+@@ -819,6 +_,7 @@
aresourcelocation = flag ? SELECTED_BOTTOM_TABS : UNSELECTED_BOTTOM_TABS;
}
@@ -275,7 +275,7 @@
p_283590_.blitSprite(RenderType::guiTextured, aresourcelocation[Mth.clamp(i, 0, aresourcelocation.length)], j, k, 26, 32);
p_283590_.pose().pushPose();
p_283590_.pose().translate(0.0F, 0.0F, 100.0F);
-@@ -861,6 +_,14 @@
+@@ -860,6 +_,14 @@
}
}
@@ -290,7 +290,7 @@
@OnlyIn(Dist.CLIENT)
static class CustomCreativeSlot extends Slot {
public CustomCreativeSlot(Container p_98633_, int p_98634_, int p_98635_, int p_98636_) {
-@@ -1042,6 +_,22 @@
+@@ -1041,6 +_,22 @@
@Override
public boolean mayPickup(Player p_98665_) {
return this.target.mayPickup(p_98665_);
@@ -307,8 +307,8 @@
+ }
+
+ @Override
-+ public Slot setBackground(ResourceLocation atlas, ResourceLocation sprite) {
-+ this.target.setBackground(atlas, sprite);
++ public Slot setBackground(ResourceLocation sprite) {
++ this.target.setBackground(sprite);
+ return this;
}
}
diff --git a/patches/net/minecraft/client/gui/screens/inventory/EnchantmentScreen.java.patch b/patches/net/minecraft/client/gui/screens/inventory/EnchantmentScreen.java.patch
index 66f9e706cc..0369823bed 100644
--- a/patches/net/minecraft/client/gui/screens/inventory/EnchantmentScreen.java.patch
+++ b/patches/net/minecraft/client/gui/screens/inventory/EnchantmentScreen.java.patch
@@ -8,7 +8,7 @@
+ if (((k < l + 1 || this.minecraft.player.experienceLevel < k1) && !this.minecraft.player.getAbilities().instabuild) || this.menu.enchantClue[l] == -1) { // Forge: render buttons as disabled when enchantable but enchantability not met on lower levels
p_282430_.blitSprite(RenderType::guiTextured, ENCHANTMENT_SLOT_DISABLED_SPRITE, i1, j + 14 + 19 * l, 108, 19);
p_282430_.blitSprite(RenderType::guiTextured, DISABLED_LEVEL_SPRITES[l], i1 + 1, j + 15 + 19 * l, 16, 16);
- p_282430_.drawWordWrap(this.font, formattedtext, j1, j + 16 + 19 * l, l1, (i2 & 16711422) >> 1);
+ p_282430_.drawWordWrap(this.font, formattedtext, j1, j + 16 + 19 * l, l1, (i2 & 16711422) >> 1, false);
@@ -179,13 +_,16 @@
.registryAccess()
.lookupOrThrow(Registries.ENCHANTMENT)
diff --git a/patches/net/minecraft/client/gui/screens/options/controls/KeyBindsList.java.patch b/patches/net/minecraft/client/gui/screens/options/controls/KeyBindsList.java.patch
index 789a7732ae..844e9dadbd 100644
--- a/patches/net/minecraft/client/gui/screens/options/controls/KeyBindsList.java.patch
+++ b/patches/net/minecraft/client/gui/screens/options/controls/KeyBindsList.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/options/controls/KeyBindsList.java
+++ b/net/minecraft/client/gui/screens/options/controls/KeyBindsList.java
-@@ -158,6 +_,7 @@
+@@ -156,6 +_,7 @@
)
.build();
this.resetButton = Button.builder(RESET_BUTTON_TITLE, p_359096_ -> {
@@ -8,7 +8,7 @@
p_345998_.setKey(p_345998_.getDefaultKey());
KeyBindsList.this.resetMappingAndUpdateButtons();
}).bounds(0, 0, 50, 20).createNarration(p_344899_ -> Component.translatable("narrator.controls.reset", p_345196_)).build();
-@@ -210,7 +_,7 @@
+@@ -208,7 +_,7 @@
MutableComponent mutablecomponent = Component.empty();
if (!this.key.isUnbound()) {
for (KeyMapping keymapping : KeyBindsList.this.minecraft.options.keyMappings) {
diff --git a/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch b/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch
index 55fa739634..1e9418a335 100644
--- a/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch
+++ b/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java
+++ b/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java
-@@ -169,6 +_,7 @@
+@@ -168,6 +_,7 @@
) {
queueLoadScreen(p_372818_, PREPARING_WORLD_DATA);
PackRepository packrepository = new PackRepository(new ServerPacksSource(p_372818_.directoryValidator()));
@@ -8,7 +8,7 @@
WorldLoader.InitConfig worldloader$initconfig = createDefaultLoadConfig(packrepository, WorldDataConfiguration.DEFAULT);
CompletableFuture completablefuture = WorldLoader.load(
worldloader$initconfig,
-@@ -307,6 +_,10 @@
+@@ -306,6 +_,10 @@
SystemToast.onPackCopyFailure(this.minecraft, s);
return false;
} else {
@@ -19,7 +19,7 @@
this.minecraft
.createWorldOpenFlows()
.createLevelFromExistingSettings(optional.get(), worldcreationcontext.dataPackResources(), p_249152_, p_374211_);
-@@ -491,7 +_,7 @@
+@@ -480,7 +_,7 @@
if (p_269627_) {
p_270552_.accept(this.uiState.getSettings().dataConfiguration());
} else {
@@ -28,7 +28,7 @@
}
},
Component.translatable("dataPack.validation.failed"),
-@@ -605,6 +_,7 @@
+@@ -594,6 +_,7 @@
if (path != null) {
if (this.tempDataPackRepository == null) {
this.tempDataPackRepository = ServerPacksSource.createPackRepository(path, this.packValidator);
diff --git a/patches/net/minecraft/client/gui/screens/worldselection/ExperimentsScreen.java.patch b/patches/net/minecraft/client/gui/screens/worldselection/ExperimentsScreen.java.patch
new file mode 100644
index 0000000000..c34df49406
--- /dev/null
+++ b/patches/net/minecraft/client/gui/screens/worldselection/ExperimentsScreen.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/client/gui/screens/worldselection/ExperimentsScreen.java
++++ b/net/minecraft/client/gui/screens/worldselection/ExperimentsScreen.java
+@@ -62,6 +_,11 @@
+
+ @Override
+ protected void init() {
++ if (net.minecraft.world.flag.FeatureFlags.REGISTRY.hasAnyModdedFlags()) {
++ this.minecraft.setScreen(new net.neoforged.neoforge.client.gui.ScrollableExperimentsScreen(this.parent, this.packRepository, this.output));
++ return;
++ }
++
+ this.layout.addTitleHeader(TITLE, this.font);
+ LinearLayout linearlayout = this.layout.addToContents(LinearLayout.vertical());
+ linearlayout.addChild(new MultiLineTextWidget(INFO, this.font).setMaxWidth(310), p_293611_ -> p_293611_.paddingBottom(15));
diff --git a/patches/net/minecraft/client/gui/screens/worldselection/WorldSelectionList.java.patch b/patches/net/minecraft/client/gui/screens/worldselection/WorldSelectionList.java.patch
index bd2c7fd8b2..ff9e5dff5c 100644
--- a/patches/net/minecraft/client/gui/screens/worldselection/WorldSelectionList.java.patch
+++ b/patches/net/minecraft/client/gui/screens/worldselection/WorldSelectionList.java.patch
@@ -9,8 +9,8 @@
static final Component FROM_NEWER_TOOLTIP_1 = Component.translatable("selectWorld.tooltip.fromNewerVersion1").withStyle(ChatFormatting.RED);
static final Component FROM_NEWER_TOOLTIP_2 = Component.translatable("selectWorld.tooltip.fromNewerVersion2").withStyle(ChatFormatting.RED);
@@ -403,6 +_,7 @@
- p_281612_.drawString(this.minecraft.font, s1, p_282820_ + 32 + 3, p_283181_ + 9 + 3, -8355712, false);
- p_281612_.drawString(this.minecraft.font, component, p_282820_ + 32 + 3, p_283181_ + 9 + 9 + 3, -8355712, false);
+ p_281612_.drawString(this.minecraft.font, s1, p_282820_ + 32 + 3, p_283181_ + 9 + 3, -8355712);
+ p_281612_.drawString(this.minecraft.font, component, p_282820_ + 32 + 3, p_283181_ + 9 + 9 + 3, -8355712);
p_281612_.blit(RenderType::guiTextured, this.icon.textureLocation(), p_282820_, p_283181_, 0.0F, 0.0F, 32, 32, 32, 32);
+ renderExperimentalWarning(p_281612_, p_283204_, p_283025_, p_283181_, p_282820_);
if (this.minecraft.options.touchscreen().get() || p_283396_) {
diff --git a/patches/net/minecraft/client/main/Main.java.patch b/patches/net/minecraft/client/main/Main.java.patch
index c66fe4ba49..af5d923dcf 100644
--- a/patches/net/minecraft/client/main/Main.java.patch
+++ b/patches/net/minecraft/client/main/Main.java.patch
@@ -1,11 +1,12 @@
--- a/net/minecraft/client/main/Main.java
+++ b/net/minecraft/client/main/Main.java
-@@ -120,7 +_,7 @@
+@@ -121,8 +_,7 @@
CrashReport.preload();
logger = LogUtils.getLogger();
s1 = "Bootstrap";
- Bootstrap.bootStrap();
-+ net.neoforged.fml.loading.BackgroundWaiter.runAndTick(()->Bootstrap.bootStrap(), net.neoforged.fml.loading.FMLLoader.progressWindowTick);
+- ClientBootstrap.bootstrap();
++ net.neoforged.fml.loading.BackgroundWaiter.runAndTick(() -> Bootstrap.bootStrap(), net.neoforged.fml.loading.FMLLoader.progressWindowTick);
GameLoadTimesEvent.INSTANCE.setBootstrapTime(Bootstrap.bootstrapDuration.get());
Bootstrap.validate();
s1 = "Argument parsing";
diff --git a/patches/net/minecraft/client/model/HumanoidModel.java.patch b/patches/net/minecraft/client/model/HumanoidModel.java.patch
index 5d2e521e41..94cf1ee81c 100644
--- a/patches/net/minecraft/client/model/HumanoidModel.java.patch
+++ b/patches/net/minecraft/client/model/HumanoidModel.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/model/HumanoidModel.java
+++ b/net/minecraft/client/model/HumanoidModel.java
-@@ -257,6 +_,8 @@
+@@ -253,6 +_,8 @@
case BRUSH:
this.rightArm.xRot = this.rightArm.xRot * 0.5F - (float) (Math.PI / 5);
this.rightArm.yRot = 0.0F;
@@ -9,7 +9,7 @@
}
}
-@@ -299,6 +_,8 @@
+@@ -295,6 +_,8 @@
case BRUSH:
this.leftArm.xRot = this.leftArm.xRot * 0.5F - (float) (Math.PI / 5);
this.leftArm.yRot = 0.0F;
@@ -18,7 +18,7 @@
}
}
-@@ -376,7 +_,7 @@
+@@ -372,7 +_,7 @@
}
@OnlyIn(Dist.CLIENT)
@@ -27,7 +27,7 @@
EMPTY(false),
ITEM(false),
BLOCK(false),
-@@ -389,13 +_,31 @@
+@@ -385,13 +_,31 @@
BRUSH(false);
private final boolean twoHanded;
diff --git a/patches/net/minecraft/client/model/geom/LayerDefinitions.java.patch b/patches/net/minecraft/client/model/geom/LayerDefinitions.java.patch
index de5171aeea..3e54603301 100644
--- a/patches/net/minecraft/client/model/geom/LayerDefinitions.java.patch
+++ b/patches/net/minecraft/client/model/geom/LayerDefinitions.java.patch
@@ -1,8 +1,8 @@
--- a/net/minecraft/client/model/geom/LayerDefinitions.java
+++ b/net/minecraft/client/model/geom/LayerDefinitions.java
-@@ -454,6 +_,7 @@
- builder.put(ModelLayers.createWallSignModelName(p_359128_), layerdefinition54);
- builder.put(ModelLayers.createHangingSignModelName(p_359128_), layerdefinition55);
+@@ -468,6 +_,7 @@
+ builder.put(ModelLayers.createHangingSignModelName(p_382521_, hangingsignrenderer$attachmenttype), layerdefinition55);
+ }
});
+ net.neoforged.neoforge.client.ClientHooks.loadLayerDefinitions(builder);
ImmutableMap immutablemap = builder.build();
diff --git a/patches/net/minecraft/client/model/geom/ModelLayers.java.patch b/patches/net/minecraft/client/model/geom/ModelLayers.java.patch
index 9b9691073a..2e84c89ca5 100644
--- a/patches/net/minecraft/client/model/geom/ModelLayers.java.patch
+++ b/patches/net/minecraft/client/model/geom/ModelLayers.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/model/geom/ModelLayers.java
+++ b/net/minecraft/client/model/geom/ModelLayers.java
-@@ -301,15 +_,18 @@
+@@ -308,15 +_,18 @@
}
public static ModelLayerLocation createStandingSignModelName(WoodType p_171292_) {
@@ -15,10 +15,10 @@
+ return new ModelLayerLocation(location.withPrefix("sign/wall/"), "main");
}
- public static ModelLayerLocation createHangingSignModelName(WoodType p_252225_) {
-- return createLocation("hanging_sign/" + p_252225_.name(), "main");
+ public static ModelLayerLocation createHangingSignModelName(WoodType p_252225_, HangingSignRenderer.AttachmentType p_382987_) {
+- return createLocation("hanging_sign/" + p_252225_.name() + "/" + p_382987_.getSerializedName(), "main");
+ ResourceLocation location = ResourceLocation.parse(p_252225_.name());
-+ return new ModelLayerLocation(location.withPrefix("hanging_sign/"), "main");
++ return new ModelLayerLocation(location.withPrefix("hanging_sign/").withSuffix("/" + p_382987_.getSerializedName()), "main");
}
public static Stream getKnownLocations() {
diff --git a/patches/net/minecraft/client/multiplayer/ClientChunkCache.java.patch b/patches/net/minecraft/client/multiplayer/ClientChunkCache.java.patch
index 768dd4d9eb..3aa9874e4a 100644
--- a/patches/net/minecraft/client/multiplayer/ClientChunkCache.java.patch
+++ b/patches/net/minecraft/client/multiplayer/ClientChunkCache.java.patch
@@ -8,7 +8,7 @@
this.storage.drop(i, levelchunk);
}
}
-@@ -125,6 +_,7 @@
+@@ -126,6 +_,7 @@
}
this.level.onChunkLoaded(chunkpos);
diff --git a/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch b/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch
index a1d3332be3..34cbc348c3 100644
--- a/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch
+++ b/patches/net/minecraft/client/multiplayer/ClientLevel.java.patch
@@ -1,6 +1,15 @@
--- a/net/minecraft/client/multiplayer/ClientLevel.java
+++ b/net/minecraft/client/multiplayer/ClientLevel.java
-@@ -126,6 +_,7 @@
+@@ -115,7 +_,7 @@
+ private final TickRateManager tickRateManager;
+ private final Minecraft minecraft = Minecraft.getInstance();
+ final List players = Lists.newArrayList();
+- final List dragonParts = Lists.newArrayList();
++ final List> dragonParts = Lists.newArrayList();
+ private final Map mapData = Maps.newHashMap();
+ private static final int CLOUD_COLOR = -1;
+ private int skyFlashTime;
+@@ -131,6 +_,7 @@
p_194170_.put(
BiomeColors.WATER_COLOR_RESOLVER, new BlockTintCache(p_194168_ -> this.calculateBlockTint(p_194168_, BiomeColors.WATER_COLOR_RESOLVER))
);
@@ -8,16 +17,15 @@
}
);
private final ClientChunkCache chunkSource;
-@@ -135,6 +_,8 @@
+@@ -140,6 +_,7 @@
private final int seaLevel;
private boolean tickDayTime;
private static final Set
- MARKER_PARTICLE_ITEMS = Set.of(Items.BARRIER, Items.LIGHT);
-+ private final it.unimi.dsi.fastutil.ints.Int2ObjectMap> partEntities = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>();
+ private final net.neoforged.neoforge.client.model.data.ModelDataManager modelDataManager = new net.neoforged.neoforge.client.model.data.ModelDataManager(this);
public void handleBlockChangedAck(int p_233652_) {
this.blockStatePredictionHandler.endPredictionsUpTo(p_233652_, this);
-@@ -164,10 +_,15 @@
+@@ -169,10 +_,15 @@
@Override
public boolean setBlock(BlockPos p_233643_, BlockState p_233644_, int p_233645_, int p_233646_) {
if (this.blockStatePredictionHandler.isPredicting()) {
@@ -33,7 +41,7 @@
}
return flag;
-@@ -201,6 +_,7 @@
+@@ -206,6 +_,7 @@
this.serverSimulationDistance = p_363776_;
this.updateSkyBrightness();
this.prepareWeather();
@@ -41,7 +49,7 @@
}
public void queueLightUpdate(Runnable p_194172_) {
-@@ -244,7 +_,7 @@
+@@ -249,7 +_,7 @@
private void tickTime() {
this.clientLevelData.setGameTime(this.clientLevelData.getGameTime() + 1L);
if (this.tickDayTime) {
@@ -50,7 +58,7 @@
}
}
-@@ -283,7 +_,11 @@
+@@ -288,7 +_,11 @@
p_104640_.setOldPosAndRot();
p_104640_.tickCount++;
Profiler.get().push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(p_104640_.getType()).toString());
@@ -63,7 +71,7 @@
Profiler.get().pop();
for (Entity entity : p_104640_.getPassengers()) {
-@@ -335,8 +_,10 @@
+@@ -339,8 +_,10 @@
}
public void addEntity(Entity p_104741_) {
@@ -74,7 +82,7 @@
}
public void removeEntity(int p_171643_, Entity.RemovalReason p_171644_) {
-@@ -507,6 +_,13 @@
+@@ -511,6 +_,13 @@
float p_263349_,
long p_263408_
) {
@@ -88,7 +96,7 @@
if (p_263381_ == this.minecraft.player) {
this.playSound(p_263372_, p_263404_, p_263365_, p_263335_.value(), p_263417_, p_263416_, p_263349_, false, p_263408_);
}
-@@ -516,6 +_,12 @@
+@@ -520,6 +_,12 @@
public void playSeededSound(
@Nullable Player p_263514_, Entity p_263536_, Holder p_263518_, SoundSource p_263487_, float p_263538_, float p_263524_, long p_263509_
) {
@@ -101,7 +109,16 @@
if (p_263514_ == this.minecraft.player) {
this.minecraft.getSoundManager().play(new EntityBoundSoundInstance(p_263518_.value(), p_263487_, p_263538_, p_263524_, p_263536_, p_263509_));
}
-@@ -1045,6 +_,7 @@
+@@ -731,7 +_,7 @@
+ return this.players;
+ }
+
+- public List dragonParts() {
++ public List> dragonParts() {
+ return this.dragonParts;
+ }
+
+@@ -1056,6 +_,7 @@
}
public void setDifficulty(Difficulty p_104852_) {
@@ -109,29 +126,27 @@
this.difficulty = p_104852_;
}
-@@ -1081,14 +_,75 @@
- if (p_171712_ instanceof AbstractClientPlayer) {
- ClientLevel.this.players.add((AbstractClientPlayer)p_171712_);
+@@ -1098,6 +_,9 @@
+ ClientLevel.this.dragonParts.addAll(Arrays.asList(enderdragon.getSubEntities()));
+ break;
+ default:
++ if (p_171712_.isMultipartEntity()) {
++ ClientLevel.this.dragonParts.addAll(Arrays.asList(p_171712_.getParts()));
++ }
}
-+ if (p_171712_.isMultipartEntity()) {
-+ for (net.neoforged.neoforge.entity.PartEntity> part : p_171712_.getParts()) {
-+ ClientLevel.this.partEntities.put(part.getId(), part);
-+ }
-+ }
}
- public void onTrackingEnd(Entity p_171716_) {
- p_171716_.unRide();
- ClientLevel.this.players.remove(p_171716_);
+@@ -1112,10 +_,58 @@
+ ClientLevel.this.dragonParts.removeAll(Arrays.asList(enderdragon.getSubEntities()));
+ break;
+ default:
++ if (p_171716_.isMultipartEntity()) {
++ ClientLevel.this.dragonParts.removeAll(Arrays.asList(p_171716_.getParts()));
++ }
+ }
+
+ p_171716_.onRemovedFromLevel();
+ net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.entity.EntityLeaveLevelEvent(p_171716_, ClientLevel.this));
-+
-+ if (p_171716_.isMultipartEntity()) {
-+ for (net.neoforged.neoforge.entity.PartEntity> part : p_171716_.getParts()) {
-+ ClientLevel.this.partEntities.remove(part.getId());
-+ }
-+ }
}
public void onSectionChange(Entity p_233660_) {
@@ -139,11 +154,6 @@
+ }
+
+ @Override
-+ public java.util.Collection> getPartEntities() {
-+ return this.partEntities.values();
-+ }
-+
-+ @Override
+ public net.neoforged.neoforge.client.model.data.ModelDataManager getModelDataManager() {
+ return modelDataManager;
+ }
diff --git a/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch b/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch
index a1ad4df3f6..dab1a825b8 100644
--- a/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch
+++ b/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/multiplayer/ClientPacketListener.java
+++ b/net/minecraft/client/multiplayer/ClientPacketListener.java
-@@ -367,6 +_,7 @@
+@@ -368,6 +_,7 @@
private final ChunkBatchSizeCalculator chunkBatchSizeCalculator = new ChunkBatchSizeCalculator();
private final PingDebugMonitor pingDebugMonitor;
private final DebugSampleSubscriber debugSampleSubscriber;
@@ -8,7 +8,7 @@
@Nullable
private LevelLoadStatusManager levelLoadStatusManager;
private boolean serverEnforcesSecureChat;
-@@ -388,7 +_,8 @@
+@@ -389,7 +_,8 @@
p_253924_.gui.getChat().restoreState(p_295121_.chatState());
}
@@ -18,7 +18,7 @@
this.fuelValues = FuelValues.vanillaBurnTimes(p_295121_.receivedRegistries(), this.enabledFeatures);
}
-@@ -451,12 +_,13 @@
+@@ -452,12 +_,13 @@
this.minecraft.debugRenderer.clear();
this.minecraft.player.resetPos();
@@ -33,7 +33,7 @@
this.minecraft.player.setReducedDebugInfo(p_105030_.reducedDebugInfo());
this.minecraft.player.setShowDeathScreen(p_105030_.showDeathScreen());
this.minecraft.player.setDoLimitedCrafting(p_105030_.doLimitedCrafting());
-@@ -900,7 +_,8 @@
+@@ -902,7 +_,8 @@
this.serverCookies,
chatcomponent$state,
this.customReportDetails,
@@ -126,7 +126,7 @@
}
@Override
-@@ -2526,6 +_,8 @@
+@@ -2540,6 +_,8 @@
}
public void sendChat(String p_249888_) {
@@ -135,7 +135,7 @@
Instant instant = Instant.now();
long i = Crypt.SaltSupplier.getLong();
LastSeenMessagesTracker.Update lastseenmessagestracker$update = this.lastSeenMessages.generateAndApplyUpdate();
-@@ -2535,6 +_,7 @@
+@@ -2549,6 +_,7 @@
}
public void sendCommand(String p_250092_) {
@@ -143,7 +143,7 @@
SignableCommand signablecommand = SignableCommand.of(this.parseCommand(p_250092_));
if (signablecommand.arguments().isEmpty()) {
this.send(new ServerboundChatCommandPacket(p_250092_));
-@@ -2622,6 +_,10 @@
+@@ -2640,6 +_,10 @@
public Scoreboard scoreboard() {
return this.scoreboard;
diff --git a/patches/net/minecraft/client/multiplayer/MultiPlayerGameMode.java.patch b/patches/net/minecraft/client/multiplayer/MultiPlayerGameMode.java.patch
index 4a88a77d66..128eb6a4de 100644
--- a/patches/net/minecraft/client/multiplayer/MultiPlayerGameMode.java.patch
+++ b/patches/net/minecraft/client/multiplayer/MultiPlayerGameMode.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/multiplayer/MultiPlayerGameMode.java
+++ b/net/minecraft/client/multiplayer/MultiPlayerGameMode.java
-@@ -120,11 +_,12 @@
+@@ -121,11 +_,12 @@
} else if (blockstate.isAir()) {
return false;
} else {
@@ -15,7 +15,7 @@
}
return flag;
-@@ -143,6 +_,7 @@
+@@ -144,6 +_,7 @@
BlockState blockstate = this.minecraft.level.getBlockState(p_105270_);
this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, p_105270_, blockstate, 1.0F);
this.startPrediction(this.minecraft.level, p_233757_ -> {
@@ -23,7 +23,7 @@
this.destroyBlock(p_105270_);
return new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, p_105270_, p_105271_, p_233757_);
});
-@@ -152,15 +_,19 @@
+@@ -153,15 +_,19 @@
this.connection
.send(new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, this.destroyBlockPos, p_105271_));
}
@@ -43,7 +43,7 @@
if (flag && blockstate1.getDestroyProgress(this.minecraft.player, this.minecraft.player.level(), p_105270_) >= 1.0F) {
this.destroyBlock(p_105270_);
} else {
-@@ -172,7 +_,7 @@
+@@ -173,7 +_,7 @@
this.minecraft.level.destroyBlockProgress(this.minecraft.player.getId(), this.destroyBlockPos, this.getDestroyStage());
}
@@ -52,7 +52,7 @@
});
}
-@@ -203,6 +_,7 @@
+@@ -204,6 +_,7 @@
BlockState blockstate1 = this.minecraft.level.getBlockState(p_105284_);
this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, p_105284_, blockstate1, 1.0F);
this.startPrediction(this.minecraft.level, p_233753_ -> {
@@ -60,7 +60,7 @@
this.destroyBlock(p_105284_);
return new ServerboundPlayerActionPacket(ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, p_105284_, p_105285_, p_233753_);
});
-@@ -215,7 +_,7 @@
+@@ -216,7 +_,7 @@
} else {
this.destroyProgress = this.destroyProgress + blockstate.getDestroyProgress(this.minecraft.player, this.minecraft.player.level(), p_105284_);
if (this.destroyTicks % 4.0F == 0.0F) {
@@ -69,7 +69,7 @@
this.minecraft
.getSoundManager()
.play(
-@@ -232,6 +_,7 @@
+@@ -233,6 +_,7 @@
this.destroyTicks++;
this.minecraft.getTutorial().onDestroyBlock(this.minecraft.level, p_105284_, blockstate, Mth.clamp(this.destroyProgress, 0.0F, 1.0F));
@@ -77,7 +77,7 @@
if (this.destroyProgress >= 1.0F) {
this.isDestroying = false;
this.startPrediction(this.minecraft.level, p_233739_ -> {
-@@ -270,7 +_,7 @@
+@@ -271,7 +_,7 @@
private boolean sameDestroyTarget(BlockPos p_105282_) {
ItemStack itemstack = this.minecraft.player.getMainHandItem();
@@ -86,7 +86,7 @@
}
private void ensureHasSentCarriedItem() {
-@@ -298,12 +_,23 @@
+@@ -299,12 +_,23 @@
private InteractionResult performUseItemOn(LocalPlayer p_233747_, InteractionHand p_233748_, BlockHitResult p_233749_) {
BlockPos blockpos = p_233749_.getBlockPos();
ItemStack itemstack = p_233747_.getItemInHand(p_233748_);
@@ -112,7 +112,7 @@
BlockState blockstate = this.minecraft.level.getBlockState(blockpos);
if (!this.connection.isFeatureEnabled(blockstate.getBlock().requiredFeatures())) {
return InteractionResult.FAIL;
-@@ -324,8 +_,10 @@
+@@ -325,8 +_,10 @@
}
}
@@ -125,7 +125,7 @@
InteractionResult interactionresult2;
if (this.localPlayerMode.isCreative()) {
int i = itemstack.getCount();
-@@ -359,6 +_,11 @@
+@@ -360,6 +_,11 @@
mutableobject.setValue(InteractionResult.PASS);
return serverbounduseitempacket;
} else {
@@ -137,7 +137,7 @@
InteractionResult interactionresult = itemstack.use(this.minecraft.level, p_233722_, p_233723_);
ItemStack itemstack1;
if (interactionresult instanceof InteractionResult.Success interactionresult$success) {
-@@ -371,6 +_,8 @@
+@@ -372,6 +_,8 @@
if (itemstack1 != itemstack) {
p_233722_.setItemInHand(p_233723_, itemstack1);
@@ -146,7 +146,7 @@
}
mutableobject.setValue(interactionresult);
-@@ -409,6 +_,9 @@
+@@ -410,6 +_,9 @@
this.ensureHasSentCarriedItem();
Vec3 vec3 = p_105233_.getLocation().subtract(p_105232_.getX(), p_105232_.getY(), p_105232_.getZ());
this.connection.send(ServerboundInteractPacket.createInteractionPacket(p_105232_, p_105231_.isShiftKeyDown(), p_105234_, vec3));
diff --git a/patches/net/minecraft/client/multiplayer/PlayerInfo.java.patch b/patches/net/minecraft/client/multiplayer/PlayerInfo.java.patch
index 27972460ce..0be4333b3b 100644
--- a/patches/net/minecraft/client/multiplayer/PlayerInfo.java.patch
+++ b/patches/net/minecraft/client/multiplayer/PlayerInfo.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/multiplayer/PlayerInfo.java
+++ b/net/minecraft/client/multiplayer/PlayerInfo.java
-@@ -86,6 +_,7 @@
+@@ -88,6 +_,7 @@
}
protected void setGameMode(GameType p_105318_) {
diff --git a/patches/net/minecraft/client/particle/BreakingItemParticle.java.patch b/patches/net/minecraft/client/particle/BreakingItemParticle.java.patch
deleted file mode 100644
index 3d9b32361f..0000000000
--- a/patches/net/minecraft/client/particle/BreakingItemParticle.java.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/net/minecraft/client/particle/BreakingItemParticle.java
-+++ b/net/minecraft/client/particle/BreakingItemParticle.java
-@@ -33,7 +_,7 @@
-
- protected BreakingItemParticle(ClientLevel p_105665_, double p_105666_, double p_105667_, double p_105668_, ItemStack p_105669_) {
- super(p_105665_, p_105666_, p_105667_, p_105668_, 0.0, 0.0, 0.0);
-- this.setSprite(Minecraft.getInstance().getItemRenderer().getModel(p_105669_, p_105665_, null, 0).getParticleIcon());
-+ this.setSprite(Minecraft.getInstance().getItemRenderer().getModel(p_105669_, p_105665_, null, 0).getParticleIcon(net.neoforged.neoforge.client.model.data.ModelData.EMPTY));
- this.gravity = 1.0F;
- this.quadSize /= 2.0F;
- this.uo = this.random.nextFloat() * 3.0F;
diff --git a/patches/net/minecraft/client/particle/ItemPickupParticle.java.patch b/patches/net/minecraft/client/particle/ItemPickupParticle.java.patch
index fbf0a1e746..8cea2aa848 100644
--- a/patches/net/minecraft/client/particle/ItemPickupParticle.java.patch
+++ b/patches/net/minecraft/client/particle/ItemPickupParticle.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/particle/ItemPickupParticle.java
+++ b/net/minecraft/client/particle/ItemPickupParticle.java
-@@ -102,4 +_,11 @@
+@@ -99,4 +_,11 @@
this.targetYOld = this.targetY;
this.targetZOld = this.targetZ;
}
diff --git a/patches/net/minecraft/client/particle/Particle.java.patch b/patches/net/minecraft/client/particle/Particle.java.patch
index a3587365f5..b74356986f 100644
--- a/patches/net/minecraft/client/particle/Particle.java.patch
+++ b/patches/net/minecraft/client/particle/Particle.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/particle/Particle.java
+++ b/net/minecraft/client/particle/Particle.java
-@@ -245,6 +_,18 @@
+@@ -250,6 +_,18 @@
return Optional.empty();
}
diff --git a/patches/net/minecraft/client/particle/ParticleEngine.java.patch b/patches/net/minecraft/client/particle/ParticleEngine.java.patch
index de43669f57..cb59bf982d 100644
--- a/patches/net/minecraft/client/particle/ParticleEngine.java.patch
+++ b/patches/net/minecraft/client/particle/ParticleEngine.java.patch
@@ -1,7 +1,7 @@
--- a/net/minecraft/client/particle/ParticleEngine.java
+++ b/net/minecraft/client/particle/ParticleEngine.java
-@@ -77,11 +_,11 @@
- ParticleRenderType.TERRAIN_SHEET, ParticleRenderType.PARTICLE_SHEET_OPAQUE, ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT, ParticleRenderType.CUSTOM
+@@ -74,11 +_,11 @@
+ ParticleRenderType.TERRAIN_SHEET, ParticleRenderType.PARTICLE_SHEET_OPAQUE, ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
);
protected ClientLevel level;
- private final Map> particles = Maps.newIdentityHashMap();
@@ -14,7 +14,7 @@
private final Queue particlesToAdd = Queues.newArrayDeque();
private final Map spriteSets = Maps.newHashMap();
private final TextureAtlas textureAtlas;
-@@ -214,10 +_,14 @@
+@@ -207,10 +_,14 @@
this.register(ParticleTypes.BLOCK_CRUMBLE, new TerrainParticle.CrumblingProvider());
}
@@ -30,7 +30,7 @@
public void register(ParticleType p_273423_, ParticleProvider.Sprite p_273134_) {
this.register(
p_273423_,
-@@ -234,10 +_,12 @@
+@@ -227,10 +_,12 @@
);
}
@@ -44,7 +44,7 @@
}
@Override
-@@ -357,7 +_,7 @@
+@@ -350,7 +_,7 @@
private Particle makeParticle(
T p_107396_, double p_107397_, double p_107398_, double p_107399_, double p_107400_, double p_107401_, double p_107402_
) {
@@ -53,36 +53,81 @@
return particleprovider == null
? null
: particleprovider.createParticle(p_107396_, this.level, p_107397_, p_107398_, p_107399_, p_107400_, p_107401_, p_107402_);
-@@ -433,17 +_,27 @@
+@@ -426,28 +_,48 @@
}
}
+ @Deprecated
- public void render(LightTexture p_107339_, Camera p_107340_, float p_107341_) {
-+ render(p_107339_, p_107340_, p_107341_, null, type -> true);
+ public void render(Camera p_107340_, float p_107341_, MultiBufferSource.BufferSource p_383193_) {
+- for (ParticleRenderType particlerendertype : RENDER_ORDER) {
++ render(p_107340_, p_107341_, p_383193_, null, type -> true);
+ }
+
-+ public void render(LightTexture p_107339_, Camera p_107340_, float p_107341_, @Nullable net.minecraft.client.renderer.culling.Frustum frustum, java.util.function.Predicate renderTypePredicate) {
- p_107339_.turnOnLightLayer();
- RenderSystem.enableDepthTest();
++ public void render(Camera p_107340_, float p_107341_, MultiBufferSource.BufferSource p_383193_, @Nullable net.minecraft.client.renderer.culling.Frustum frustum, java.util.function.Predicate renderTypePredicate) {
+ //TODO porting: is this even needed with the particle render order fix???
-+ RenderSystem.activeTexture(org.lwjgl.opengl.GL13.GL_TEXTURE2);
-+ RenderSystem.activeTexture(org.lwjgl.opengl.GL13.GL_TEXTURE0);
-
-- for (ParticleRenderType particlerendertype : RENDER_ORDER) {
++ com.mojang.blaze3d.systems.RenderSystem.activeTexture(org.lwjgl.opengl.GL13.GL_TEXTURE2);
++ com.mojang.blaze3d.systems.RenderSystem.activeTexture(org.lwjgl.opengl.GL13.GL_TEXTURE0);
+ for (ParticleRenderType particlerendertype : this.particles.keySet()) { // Neo: allow custom IParticleRenderType's
-+ if (particlerendertype == ParticleRenderType.NO_RENDER || !renderTypePredicate.test(particlerendertype)) continue;
++ if (particlerendertype == ParticleRenderType.NO_RENDER || particlerendertype == ParticleRenderType.CUSTOM || !renderTypePredicate.test(particlerendertype)) continue;
Queue queue = this.particles.get(particlerendertype);
if (queue != null && !queue.isEmpty()) {
- Tesselator tesselator = Tesselator.getInstance();
- BufferBuilder bufferbuilder = particlerendertype.begin(tesselator, this.textureManager);
- if (bufferbuilder != null) {
- for (Particle particle : queue) {
-+ if (frustum != null && !frustum.isVisible(particle.getRenderBoundingBox(p_107341_))) continue;
- try {
- particle.render(bufferbuilder, p_107340_, p_107341_);
- } catch (Throwable throwable) {
-@@ -475,7 +_,7 @@
+- renderParticleType(p_107340_, p_107341_, p_383193_, particlerendertype, queue);
++ renderParticleType(p_107340_, p_107341_, p_383193_, particlerendertype, queue, frustum);
+ }
+ }
+
+ Queue queue1 = this.particles.get(ParticleRenderType.CUSTOM);
+ if (queue1 != null && !queue1.isEmpty()) {
+- renderCustomParticles(p_107340_, p_107341_, p_383193_, queue1);
++ renderCustomParticles(p_107340_, p_107341_, p_383193_, queue1, frustum);
+ }
+
+ p_383193_.endBatch();
+ }
+
+- private static void renderParticleType(
+- Camera p_382847_, float p_383032_, MultiBufferSource.BufferSource p_383105_, ParticleRenderType p_383179_, Queue p_383046_
++ /**
++ * @deprecated Neo: use {@link #renderParticleType(Camera, float, MultiBufferSource.BufferSource, ParticleRenderType, Queue, net.minecraft.client.renderer.culling.Frustum)} instead
++ */
++ @Deprecated
++ private static void renderParticleType(
++ Camera p_382847_, float p_383032_, MultiBufferSource.BufferSource p_383105_, ParticleRenderType p_383179_, Queue p_383046_
++ ) {
++ renderParticleType(p_382847_, p_383032_, p_383105_, p_383179_, p_383046_, null);
++ }
++
++ private static void renderParticleType(
++ Camera p_382847_, float p_383032_, MultiBufferSource.BufferSource p_383105_, ParticleRenderType p_383179_, Queue p_383046_, @Nullable net.minecraft.client.renderer.culling.Frustum frustum
+ ) {
+ VertexConsumer vertexconsumer = p_383105_.getBuffer(Objects.requireNonNull(p_383179_.renderType()));
+
+ for (Particle particle : p_383046_) {
++ if (frustum != null && !frustum.isVisible(particle.getRenderBoundingBox(p_383032_))) continue;
+ try {
+ particle.render(vertexconsumer, p_382847_, p_383032_);
+ } catch (Throwable throwable) {
+@@ -460,10 +_,19 @@
+ }
+ }
+
++ /**
++ * @deprecated Neo: use {@link #renderCustomParticles(Camera, float, MultiBufferSource.BufferSource, Queue, net.minecraft.client.renderer.culling.Frustum)} instead
++ */
++ @Deprecated
+ private static void renderCustomParticles(Camera p_383089_, float p_383167_, MultiBufferSource.BufferSource p_382990_, Queue p_383010_) {
++ renderCustomParticles(p_383089_, p_383167_, p_382990_, p_383010_, null);
++ }
++
++ private static void renderCustomParticles(Camera p_383089_, float p_383167_, MultiBufferSource.BufferSource p_382990_, Queue p_383010_, @Nullable net.minecraft.client.renderer.culling.Frustum frustum) {
+ PoseStack posestack = new PoseStack();
+
+ for (Particle particle : p_383010_) {
++ if (frustum != null && !frustum.isVisible(particle.getRenderBoundingBox(p_383167_))) continue;
+ try {
+ particle.renderCustom(posestack, p_382990_, p_383089_, p_383167_);
+ } catch (Throwable throwable) {
+@@ -483,7 +_,7 @@
}
public void destroy(BlockPos p_107356_, BlockState p_107357_) {
@@ -91,7 +136,7 @@
VoxelShape voxelshape = p_107357_.getShape(this.level, p_107356_);
double d0 = 0.25;
voxelshape.forAllBoxes(
-@@ -507,7 +_,7 @@
+@@ -515,7 +_,7 @@
d6 - 0.5,
p_107357_,
p_107356_
@@ -100,7 +145,7 @@
);
}
}
-@@ -552,12 +_,28 @@
+@@ -560,12 +_,28 @@
d0 = (double)i + aabb.maxX + 0.1F;
}
diff --git a/patches/net/minecraft/client/particle/ParticleRenderType.java.patch b/patches/net/minecraft/client/particle/ParticleRenderType.java.patch
index e3d114e526..9066738f82 100644
--- a/patches/net/minecraft/client/particle/ParticleRenderType.java.patch
+++ b/patches/net/minecraft/client/particle/ParticleRenderType.java.patch
@@ -1,24 +1,23 @@
--- a/net/minecraft/client/particle/ParticleRenderType.java
+++ b/net/minecraft/client/particle/ParticleRenderType.java
-@@ -44,6 +_,11 @@
- public String toString() {
- return "PARTICLE_SHEET_OPAQUE";
- }
-+
-+ @Override
-+ public boolean isTranslucent() {
-+ return false;
-+ }
- };
- ParticleRenderType PARTICLE_SHEET_TRANSLUCENT = new ParticleRenderType() {
- @Override
-@@ -89,4 +_,9 @@
+@@ -7,14 +_,18 @@
+ import net.neoforged.api.distmarker.OnlyIn;
- @Nullable
- BufferBuilder begin(Tesselator p_350949_, TextureManager p_107437_);
+ @OnlyIn(Dist.CLIENT)
+-public record ParticleRenderType(String name, @Nullable RenderType renderType) {
++public record ParticleRenderType(String name, @Nullable RenderType renderType, boolean translucent) {
+ public static final ParticleRenderType TERRAIN_SHEET = new ParticleRenderType("TERRAIN_SHEET", RenderType.translucentParticle(TextureAtlas.LOCATION_BLOCKS));
+ public static final ParticleRenderType PARTICLE_SHEET_OPAQUE = new ParticleRenderType(
+- "PARTICLE_SHEET_OPAQUE", RenderType.opaqueParticle(TextureAtlas.LOCATION_PARTICLES)
++ "PARTICLE_SHEET_OPAQUE", RenderType.opaqueParticle(TextureAtlas.LOCATION_PARTICLES), false
+ );
+ public static final ParticleRenderType PARTICLE_SHEET_TRANSLUCENT = new ParticleRenderType(
+ "PARTICLE_SHEET_TRANSLUCENT", RenderType.translucentParticle(TextureAtlas.LOCATION_PARTICLES)
+ );
+ public static final ParticleRenderType CUSTOM = new ParticleRenderType("CUSTOM", null);
+ public static final ParticleRenderType NO_RENDER = new ParticleRenderType("NO_RENDER", null);
+
-+ /** {@return whether this type renders before or after the translucent chunk layer} */
-+ default boolean isTranslucent() {
-+ return true;
++ public ParticleRenderType(String name, @Nullable RenderType renderType) {
++ this(name, renderType, true);
+ }
}
diff --git a/patches/net/minecraft/client/player/LocalPlayer.java.patch b/patches/net/minecraft/client/player/LocalPlayer.java.patch
index d772181669..7d98bb110e 100644
--- a/patches/net/minecraft/client/player/LocalPlayer.java.patch
+++ b/patches/net/minecraft/client/player/LocalPlayer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/player/LocalPlayer.java
+++ b/net/minecraft/client/player/LocalPlayer.java
-@@ -302,6 +_,7 @@
+@@ -307,6 +_,7 @@
ServerboundPlayerActionPacket.Action serverboundplayeractionpacket$action = p_108701_
? ServerboundPlayerActionPacket.Action.DROP_ALL_ITEMS
: ServerboundPlayerActionPacket.Action.DROP_ITEM;
@@ -8,7 +8,7 @@
ItemStack itemstack = this.getInventory().removeFromSelected(p_108701_);
this.connection.send(new ServerboundPlayerActionPacket(serverboundplayeractionpacket$action, BlockPos.ZERO, Direction.DOWN));
return !itemstack.isEmpty();
-@@ -482,7 +_,14 @@
+@@ -487,7 +_,14 @@
@Override
public void playSound(SoundEvent p_108651_, float p_108652_, float p_108653_) {
@@ -24,43 +24,43 @@
}
@Override
-@@ -676,6 +_,7 @@
+@@ -680,6 +_,7 @@
+ && this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.CROUCHING)
&& (this.isShiftKeyDown() || !this.isSleeping() && !this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.STANDING));
- float f = (float)this.getAttributeValue(Attributes.SNEAKING_SPEED);
- this.input.tick(this.isMovingSlowly(), f);
+ this.input.tick();
+ net.neoforged.neoforge.client.ClientHooks.onMovementInputUpdate(this, this.input);
this.minecraft.getTutorial().onInput(this.input);
- if (this.isUsingItem() && !this.isPassenger()) {
- this.input.leftImpulse *= 0.2F;
-@@ -704,7 +_,7 @@
- boolean flag4 = this.canStartSprinting();
- boolean flag5 = this.isPassenger() ? this.getVehicle().onGround() : this.onGround();
- boolean flag6 = !flag1 && !flag2;
-- if ((flag5 || this.isUnderWater()) && flag6 && flag4) {
-+ if ((flag5 || this.isUnderWater() || this.canStartSwimming()) && flag6 && flag4) {
+ if (this.shouldStopSprinting()) {
+ this.setSprinting(false);
+@@ -718,7 +_,7 @@
+ boolean flag3 = this.canStartSprinting();
+ boolean flag4 = this.isPassenger() ? this.getVehicle().onGround() : this.onGround();
+ boolean flag5 = !flag1 && !flag2;
+- if ((flag4 || this.isUnderWater()) && flag5 && flag3) {
++ if ((flag4 || this.isUnderWater() || this.canStartSwimming()) && flag5 && flag3) {
if (this.sprintTriggerTime <= 0 && !this.minecraft.options.keySprint.isDown()) {
this.sprintTriggerTime = 7;
} else {
-@@ -712,15 +_,15 @@
+@@ -726,15 +_,15 @@
}
}
-- if ((!this.isInWater() || this.isUnderWater()) && flag4 && this.minecraft.options.keySprint.isDown()) {
-+ if (!this.isSprinting() && (!(this.isInWater() || this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType))) || (this.isUnderWater() || this.canStartSwimming())) && this.hasEnoughImpulseToStartSprinting() && flag4 && !this.isUsingItem() && !this.hasEffect(MobEffects.BLINDNESS) && this.minecraft.options.keySprint.isDown()) {
+- if ((!this.isInWater() || this.isUnderWater()) && flag3 && this.minecraft.options.keySprint.isDown()) {
++ if (!this.isSprinting() && (!(this.isInWater() || this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType))) || (this.isUnderWater() || this.canStartSwimming())) && this.hasEnoughImpulseToStartSprinting() && flag3 && !this.isUsingItem() && !this.hasEffect(MobEffects.BLINDNESS) && this.minecraft.options.keySprint.isDown()) {
this.setSprinting(true);
}
if (this.isSprinting()) {
- boolean flag7 = !this.input.hasForwardImpulse() || !this.hasEnoughFoodToStartSprinting();
-- boolean flag8 = flag7 || this.horizontalCollision && !this.minorHorizontalCollision || this.isInWater() && !this.isUnderWater();
-+ boolean flag8 = flag7 || this.horizontalCollision && !this.minorHorizontalCollision || this.isInWater() && !this.isUnderWater() || (this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType)) && !this.canStartSwimming());
+ boolean flag6 = !this.input.hasForwardImpulse() || !this.hasEnoughFoodToStartSprinting();
+- boolean flag7 = flag6 || this.horizontalCollision && !this.minorHorizontalCollision || this.isInWater() && !this.isUnderWater();
++ boolean flag7 = flag6 || this.horizontalCollision && !this.minorHorizontalCollision || this.isInWater() && !this.isUnderWater() || (this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType)) && !this.canStartSwimming());
if (this.isSwimming()) {
-- if (!this.onGround() && !this.input.keyPresses.shift() && flag7 || !this.isInWater()) {
-+ if (!this.onGround() && !this.input.keyPresses.shift() && flag7 || !(this.isInWater() || this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType)))) {
+- if (!this.onGround() && !this.input.keyPresses.shift() && flag6 || !this.isInWater()) {
++ if (!this.onGround() && !this.input.keyPresses.shift() && flag6 || !(this.isInWater() || this.isInFluidType((fluidType, height) -> this.canSwimInFluidType(fluidType)))) {
this.setSprinting(false);
}
- } else if (flag8) {
-@@ -729,7 +_,7 @@
+ } else if (flag7) {
+@@ -743,7 +_,7 @@
}
boolean flag9 = false;
@@ -69,7 +69,7 @@
if (this.minecraft.gameMode.isAlwaysFlying()) {
if (!abilities.flying) {
abilities.flying = true;
-@@ -866,6 +_,10 @@
+@@ -896,6 +_,10 @@
@Override
public void rideTick() {
super.rideTick();
@@ -80,7 +80,7 @@
this.handsBusy = false;
if (this.getControlledVehicle() instanceof AbstractBoat abstractboat) {
abstractboat.setInput(
-@@ -1063,7 +_,7 @@
+@@ -1094,7 +_,7 @@
}
private boolean hasEnoughFoodToStartSprinting() {
diff --git a/patches/net/minecraft/client/renderer/GameRenderer.java.patch b/patches/net/minecraft/client/renderer/GameRenderer.java.patch
index db82c7ecba..783162e3ba 100644
--- a/patches/net/minecraft/client/renderer/GameRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/GameRenderer.java.patch
@@ -29,7 +29,7 @@
f2 /= (float)livingentity.hurtDuration;
f2 = Mth.sin(f2 * f2 * f2 * f2 * (float) Math.PI);
float f3 = livingentity.getHurtDir();
-@@ -474,12 +_,12 @@
+@@ -476,12 +_,12 @@
(float)((double)window.getHeight() / window.getGuiScale()),
0.0F,
1000.0F,
@@ -44,7 +44,7 @@
Lighting.setupFor3DItems();
GuiGraphics guigraphics = new GuiGraphics(this.minecraft, this.renderBuffers.bufferSource());
if (flag && p_109096_ && this.minecraft.level != null) {
-@@ -505,7 +_,8 @@
+@@ -507,7 +_,8 @@
}
} else if (flag && this.minecraft.screen != null) {
try {
@@ -54,10 +54,10 @@
} catch (Throwable throwable1) {
CrashReport crashreport1 = CrashReport.forThrowable(throwable1, "Rendering screen");
CrashReportCategory crashreportcategory1 = crashreport1.addCategory("Screen render details");
-@@ -687,6 +_,8 @@
+@@ -689,6 +_,8 @@
this.minecraft.levelRenderer.prepareCullFrustum(camera.getPosition(), matrix4f2, matrix4f1);
this.minecraft.getMainRenderTarget().bindWrite(true);
- this.minecraft.levelRenderer.renderLevel(this.resourcePool, p_348589_, flag, camera, this, this.lightTexture, matrix4f2, matrix4f);
+ this.minecraft.levelRenderer.renderLevel(this.resourcePool, p_348589_, flag, camera, this, matrix4f2, matrix4f);
+ profilerfiller.popPush("neoforge_render_last");
+ net.neoforged.neoforge.client.ClientHooks.dispatchRenderStage(net.neoforged.neoforge.client.event.RenderLevelStageEvent.Stage.AFTER_LEVEL, this.minecraft.levelRenderer, null, matrix4f1, matrix4f, this.minecraft.levelRenderer.getTicks(), camera, this.minecraft.levelRenderer.getFrustum());
profilerfiller.popPush("hand");
diff --git a/patches/net/minecraft/client/renderer/ItemBlockRenderTypes.java.patch b/patches/net/minecraft/client/renderer/ItemBlockRenderTypes.java.patch
index 1bf5aead8b..81aaa5cdef 100644
--- a/patches/net/minecraft/client/renderer/ItemBlockRenderTypes.java.patch
+++ b/patches/net/minecraft/client/renderer/ItemBlockRenderTypes.java.patch
@@ -5,18 +5,18 @@
@OnlyIn(Dist.CLIENT)
public class ItemBlockRenderTypes {
+ @Deprecated
- private static final Map TYPE_BY_BLOCK = Util.make(Maps.newHashMap(), p_378824_ -> {
+ private static final Map TYPE_BY_BLOCK = Util.make(Maps.newHashMap(), p_382527_ -> {
RenderType rendertype = RenderType.tripwire();
- p_378824_.put(Blocks.TRIPWIRE, rendertype);
-@@ -340,6 +_,7 @@
- p_378824_.put(Blocks.BUBBLE_COLUMN, rendertype3);
- p_378824_.put(Blocks.TINTED_GLASS, rendertype3);
+ p_382527_.put(Blocks.TRIPWIRE, rendertype);
+@@ -345,6 +_,7 @@
+ p_382527_.put(Blocks.BUBBLE_COLUMN, rendertype3);
+ p_382527_.put(Blocks.TINTED_GLASS, rendertype3);
});
+ @Deprecated
private static final Map TYPE_BY_FLUID = Util.make(Maps.newHashMap(), p_109290_ -> {
RenderType rendertype = RenderType.translucent();
p_109290_.put(Fluids.FLOWING_WATER, rendertype);
-@@ -347,6 +_,8 @@
+@@ -352,6 +_,8 @@
});
private static boolean renderCutout;
@@ -25,7 +25,7 @@
public static RenderType getChunkRenderType(BlockState p_109283_) {
Block block = p_109283_.getBlock();
if (block instanceof LeavesBlock) {
-@@ -357,6 +_,8 @@
+@@ -362,6 +_,8 @@
}
}
@@ -34,7 +34,7 @@
public static RenderType getMovingBlockRenderType(BlockState p_109294_) {
Block block = p_109294_.getBlock();
if (block instanceof LeavesBlock) {
-@@ -371,11 +_,15 @@
+@@ -376,11 +_,15 @@
}
}
@@ -50,7 +50,7 @@
public static RenderType getRenderType(ItemStack p_366701_) {
if (p_366701_.getItem() instanceof BlockItem blockitem) {
Block block = blockitem.getBlock();
-@@ -392,5 +_,78 @@
+@@ -397,5 +_,78 @@
public static void setFancy(boolean p_109292_) {
renderCutout = p_109292_;
diff --git a/patches/net/minecraft/client/renderer/ItemInHandRenderer.java.patch b/patches/net/minecraft/client/renderer/ItemInHandRenderer.java.patch
index 50e02aa55f..cc0c374d6d 100644
--- a/patches/net/minecraft/client/renderer/ItemInHandRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/ItemInHandRenderer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/ItemInHandRenderer.java
+++ b/net/minecraft/client/renderer/ItemInHandRenderer.java
-@@ -168,11 +_,11 @@
+@@ -172,11 +_,11 @@
ResourceLocation resourcelocation = this.minecraft.player.getSkin().texture();
if (p_109365_ == HumanoidArm.RIGHT) {
playerrenderer.renderRightHand(
@@ -14,7 +14,7 @@
);
}
-@@ -234,7 +_,7 @@
+@@ -238,7 +_,7 @@
p_109367_.translate(-0.5F, -0.5F, 0.0F);
p_109367_.scale(0.0078125F, 0.0078125F, 0.0078125F);
MapId mapid = p_109370_.get(DataComponents.MAP_ID);
@@ -23,7 +23,7 @@
VertexConsumer vertexconsumer = p_109368_.getBuffer(mapitemsaveddata == null ? MAP_BACKGROUND : MAP_BACKGROUND_CHECKERBOARD);
Matrix4f matrix4f = p_109367_.last().pose();
vertexconsumer.addVertex(matrix4f, -7.0F, 135.0F, 0.0F).setColor(-1).setUv(0.0F, 1.0F).setLight(p_109369_);
-@@ -271,10 +_,10 @@
+@@ -275,10 +_,10 @@
ResourceLocation resourcelocation = abstractclientplayer.getSkin().texture();
if (flag) {
playerrenderer.renderRightHand(
@@ -36,7 +36,7 @@
}
}
-@@ -347,12 +_,14 @@
+@@ -351,12 +_,14 @@
if (iteminhandrenderer$handrenderselection.renderMainHand) {
float f4 = interactionhand == InteractionHand.MAIN_HAND ? f : 0.0F;
float f5 = 1.0F - Mth.lerp(p_109315_, this.oMainHandHeight, this.mainHandHeight);
@@ -51,7 +51,7 @@
this.renderArmWithItem(p_109318_, p_109315_, f1, InteractionHand.OFF_HAND, f6, this.offHandItem, f7, p_109316_, p_109317_, p_109319_);
}
-@@ -412,13 +_,13 @@
+@@ -416,13 +_,13 @@
if (flag && !p_109372_.isInvisible()) {
this.renderPlayerArm(p_109379_, p_109380_, p_109381_, p_109378_, p_109376_, humanoidarm);
}
@@ -67,30 +67,12 @@
boolean flag1 = CrossbowItem.isCharged(p_109377_);
boolean flag2 = humanoidarm == HumanoidArm.RIGHT;
int i = flag2 ? 1 : -1;
-@@ -468,6 +_,7 @@
- );
+@@ -468,6 +_,8 @@
} else {
boolean flag3 = humanoidarm == HumanoidArm.RIGHT;
-+ if (!net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(p_109377_).applyForgeHandTransform(p_109379_, minecraft.player, humanoidarm, p_109377_, p_109373_, p_109378_, p_109376_)) // FORGE: Allow items to define custom arm animation
+ int j = flag3 ? 1 : -1;
++ // Neo: Allow items to define custom arm animation
++ if (!net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(p_109377_).applyForgeHandTransform(p_109379_, minecraft.player, humanoidarm, p_109377_, p_109373_, p_109378_, p_109376_))
if (p_109372_.isUsingItem() && p_109372_.getUseItemRemainingTicks() > 0 && p_109372_.getUsedItemHand() == p_109375_) {
- int k = flag3 ? 1 : -1;
switch (p_109377_.getUseAnimation()) {
-@@ -582,8 +_,16 @@
- this.offHandHeight = Mth.clamp(this.offHandHeight - 0.4F, 0.0F, 1.0F);
- } else {
- float f = localplayer.getAttackStrengthScale(1.0F);
-- this.mainHandHeight = this.mainHandHeight + Mth.clamp((this.mainHandItem == itemstack ? f * f * f : 0.0F) - this.mainHandHeight, -0.4F, 0.4F);
-- this.offHandHeight = this.offHandHeight + Mth.clamp((float)(this.offHandItem == itemstack1 ? 1 : 0) - this.offHandHeight, -0.4F, 0.4F);
-+ boolean requipM = net.neoforged.neoforge.client.ClientHooks.shouldCauseReequipAnimation(this.mainHandItem, itemstack, localplayer.getInventory().selected);
-+ boolean requipO = net.neoforged.neoforge.client.ClientHooks.shouldCauseReequipAnimation(this.offHandItem, itemstack1, -1);
-+
-+ if (!requipM && this.mainHandItem != itemstack)
-+ this.mainHandItem = itemstack;
-+ if (!requipO && this.offHandItem != itemstack1)
-+ this.offHandItem = itemstack1;
-+
-+ this.mainHandHeight += Mth.clamp((!requipM ? f * f * f : 0.0F) - this.mainHandHeight, -0.4F, 0.4F);
-+ this.offHandHeight += Mth.clamp((float)(!requipO ? 1 : 0) - this.offHandHeight, -0.4F, 0.4F);
- }
-
- if (this.mainHandHeight < 0.1F) {
+ case NONE:
diff --git a/patches/net/minecraft/client/renderer/LevelRenderer.java.patch b/patches/net/minecraft/client/renderer/LevelRenderer.java.patch
index f5f9a14c0d..8bb0f0e49c 100644
--- a/patches/net/minecraft/client/renderer/LevelRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/LevelRenderer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/LevelRenderer.java
+++ b/net/minecraft/client/renderer/LevelRenderer.java
-@@ -485,7 +_,7 @@
+@@ -480,7 +_,7 @@
RenderSystem.clear(16640);
});
if (!flag1) {
@@ -9,25 +9,25 @@
}
this.addMainPass(framegraphbuilder, frustum, p_109604_, p_254120_, p_323920_, fogparameters, p_109603_, flag2, p_348530_, profilerfiller);
-@@ -494,7 +_,7 @@
+@@ -489,7 +_,7 @@
postchain1.addToFrame(framegraphbuilder, i, j, this.targets);
}
-- this.addParticlesPass(framegraphbuilder, p_109604_, p_109606_, f, fogparameters);
-+ this.addParticlesPass(framegraphbuilder, p_109604_, p_109606_, f, fogparameters, frustum, p_254120_, p_323920_);
+- this.addParticlesPass(framegraphbuilder, p_109604_, f, fogparameters);
++ this.addParticlesPass(framegraphbuilder, p_109604_, f, fogparameters, frustum, p_254120_, p_323920_);
CloudStatus cloudstatus = this.minecraft.options.getCloudsType();
if (cloudstatus != CloudStatus.OFF) {
float f2 = this.level.effects().getCloudHeight();
-@@ -505,7 +_,7 @@
+@@ -500,7 +_,7 @@
}
}
-- this.addWeatherPass(framegraphbuilder, p_109606_, p_109604_.getPosition(), f, fogparameters);
-+ this.addWeatherPass(framegraphbuilder, p_109606_, p_109604_.getPosition(), f, fogparameters, p_254120_, p_323920_, p_109604_);
+- this.addWeatherPass(framegraphbuilder, p_109604_.getPosition(), f, fogparameters);
++ this.addWeatherPass(framegraphbuilder, p_109604_.getPosition(), f, fogparameters, p_254120_, p_323920_, p_109604_);
if (postchain != null) {
postchain.addToFrame(framegraphbuilder, i, j, this.targets);
}
-@@ -576,7 +_,9 @@
+@@ -571,7 +_,9 @@
double d2 = vec3.z();
p_362234_.push("terrain");
this.renderSectionLayer(RenderType.solid(), d0, d1, d2, p_362420_, p_361272_);
@@ -37,7 +37,7 @@
this.renderSectionLayer(RenderType.cutout(), d0, d1, d2, p_362420_, p_361272_);
if (this.level.effects().constantAmbientLight()) {
Lighting.setupNetherLevel();
-@@ -608,6 +_,7 @@
+@@ -603,6 +_,7 @@
p_362234_.popPush("entities");
this.renderEntities(posestack, multibuffersource$buffersource, p_363453_, p_360931_, this.visibleEntities);
multibuffersource$buffersource.endLastBatch();
@@ -45,7 +45,7 @@
this.checkPoseStack(posestack);
p_362234_.popPush("blockentities");
this.renderBlockEntities(posestack, multibuffersource$buffersource, multibuffersource$buffersource1, p_363453_, f);
-@@ -624,6 +_,7 @@
+@@ -619,6 +_,7 @@
multibuffersource$buffersource.endBatch(Sheets.hangingSignSheet());
multibuffersource$buffersource.endBatch(Sheets.chestSheet());
this.renderBuffers.outlineBufferSource().endOutlineBatch();
@@ -53,45 +53,43 @@
if (p_363964_) {
this.renderBlockOutline(p_363453_, multibuffersource$buffersource, posestack, false);
}
-@@ -644,6 +_,11 @@
+@@ -639,6 +_,11 @@
multibuffersource$buffersource1.endBatch();
this.checkPoseStack(posestack);
multibuffersource$buffersource.endBatch(RenderType.waterMask());
+ // Neo: in Fast/Fancy, render solid particles before translucent geometry so they don't disappear underwater (MC-161917)
+ if (this.targets.particles == null) {
+ p_362234_.popPush("solid_particles");
-+ this.minecraft.particleEngine.render(this.minecraft.gameRenderer.lightTexture(), p_363453_, f, p_366590_, type -> !type.isTranslucent());
++ this.minecraft.particleEngine.render(p_363453_, f, this.renderBuffers.bufferSource(), p_366590_, type -> !type.translucent());
+ }
multibuffersource$buffersource.endBatch();
if (resourcehandle1 != null) {
resourcehandle1.get().setClearColor(0.0F, 0.0F, 0.0F, 0.0F);
-@@ -664,7 +_,15 @@
+@@ -659,7 +_,15 @@
});
}
+ /**
-+ * @deprecated Neo: use {@link #addParticlesPass(FrameGraphBuilder, Camera, LightTexture, float, FogParameters, Frustum, Matrix4f, Matrix4f)} instead
++ * @deprecated Neo: use {@link #addParticlesPass(FrameGraphBuilder, Camera, float, FogParameters, Frustum, Matrix4f, Matrix4f)} instead
+ */
+ @Deprecated
- private void addParticlesPass(FrameGraphBuilder p_363357_, Camera p_365299_, LightTexture p_364308_, float p_364282_, FogParameters p_362149_) {
-+ addParticlesPass(p_363357_, p_365299_, p_364308_, p_364282_, p_362149_, this.capturedFrustum != null ? this.capturedFrustum : this.cullingFrustum, RenderSystem.getModelViewMatrix(), RenderSystem.getProjectionMatrix());
+ private void addParticlesPass(FrameGraphBuilder p_363357_, Camera p_365299_, float p_364282_, FogParameters p_362149_) {
++ addParticlesPass(p_363357_, p_365299_, p_364282_, p_362149_, this.capturedFrustum != null ? this.capturedFrustum : this.cullingFrustum, RenderSystem.getModelViewMatrix(), RenderSystem.getProjectionMatrix());
+ }
+
-+ private void addParticlesPass(FrameGraphBuilder p_363357_, Camera p_365299_, LightTexture p_364308_, float p_364282_, FogParameters p_362149_, Frustum frustum, Matrix4f modelViewMatrix, Matrix4f projectionMatrix) {
++ private void addParticlesPass(FrameGraphBuilder p_363357_, Camera p_365299_, float p_364282_, FogParameters p_362149_, Frustum frustum, Matrix4f modelViewMatrix, Matrix4f projectionMatrix) {
FramePass framepass = p_363357_.addPass("particles");
if (this.targets.particles != null) {
this.targets.particles = framepass.readsAndWrites(this.targets.particles);
-@@ -684,7 +_,8 @@
+@@ -679,6 +_,7 @@
}
- RenderStateShard.PARTICLES_TARGET.setupRenderState();
-- this.minecraft.particleEngine.render(p_364308_, p_365299_, p_364282_);
-+ this.minecraft.particleEngine.render(p_364308_, p_365299_, p_364282_, frustum, resourcehandle1 == null ? type -> type.isTranslucent() : type -> true); // Neo: only render translucent particles here in Fast/Fancy
+ this.minecraft.particleEngine.render(p_365299_, p_364282_, this.renderBuffers.bufferSource());
+ net.neoforged.neoforge.client.ClientHooks.dispatchRenderStage(net.neoforged.neoforge.client.event.RenderLevelStageEvent.Stage.AFTER_PARTICLES, this, null, modelViewMatrix, projectionMatrix, this.ticks, p_365299_, getFrustum());
- RenderStateShard.PARTICLES_TARGET.clearRenderState();
});
}
-@@ -713,11 +_,20 @@
+
+@@ -706,11 +_,20 @@
resourcehandle.get().clear();
}
@@ -101,26 +99,26 @@
}
+ /**
-+ * @deprecated Neo: use {@link #addWeatherPass(FrameGraphBuilder, LightTexture, Vec3, float, FogParameters, Matrix4f, Matrix4f, Camera)} instead
++ * @deprecated Neo: use {@link #addWeatherPass(FrameGraphBuilder, Vec3, float, FogParameters, Matrix4f, Matrix4f, Camera)} instead
+ */
+ @Deprecated
- private void addWeatherPass(FrameGraphBuilder p_364025_, LightTexture p_361536_, Vec3 p_360771_, float p_362434_, FogParameters p_360974_) {
-+ addWeatherPass(p_364025_, p_361536_, p_360771_, p_362434_, p_360974_, RenderSystem.getModelViewMatrix(), RenderSystem.getProjectionMatrix(), this.minecraft.gameRenderer.getMainCamera());
+ private void addWeatherPass(FrameGraphBuilder p_364025_, Vec3 p_360771_, float p_362434_, FogParameters p_360974_) {
++ addWeatherPass(p_364025_, p_360771_, p_362434_, p_360974_, RenderSystem.getModelViewMatrix(), RenderSystem.getProjectionMatrix(), this.minecraft.gameRenderer.getMainCamera());
+ }
+
-+ private void addWeatherPass(FrameGraphBuilder p_364025_, LightTexture p_361536_, Vec3 p_360771_, float p_362434_, FogParameters p_360974_, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, Camera camera) {
++ private void addWeatherPass(FrameGraphBuilder p_364025_, Vec3 p_360771_, float p_362434_, FogParameters p_360974_, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, Camera camera) {
int i = this.minecraft.options.getEffectiveRenderDistance() * 16;
float f = this.minecraft.gameRenderer.getDepthFar();
FramePass framepass = p_364025_.addPass("weather");
-@@ -731,6 +_,7 @@
+@@ -724,6 +_,7 @@
RenderSystem.setShaderFog(p_360974_);
- RenderStateShard.WEATHER_TARGET.setupRenderState();
- this.weatherEffectRenderer.render(this.minecraft.level, p_361536_, this.ticks, p_362434_, p_360771_);
+ MultiBufferSource.BufferSource multibuffersource$buffersource = this.renderBuffers.bufferSource();
+ this.weatherEffectRenderer.render(this.minecraft.level, multibuffersource$buffersource, this.ticks, p_362434_, p_360771_);
+ net.neoforged.neoforge.client.ClientHooks.dispatchRenderStage(net.neoforged.neoforge.client.event.RenderLevelStageEvent.Stage.AFTER_WEATHER, this, null, modelViewMatrix, projectionMatrix, this.ticks, camera, getFrustum());
this.worldBorderRenderer.render(this.level.getWorldBorder(), p_360771_, (double)i, (double)f);
- RenderStateShard.WEATHER_TARGET.clearRenderState();
+ multibuffersource$buffersource.endBatch();
});
-@@ -775,11 +_,14 @@
+@@ -768,11 +_,14 @@
|| p_363510_.isDetached()
|| p_363510_.getEntity() instanceof LivingEntity && ((LivingEntity)p_363510_.getEntity()).isSleeping()
)
@@ -136,7 +134,7 @@
}
}
}
-@@ -825,10 +_,12 @@
+@@ -818,10 +_,12 @@
double d1 = vec3.y();
double d2 = vec3.z();
@@ -149,7 +147,7 @@
BlockPos blockpos = blockentity.getBlockPos();
MultiBufferSource multibuffersource = p_363819_;
p_362832_.pushPose();
-@@ -856,6 +_,7 @@
+@@ -849,6 +_,7 @@
synchronized (this.globalBlockEntities) {
for (BlockEntity blockentity1 : this.globalBlockEntities) {
@@ -157,7 +155,7 @@
BlockPos blockpos1 = blockentity1.getBlockPos();
p_362832_.pushPose();
p_362832_.translate((double)blockpos1.getX() - d0, (double)blockpos1.getY() - d1, (double)blockpos1.getZ() - d2);
-@@ -883,9 +_,10 @@
+@@ -876,9 +_,10 @@
VertexConsumer vertexconsumer = new SheetedDecalTextureGenerator(
p_365216_.getBuffer(ModelBakery.DESTROY_TYPES.get(i)), posestack$pose, 1.0F
);
@@ -169,7 +167,7 @@
p_363901_.popPose();
}
}
-@@ -897,8 +_,9 @@
+@@ -890,8 +_,9 @@
if (blockhitresult.getType() != HitResult.Type.MISS) {
BlockPos blockpos = blockhitresult.getBlockPos();
BlockState blockstate = this.level.getBlockState(blockpos);
@@ -180,7 +178,7 @@
if (flag != p_361698_) {
return;
}
-@@ -1026,6 +_,7 @@
+@@ -1019,6 +_,7 @@
compiledshaderprogram.clear();
VertexBuffer.unbind();
zone.close();
@@ -188,7 +186,7 @@
p_294513_.clearRenderState();
}
}
-@@ -1066,7 +_,15 @@
+@@ -1059,7 +_,15 @@
}
}
@@ -204,15 +202,15 @@
FogType fogtype = p_362177_.getFluidInCamera();
if (fogtype != FogType.POWDER_SNOW && fogtype != FogType.LAVA && !this.doesMobEffectBlockSky(p_362177_)) {
DimensionSpecialEffects dimensionspecialeffects = this.level.effects();
-@@ -1075,6 +_,7 @@
+@@ -1068,6 +_,7 @@
FramePass framepass = p_362870_.addPass("sky");
this.targets.main = framepass.readsAndWrites(this.targets.main);
framepass.executes(() -> {
+ if (!level.effects().renderSky(level, ticks, p_363799_, modelViewMatrix, p_362177_, projectionMatrix, () -> RenderSystem.setShaderFog(p_364999_))) {
RenderSystem.setShaderFog(p_364999_);
- RenderStateShard.MAIN_TARGET.setupRenderState();
- PoseStack posestack = new PoseStack();
-@@ -1102,6 +_,8 @@
+ if (dimensionspecialeffects$skytype == DimensionSpecialEffects.SkyType.END) {
+ this.skyRenderer.renderEndSky();
+@@ -1095,6 +_,8 @@
this.skyRenderer.renderDarkDisc(posestack);
}
}
@@ -221,7 +219,7 @@
});
}
}
-@@ -1413,7 +_,7 @@
+@@ -1387,7 +_,7 @@
} else {
int i = p_109538_.getBrightness(LightLayer.SKY, p_109540_);
int j = p_109538_.getBrightness(LightLayer.BLOCK, p_109540_);
@@ -230,7 +228,7 @@
if (j < k) {
j = k;
}
-@@ -1475,5 +_,22 @@
+@@ -1449,5 +_,22 @@
public CloudRenderer getCloudRenderer() {
return this.cloudRenderer;
diff --git a/patches/net/minecraft/client/renderer/MapRenderer.java.patch b/patches/net/minecraft/client/renderer/MapRenderer.java.patch
index 8d0c96067c..8a46ee0351 100644
--- a/patches/net/minecraft/client/renderer/MapRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/MapRenderer.java.patch
@@ -11,7 +11,16 @@
p_362483_.pushPose();
p_362483_.translate(
(float)maprenderstate$mapdecorationrenderstate.x / 2.0F + 64.0F, (float)maprenderstate$mapdecorationrenderstate.y / 2.0F + 64.0F, -0.02F
-@@ -116,6 +_,7 @@
+@@ -109,13 +_,15 @@
+ p_364922_.texture = this.mapTextureManager.prepareMapTexture(p_361383_, p_363500_);
+ p_364922_.decorations.clear();
+
++ net.neoforged.neoforge.client.renderstate.RenderStateExtensions.onUpdateMapRenderState(p_363500_, p_364922_);
+ for (MapDecoration mapdecoration : p_363500_.getDecorations()) {
+- p_364922_.decorations.add(this.extractDecorationRenderState(mapdecoration));
++ p_364922_.decorations.add(net.neoforged.neoforge.client.renderstate.RenderStateExtensions.onUpdateMapDecorationRenderState(mapdecoration.type(), p_363500_, p_364922_, this.extractDecorationRenderState(mapdecoration)));
+ }
+ }
private MapRenderState.MapDecorationRenderState extractDecorationRenderState(MapDecoration p_364175_) {
MapRenderState.MapDecorationRenderState maprenderstate$mapdecorationrenderstate = new MapRenderState.MapDecorationRenderState();
diff --git a/patches/net/minecraft/client/renderer/RenderType.java.patch b/patches/net/minecraft/client/renderer/RenderType.java.patch
index 3186edd211..cff5c841ea 100644
--- a/patches/net/minecraft/client/renderer/RenderType.java.patch
+++ b/patches/net/minecraft/client/renderer/RenderType.java.patch
@@ -1,52 +1,9 @@
--- a/net/minecraft/client/renderer/RenderType.java
+++ b/net/minecraft/client/renderer/RenderType.java
-@@ -1098,7 +_,7 @@
- }
-
- public static RenderType text(ResourceLocation p_110498_) {
-- return TEXT.apply(p_110498_);
-+ return net.neoforged.neoforge.client.NeoForgeRenderTypes.getText(p_110498_);
- }
-
- public static RenderType textBackground() {
-@@ -1106,19 +_,19 @@
- }
-
- public static RenderType textIntensity(ResourceLocation p_173238_) {
-- return TEXT_INTENSITY.apply(p_173238_);
-+ return net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextIntensity(p_173238_);
- }
-
- public static RenderType textPolygonOffset(ResourceLocation p_181445_) {
-- return TEXT_POLYGON_OFFSET.apply(p_181445_);
-+ return net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextPolygonOffset(p_181445_);
- }
-
- public static RenderType textIntensityPolygonOffset(ResourceLocation p_181447_) {
-- return TEXT_INTENSITY_POLYGON_OFFSET.apply(p_181447_);
-+ return net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextIntensityPolygonOffset(p_181447_);
- }
-
- public static RenderType textSeeThrough(ResourceLocation p_110501_) {
-- return TEXT_SEE_THROUGH.apply(p_110501_);
-+ return net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextSeeThrough(p_110501_);
- }
-
- public static RenderType textBackgroundSeeThrough() {
-@@ -1126,7 +_,7 @@
- }
-
- public static RenderType textIntensitySeeThrough(ResourceLocation p_173241_) {
-- return TEXT_INTENSITY_SEE_THROUGH.apply(p_173241_);
-+ return net.neoforged.neoforge.client.NeoForgeRenderTypes.getTextIntensitySeeThrough(p_173241_);
- }
-
- public static RenderType lightning() {
-@@ -1622,5 +_,17 @@
- public String toString() {
+@@ -1847,4 +_,16 @@
return this.name;
}
-+ }
+ }
+
+ // Neo: Assign internal IDs for RenderType to be used in rendering
+ private int chunkLayerId = -1;
@@ -58,5 +15,5 @@
+ int i = 0;
+ for (var layer : chunkBufferLayers())
+ layer.chunkLayerId = i++;
- }
++ }
}
diff --git a/patches/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch b/patches/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch
index 901854e375..e2f0f92603 100644
--- a/patches/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/ScreenEffectRenderer.java.patch
@@ -1,32 +1,32 @@
--- a/net/minecraft/client/renderer/ScreenEffectRenderer.java
+++ b/net/minecraft/client/renderer/ScreenEffectRenderer.java
-@@ -30,18 +_,22 @@
- public static void renderScreenEffect(Minecraft p_110719_, PoseStack p_110720_) {
+@@ -26,18 +_,22 @@
+ public static void renderScreenEffect(Minecraft p_110719_, PoseStack p_110720_, MultiBufferSource p_382889_) {
Player player = p_110719_.player;
if (!player.noPhysics) {
- BlockState blockstate = getViewBlockingState(player);
- if (blockstate != null) {
-- renderTex(p_110719_.getBlockRenderer().getBlockModelShaper().getParticleIcon(blockstate), p_110720_);
+- renderTex(p_110719_.getBlockRenderer().getBlockModelShaper().getParticleIcon(blockstate), p_110720_, p_382889_);
+ org.apache.commons.lang3.tuple.Pair overlay = getOverlayBlock(player);
+ if (overlay != null) {
+ if (!net.neoforged.neoforge.client.ClientHooks.renderBlockOverlay(player, p_110720_, net.neoforged.neoforge.client.event.RenderBlockScreenEffectEvent.OverlayType.BLOCK, overlay.getLeft(), overlay.getRight()))
-+ renderTex(p_110719_.getBlockRenderer().getBlockModelShaper().getTexture(overlay.getLeft(), p_110719_.level, overlay.getRight()), p_110720_);
++ renderTex(p_110719_.getBlockRenderer().getBlockModelShaper().getTexture(overlay.getLeft(), p_110719_.level, overlay.getRight()), p_110720_, p_382889_);
}
}
if (!p_110719_.player.isSpectator()) {
if (p_110719_.player.isEyeInFluid(FluidTags.WATER)) {
+ if (!net.neoforged.neoforge.client.ClientHooks.renderWaterOverlay(player, p_110720_))
- renderWater(p_110719_, p_110720_);
+ renderWater(p_110719_, p_110720_, p_382889_);
}
-+ else if (!player.getEyeInFluidType().isAir()) net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions.of(player.getEyeInFluidType()).renderOverlay(p_110719_, p_110720_);
++ else if (!player.getEyeInFluidType().isAir()) net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions.of(player.getEyeInFluidType()).renderOverlay(p_110719_, p_110720_, p_382889_);
if (p_110719_.player.isOnFire()) {
+ if (!net.neoforged.neoforge.client.ClientHooks.renderFireOverlay(player, p_110720_))
- renderFire(p_110719_, p_110720_);
+ renderFire(p_110720_, p_382889_);
}
}
-@@ -49,6 +_,11 @@
+@@ -45,6 +_,11 @@
@Nullable
private static BlockState getViewBlockingState(Player p_110717_) {
@@ -38,7 +38,7 @@
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
for (int i = 0; i < 8; i++) {
-@@ -58,7 +_,7 @@
+@@ -54,7 +_,7 @@
blockpos$mutableblockpos.set(d0, d1, d2);
BlockState blockstate = p_110717_.level().getBlockState(blockpos$mutableblockpos);
if (blockstate.getRenderShape() != RenderShape.INVISIBLE && blockstate.isViewBlocking(p_110717_.level(), blockpos$mutableblockpos)) {
@@ -47,17 +47,23 @@
}
}
-@@ -88,8 +_,12 @@
+@@ -82,6 +_,10 @@
}
- private static void renderWater(Minecraft p_110726_, PoseStack p_110727_) {
-+ renderFluid(p_110726_, p_110727_, UNDERWATER_LOCATION);
+ private static void renderWater(Minecraft p_110726_, PoseStack p_110727_, MultiBufferSource p_383128_) {
++ renderFluid(p_110726_, p_110727_, p_383128_, UNDERWATER_LOCATION);
+ }
+
-+ public static void renderFluid(Minecraft p_110726_, PoseStack p_110727_, ResourceLocation texture) {
- RenderSystem.setShader(CoreShaders.POSITION_TEX);
-- RenderSystem.setShaderTexture(0, UNDERWATER_LOCATION);
-+ RenderSystem.setShaderTexture(0, texture);
++ public static void renderFluid(Minecraft p_110726_, PoseStack p_110727_, MultiBufferSource p_383128_, ResourceLocation texture) {
BlockPos blockpos = BlockPos.containing(p_110726_.player.getX(), p_110726_.player.getEyeY(), p_110726_.player.getZ());
float f = LightTexture.getBrightness(p_110726_.player.level().dimensionType(), p_110726_.player.level().getMaxLocalRawBrightness(blockpos));
- RenderSystem.enableBlend();
+ int i = ARGB.colorFromFloat(0.1F, f, f, f);
+@@ -94,7 +_,7 @@
+ float f7 = -p_110726_.player.getYRot() / 64.0F;
+ float f8 = p_110726_.player.getXRot() / 64.0F;
+ Matrix4f matrix4f = p_110727_.last().pose();
+- VertexConsumer vertexconsumer = p_383128_.getBuffer(RenderType.blockScreenEffect(UNDERWATER_LOCATION));
++ VertexConsumer vertexconsumer = p_383128_.getBuffer(RenderType.blockScreenEffect(texture));
+ vertexconsumer.addVertex(matrix4f, -1.0F, -1.0F, -0.5F).setUv(4.0F + f7, 4.0F + f8).setColor(i);
+ vertexconsumer.addVertex(matrix4f, 1.0F, -1.0F, -0.5F).setUv(0.0F + f7, 4.0F + f8).setColor(i);
+ vertexconsumer.addVertex(matrix4f, 1.0F, 1.0F, -0.5F).setUv(0.0F + f7, 0.0F + f8).setColor(i);
diff --git a/patches/net/minecraft/client/renderer/ShaderManager.java.patch b/patches/net/minecraft/client/renderer/ShaderManager.java.patch
index 4e634d23e7..d6621b2f20 100644
--- a/patches/net/minecraft/client/renderer/ShaderManager.java.patch
+++ b/patches/net/minecraft/client/renderer/ShaderManager.java.patch
@@ -1,11 +1,11 @@
--- a/net/minecraft/client/renderer/ShaderManager.java
+++ b/net/minecraft/client/renderer/ShaderManager.java
@@ -169,7 +_,7 @@
+ protected void apply(ShaderManager.Configs p_366597_, ResourceManager p_366533_, ProfilerFiller p_366866_) {
ShaderManager.CompilationCache shadermanager$compilationcache = new ShaderManager.CompilationCache(p_366597_);
Map map = new HashMap<>();
+- Set set = new HashSet<>(CoreShaders.getProgramsToPreload());
++ Set set = new HashSet<>(net.neoforged.neoforge.client.CoreShaderManager.getProgramsToPreload());
-- for (ShaderProgram shaderprogram : CoreShaders.getProgramsToPreload()) {
-+ for (ShaderProgram shaderprogram : net.neoforged.neoforge.client.CoreShaderManager.getProgramsToPreload()) {
- try {
- shadermanager$compilationcache.programs.put(shaderprogram, Optional.of(shadermanager$compilationcache.compileProgram(shaderprogram)));
- } catch (ShaderManager.CompilationException shadermanager$compilationexception) {
+ for (PostChainConfig postchainconfig : p_366597_.postChains.values()) {
+ for (PostChainConfig.Pass postchainconfig$pass : postchainconfig.passes()) {
diff --git a/patches/net/minecraft/client/renderer/Sheets.java.patch b/patches/net/minecraft/client/renderer/Sheets.java.patch
index cb01eb86db..16c8df9f7a 100644
--- a/patches/net/minecraft/client/renderer/Sheets.java.patch
+++ b/patches/net/minecraft/client/renderer/Sheets.java.patch
@@ -1,22 +1,24 @@
--- a/net/minecraft/client/renderer/Sheets.java
+++ b/net/minecraft/client/renderer/Sheets.java
-@@ -141,11 +_,13 @@
+@@ -156,7 +_,7 @@
}
private static Material createSignMaterial(WoodType p_173386_) {
-- return new Material(SIGN_SHEET, ResourceLocation.withDefaultNamespace("entity/signs/" + p_173386_.name()));
-+ ResourceLocation location = ResourceLocation.parse(p_173386_.name());
-+ return new Material(SIGN_SHEET, ResourceLocation.fromNamespaceAndPath(location.getNamespace(), "entity/signs/" + location.getPath()));
+- return createSignMaterial(ResourceLocation.withDefaultNamespace(p_173386_.name()));
++ return createSignMaterial(ResourceLocation.parse(p_173386_.name()));
+ }
+
+ public static Material createSignMaterial(ResourceLocation p_389416_) {
+@@ -164,7 +_,7 @@
}
private static Material createHangingSignMaterial(WoodType p_251735_) {
-- return new Material(SIGN_SHEET, ResourceLocation.withDefaultNamespace("entity/signs/hanging/" + p_251735_.name()));
-+ ResourceLocation location = ResourceLocation.parse(p_251735_.name());
-+ return new Material(SIGN_SHEET, ResourceLocation.fromNamespaceAndPath(location.getNamespace(), "entity/signs/hanging/" + location.getPath()));
+- return createHangingSignMaterial(ResourceLocation.withDefaultNamespace(p_251735_.name()));
++ return createHangingSignMaterial(ResourceLocation.parse(p_251735_.name()));
}
- public static Material getSignMaterial(WoodType p_173382_) {
-@@ -204,6 +_,23 @@
+ public static Material createHangingSignMaterial(ResourceLocation p_389413_) {
+@@ -231,6 +_,23 @@
case SINGLE:
default:
return p_110773_;
diff --git a/patches/net/minecraft/client/renderer/WeatherEffectRenderer.java.patch b/patches/net/minecraft/client/renderer/WeatherEffectRenderer.java.patch
index 180f282591..9d71f4e7ca 100644
--- a/patches/net/minecraft/client/renderer/WeatherEffectRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/WeatherEffectRenderer.java.patch
@@ -1,15 +1,15 @@
--- a/net/minecraft/client/renderer/WeatherEffectRenderer.java
+++ b/net/minecraft/client/renderer/WeatherEffectRenderer.java
-@@ -61,6 +_,8 @@
+@@ -56,6 +_,8 @@
}
- public void render(Level p_364994_, LightTexture p_363130_, int p_363664_, float p_361655_, Vec3 p_363375_) {
-+ if (((ClientLevel) p_364994_).effects().renderSnowAndRain((ClientLevel) p_364994_, p_363664_, p_361655_, p_363130_, p_363375_.x, p_363375_.y, p_363375_.z))
+ public void render(Level p_364994_, MultiBufferSource p_383071_, int p_363664_, float p_361655_, Vec3 p_363375_) {
++ if (((ClientLevel) p_364994_).effects().renderSnowAndRain((ClientLevel) p_364994_, p_363664_, p_361655_, p_363375_.x, p_363375_.y, p_363375_.z))
+ return;
float f = p_364994_.getRainLevel(p_361655_);
if (!(f <= 0.0F)) {
int i = Minecraft.useFancyGraphics() ? 10 : 5;
-@@ -201,6 +_,8 @@
+@@ -180,6 +_,8 @@
}
public void tickRainParticles(ClientLevel p_361823_, Camera p_364990_, int p_361788_, ParticleStatus p_363302_) {
diff --git a/patches/net/minecraft/client/renderer/block/BlockRenderDispatcher.java.patch b/patches/net/minecraft/client/renderer/block/BlockRenderDispatcher.java.patch
index 5ee7f47292..144c236c6b 100644
--- a/patches/net/minecraft/client/renderer/block/BlockRenderDispatcher.java.patch
+++ b/patches/net/minecraft/client/renderer/block/BlockRenderDispatcher.java.patch
@@ -2,7 +2,7 @@
+++ b/net/minecraft/client/renderer/block/BlockRenderDispatcher.java
@@ -37,7 +_,7 @@
this.blockModelShaper = p_173399_;
- this.blockEntityRenderer = p_173400_;
+ this.specialBlockModelRenderer = p_386717_;
this.blockColors = p_173401_;
- this.modelRenderer = new ModelBlockRenderer(this.blockColors);
+ this.modelRenderer = new net.neoforged.neoforge.client.model.lighting.LightPipelineAwareModelBlockRenderer(this.blockColors);
@@ -71,7 +71,7 @@
this.liquidBlockRenderer.tesselate(p_234365_, p_234364_, p_234366_, p_234367_, p_234368_);
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "Tesselating liquid in world");
-@@ -104,7 +_,11 @@
+@@ -104,25 +_,33 @@
return this.blockModelShaper.getBlockModel(p_110911_);
}
@@ -79,41 +79,38 @@
public void renderSingleBlock(BlockState p_110913_, PoseStack p_110914_, MultiBufferSource p_110915_, int p_110916_, int p_110917_) {
+ renderSingleBlock(p_110913_, p_110914_, p_110915_, p_110916_, p_110917_, net.neoforged.neoforge.client.model.data.ModelData.EMPTY, null);
+ }
-+ public void renderSingleBlock(BlockState p_110913_, PoseStack p_110914_, MultiBufferSource p_110915_, int p_110916_, int p_110917_, net.neoforged.neoforge.client.model.data.ModelData modelData, net.minecraft.client.renderer.RenderType renderType) {
++
++ public void renderSingleBlock(BlockState p_110913_, PoseStack p_110914_, MultiBufferSource p_110915_, int p_110916_, int p_110917_, net.neoforged.neoforge.client.model.data.ModelData modelData, @org.jetbrains.annotations.Nullable net.minecraft.client.renderer.RenderType renderType) {
RenderShape rendershape = p_110913_.getRenderShape();
if (rendershape != RenderShape.INVISIBLE) {
- switch (rendershape) {
-@@ -114,22 +_,25 @@
- float f = (float)(i >> 16 & 0xFF) / 255.0F;
- float f1 = (float)(i >> 8 & 0xFF) / 255.0F;
- float f2 = (float)(i & 0xFF) / 255.0F;
-+ for (net.minecraft.client.renderer.RenderType rt : bakedmodel.getRenderTypes(p_110913_, RandomSource.create(42), modelData))
- this.modelRenderer
- .renderModel(
- p_110914_.last(),
-- p_110915_.getBuffer(ItemBlockRenderTypes.getRenderType(p_110913_)),
-+ p_110915_.getBuffer(renderType != null ? renderType : net.neoforged.neoforge.client.RenderTypeHelper.getEntityRenderType(rt)),
- p_110913_,
- bakedmodel,
- f,
- f1,
- f2,
- p_110916_,
-- p_110917_
-+ p_110917_,
-+ modelData,
-+ rt
- );
- break;
- case ENTITYBLOCK_ANIMATED:
-- this.blockEntityRenderer
-- .renderByItem(new ItemStack(p_110913_.getBlock()), ItemDisplayContext.NONE, p_110914_, p_110915_, p_110916_, p_110917_);
-+ ItemStack stack = new ItemStack(p_110913_.getBlock());
-+ net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(stack).getCustomRenderer().renderByItem(stack, ItemDisplayContext.NONE, p_110914_, p_110915_, p_110916_, p_110917_);
- }
+ BakedModel bakedmodel = this.getBlockModel(p_110913_);
+ int i = this.blockColors.getColor(p_110913_, null, null, 0);
+- float f = (float)(i >> 16 & 0xFF) / 255.0F;
+- float f1 = (float)(i >> 8 & 0xFF) / 255.0F;
+- float f2 = (float)(i & 0xFF) / 255.0F;
++ float f = (float) (i >> 16 & 0xFF) / 255.0F;
++ float f1 = (float) (i >> 8 & 0xFF) / 255.0F;
++ float f2 = (float) (i & 0xFF) / 255.0F;
++ for (net.minecraft.client.renderer.RenderType rt : bakedmodel.getRenderTypes(p_110913_, RandomSource.create(42), modelData))
+ this.modelRenderer
+ .renderModel(
+ p_110914_.last(),
+- p_110915_.getBuffer(ItemBlockRenderTypes.getRenderType(p_110913_)),
++ p_110915_.getBuffer(renderType != null ? renderType : net.neoforged.neoforge.client.RenderTypeHelper.getEntityRenderType(rt)),
+ p_110913_,
+ bakedmodel,
+ f,
+ f1,
+ f2,
+ p_110916_,
+- p_110917_
++ p_110917_,
++ modelData,
++ rt
+ );
+ this.specialBlockModelRenderer.get().renderByBlock(p_110913_.getBlock(), ItemDisplayContext.NONE, p_110914_, p_110915_, p_110916_, p_110917_);
}
- }
-@@ -137,5 +_,9 @@
+@@ -131,5 +_,9 @@
@Override
public void onResourceManagerReload(ResourceManager p_110909_) {
this.liquidBlockRenderer.setupSprites();
diff --git a/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch b/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch
index f2ca146f9c..72aee1fb3e 100644
--- a/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/block/LiquidBlockRenderer.java
+++ b/net/minecraft/client/renderer/block/LiquidBlockRenderer.java
-@@ -37,6 +_,7 @@
+@@ -37,12 +_,17 @@
this.waterIcons[0] = Minecraft.getInstance().getModelManager().getBlockModelShaper().getBlockModel(Blocks.WATER.defaultBlockState()).getParticleIcon();
this.waterIcons[1] = ModelBakery.WATER_FLOW.sprite();
this.waterOverlay = ModelBakery.WATER_OVERLAY.sprite();
@@ -8,8 +8,29 @@
}
private static boolean isNeighborSameFluid(FluidState p_203186_, FluidState p_203187_) {
-@@ -70,8 +_,9 @@
+ return p_203187_.getType().isSame(p_203186_.getType());
+ }
+
++ private static boolean isNeighborStateHidingOverlay(FluidState selfState, BlockState otherState, Direction neighborFace) {
++ return otherState.shouldHideAdjacentFluidFace(neighborFace, selfState);
++ }
++
+ private static boolean isFaceOccludedByState(Direction p_110980_, float p_110981_, BlockState p_110983_) {
+ VoxelShape voxelshape = p_110983_.getFaceOcclusionShape(p_110980_.getOpposite());
+ if (voxelshape == Shapes.empty()) {
+@@ -64,14 +_,20 @@
+ return isFaceOccludedByState(p_110963_.getOpposite(), 1.0F, p_110962_);
+ }
++ /** @deprecated Neo: use overload that accepts BlockState */
+ public static boolean shouldRenderFace(FluidState p_203169_, BlockState p_203170_, Direction p_203171_, FluidState p_203172_) {
+ return !isFaceOccludedBySelf(p_203170_, p_203171_) && !isNeighborSameFluid(p_203169_, p_203172_);
+ }
+
++ public static boolean shouldRenderFace(FluidState fluidState, BlockState selfState, Direction direction, BlockState otherState) {
++ return !isFaceOccludedBySelf(selfState, direction) && !isNeighborStateHidingOverlay(fluidState, otherState, direction.getOpposite());
++ }
++
public void tesselate(BlockAndTintGetter p_234370_, BlockPos p_234371_, VertexConsumer p_234372_, BlockState p_234373_, FluidState p_234374_) {
boolean flag = p_234374_.is(FluidTags.LAVA);
- TextureAtlasSprite[] atextureatlassprite = flag ? this.lavaIcons : this.waterIcons;
@@ -20,6 +41,25 @@
float f = (float)(i >> 16 & 0xFF) / 255.0F;
float f1 = (float)(i >> 8 & 0xFF) / 255.0F;
float f2 = (float)(i & 0xFF) / 255.0F;
+@@ -87,12 +_,12 @@
+ FluidState fluidstate4 = blockstate4.getFluidState();
+ BlockState blockstate5 = p_234370_.getBlockState(p_234371_.relative(Direction.EAST));
+ FluidState fluidstate5 = blockstate5.getFluidState();
+- boolean flag1 = !isNeighborSameFluid(p_234374_, fluidstate1);
+- boolean flag2 = shouldRenderFace(p_234374_, p_234373_, Direction.DOWN, fluidstate) && !isFaceOccludedByNeighbor(Direction.DOWN, 0.8888889F, blockstate);
+- boolean flag3 = shouldRenderFace(p_234374_, p_234373_, Direction.NORTH, fluidstate2);
+- boolean flag4 = shouldRenderFace(p_234374_, p_234373_, Direction.SOUTH, fluidstate3);
+- boolean flag5 = shouldRenderFace(p_234374_, p_234373_, Direction.WEST, fluidstate4);
+- boolean flag6 = shouldRenderFace(p_234374_, p_234373_, Direction.EAST, fluidstate5);
++ boolean flag1 = !isNeighborStateHidingOverlay(p_234374_, blockstate1, Direction.DOWN);
++ boolean flag2 = shouldRenderFace(p_234374_, p_234373_, Direction.DOWN, blockstate) && !isFaceOccludedByNeighbor(Direction.DOWN, 0.8888889F, blockstate);
++ boolean flag3 = shouldRenderFace(p_234374_, p_234373_, Direction.NORTH, blockstate2);
++ boolean flag4 = shouldRenderFace(p_234374_, p_234373_, Direction.SOUTH, blockstate3);
++ boolean flag5 = shouldRenderFace(p_234374_, p_234373_, Direction.WEST, blockstate4);
++ boolean flag6 = shouldRenderFace(p_234374_, p_234373_, Direction.EAST, blockstate5);
+ if (flag1 || flag2 || flag6 || flag5 || flag3 || flag4) {
+ float f3 = p_234370_.getShade(Direction.DOWN, true);
+ float f4 = p_234370_.getShade(Direction.UP, true);
@@ -180,15 +_,15 @@
float f57 = f4 * f;
float f29 = f4 * f1;
diff --git a/patches/net/minecraft/client/renderer/block/model/BakedOverrides.java.patch b/patches/net/minecraft/client/renderer/block/model/BakedOverrides.java.patch
deleted file mode 100644
index 60cb1a713d..0000000000
--- a/patches/net/minecraft/client/renderer/block/model/BakedOverrides.java.patch
+++ /dev/null
@@ -1,38 +0,0 @@
---- a/net/minecraft/client/renderer/block/model/BakedOverrides.java
-+++ b/net/minecraft/client/renderer/block/model/BakedOverrides.java
-@@ -29,7 +_,15 @@
- this.properties = new ResourceLocation[0];
- }
-
-+ /**
-+ * @deprecated Neo: Use {@link #BakedOverrides(ModelBaker, List, java.util.function.Function)}
-+ */
-+ @Deprecated
- public BakedOverrides(ModelBaker p_371950_, List p_371198_) {
-+ this(p_371950_, p_371198_, p_371950_.getModelTextureGetter());
-+ }
-+
-+ public BakedOverrides(ModelBaker p_371950_, List p_371198_, java.util.function.Function spriteGetter) {
- this.properties = p_371198_.stream()
- .flatMap(p_371945_ -> p_371945_.predicates().stream())
- .map(ItemOverride.Predicate::property)
-@@ -45,7 +_,7 @@
-
- for (int j = p_371198_.size() - 1; j >= 0; j--) {
- ItemOverride itemoverride = p_371198_.get(j);
-- BakedModel bakedmodel = p_371950_.bake(itemoverride.model(), BlockModelRotation.X0_Y0);
-+ BakedModel bakedmodel = p_371950_.bake(itemoverride.model(), BlockModelRotation.X0_Y0, spriteGetter);
- BakedOverrides.PropertyMatcher[] abakedoverrides$propertymatcher = itemoverride.predicates().stream().map(p_371644_ -> {
- int k = object2intmap.getInt(p_371644_.property());
- return new BakedOverrides.PropertyMatcher(k, p_371644_.value());
-@@ -80,6 +_,10 @@
- }
-
- return null;
-+ }
-+
-+ public com.google.common.collect.ImmutableList getOverrides() {
-+ return com.google.common.collect.ImmutableList.copyOf(overrides);
- }
-
- @OnlyIn(Dist.CLIENT)
diff --git a/patches/net/minecraft/client/renderer/block/model/BlockElement.java.patch b/patches/net/minecraft/client/renderer/block/model/BlockElement.java.patch
index c19fb03a3e..fbef04d799 100644
--- a/patches/net/minecraft/client/renderer/block/model/BlockElement.java.patch
+++ b/patches/net/minecraft/client/renderer/block/model/BlockElement.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/block/model/BlockElement.java
+++ b/net/minecraft/client/renderer/block/model/BlockElement.java
-@@ -30,6 +_,7 @@
+@@ -31,6 +_,7 @@
public final BlockElementRotation rotation;
public final boolean shade;
public final int lightEmission;
@@ -8,7 +8,7 @@
public BlockElement(Vector3f p_253626_, Vector3f p_254426_, Map p_254454_) {
this(p_253626_, p_254426_, p_254454_, null, true, 0);
-@@ -43,6 +_,10 @@
+@@ -44,6 +_,10 @@
boolean p_361372_,
int p_361908_
) {
@@ -19,7 +19,7 @@
this.from = p_361324_;
this.to = p_363867_;
this.faces = p_362722_;
-@@ -50,6 +_,8 @@
+@@ -51,6 +_,8 @@
this.shade = p_361372_;
this.lightEmission = p_361908_;
this.fillUvs();
@@ -28,7 +28,7 @@
}
private void fillUvs() {
-@@ -104,7 +_,8 @@
+@@ -98,7 +_,8 @@
}
}
@@ -38,7 +38,7 @@
}
}
-@@ -215,5 +_,13 @@
+@@ -209,5 +_,13 @@
return new Vector3f(afloat[0], afloat[1], afloat[2]);
}
}
diff --git a/patches/net/minecraft/client/renderer/block/model/BlockModel.java.patch b/patches/net/minecraft/client/renderer/block/model/BlockModel.java.patch
index 767bdb3611..cfe8c768a8 100644
--- a/patches/net/minecraft/client/renderer/block/model/BlockModel.java.patch
+++ b/patches/net/minecraft/client/renderer/block/model/BlockModel.java.patch
@@ -1,104 +1,119 @@
--- a/net/minecraft/client/renderer/block/model/BlockModel.java
+++ b/net/minecraft/client/renderer/block/model/BlockModel.java
-@@ -72,9 +_,10 @@
- public BlockModel parent;
- @Nullable
- protected ResourceLocation parentLocation;
-+ public final net.neoforged.neoforge.client.model.geometry.BlockGeometryBakingContext customData = new net.neoforged.neoforge.client.model.geometry.BlockGeometryBakingContext(this);
+@@ -26,15 +_,17 @@
+ import net.neoforged.api.distmarker.OnlyIn;
- public static BlockModel fromStream(Reader p_111462_) {
-- return GsonHelper.fromJson(GSON, p_111462_, BlockModel.class);
-+ return GsonHelper.fromJson(net.neoforged.neoforge.client.model.ExtendedBlockModelDeserializer.INSTANCE, p_111462_, BlockModel.class);
- }
-
- public BlockModel(
-@@ -95,10 +_,15 @@
- this.overrides = p_273099_;
- }
+ @OnlyIn(Dist.CLIENT)
+-public class BlockModel implements UnbakedModel {
++public class BlockModel implements UnbakedModel, net.neoforged.neoforge.client.model.ExtendedUnbakedModel {
+ @VisibleForTesting
+ public static final Gson GSON = new GsonBuilder()
++ .registerTypeHierarchyAdapter(UnbakedModel.class, new net.neoforged.neoforge.client.model.UnbakedModelParser.Deserializer())
+ .registerTypeAdapter(BlockModel.class, new BlockModel.Deserializer())
+ .registerTypeAdapter(BlockElement.class, new BlockElement.Deserializer())
+ .registerTypeAdapter(BlockElementFace.class, new BlockElementFace.Deserializer())
+ .registerTypeAdapter(BlockFaceUV.class, new BlockFaceUV.Deserializer())
+ .registerTypeAdapter(ItemTransform.class, new ItemTransform.Deserializer())
+ .registerTypeAdapter(ItemTransforms.class, new ItemTransforms.Deserializer())
++ .registerTypeAdapter(com.mojang.math.Transformation.class, new net.neoforged.neoforge.common.util.TransformationHelper.Deserializer())
+ .create();
+ private final List elements;
+ @Nullable
+@@ -49,7 +_,14 @@
+ private UnbakedModel parent;
+ @Nullable
+ private final ResourceLocation parentLocation;
++ @Nullable
++ private final com.mojang.math.Transformation transformation;
++ private final net.neoforged.neoforge.client.RenderTypeGroup renderTypeGroup;
++ /**
++ * @deprecated Neo: use {@link net.neoforged.neoforge.client.model.UnbakedModelParser#parse(Reader)} instead
++ */
+ @Deprecated
- public List getElements() {
-+ if (customData.hasCustomGeometry()) return java.util.Collections.emptyList();
- return this.elements.isEmpty() && this.parent != null ? this.parent.getElements() : this.elements;
+ public static BlockModel fromStream(Reader p_111462_) {
+ return GsonHelper.fromJson(GSON, p_111462_, BlockModel.class);
}
-
-+ @Nullable
-+ public ResourceLocation getParentLocation() { return parentLocation; }
+@@ -62,12 +_,27 @@
+ @Nullable UnbakedModel.GuiLight p_387948_,
+ @Nullable ItemTransforms p_273480_
+ ) {
++ this(p_273263_, p_272668_, p_386899_, p_272676_, p_387948_, p_273480_, null, net.neoforged.neoforge.client.RenderTypeGroup.EMPTY);
++ }
+
- public boolean hasAmbientOcclusion() {
- if (this.hasAmbientOcclusion != null) {
- return this.hasAmbientOcclusion;
-@@ -132,10 +_,18 @@
++ public BlockModel(
++ @Nullable ResourceLocation p_273263_,
++ List p_272668_,
++ TextureSlots.Data p_386899_,
++ @Nullable Boolean p_272676_,
++ @Nullable UnbakedModel.GuiLight p_387948_,
++ @Nullable ItemTransforms p_273480_,
++ @Nullable com.mojang.math.Transformation transformation,
++ net.neoforged.neoforge.client.RenderTypeGroup renderTypeGroup
++ ) {
+ this.elements = p_272668_;
+ this.hasAmbientOcclusion = p_272676_;
+ this.guiLight = p_387948_;
+ this.textureSlots = p_386899_;
+ this.parentLocation = p_273263_;
+ this.transforms = p_273480_;
++ this.transformation = transformation;
++ this.renderTypeGroup = renderTypeGroup;
+ }
- this.parent = blockmodel;
- }
-+
-+ if (customData.hasCustomGeometry()) {
-+ customData.getCustomGeometry().resolveDependencies(p_361203_, customData);
-+ }
+ @Nullable
+@@ -107,10 +_,12 @@
}
@Override
- public BakedModel bake(ModelBaker p_252120_, Function p_250023_, ModelState p_251130_) {
-+ var customGeometry = this.customData.getCustomGeometry();
-+ if (customGeometry != null) {
-+ return customGeometry.bake(this.customData, p_252120_, p_250023_, p_251130_, this.overrides);
-+ }
- return this.bake(p_250023_, p_251130_, true);
+- public BakedModel bake(TextureSlots p_387258_, ModelBaker p_388168_, ModelState p_111453_, boolean p_111455_, boolean p_387632_, ItemTransforms p_386577_) {
++ public BakedModel bake(TextureSlots p_387258_, ModelBaker p_388168_, ModelState p_111453_, boolean p_111455_, boolean p_387632_, ItemTransforms p_386577_, net.minecraft.util.context.ContextMap additionalProperties) {
+ return this.elements.isEmpty() && this.parent != null
+- ? this.parent.bake(p_387258_, p_388168_, p_111453_, p_111455_, p_387632_, p_386577_)
+- : SimpleBakedModel.bakeElements(this.elements, p_387258_, p_388168_.sprites(), p_111453_, p_111455_, p_387632_, true, p_386577_);
++ ? this.parent.bake(p_387258_, p_388168_, p_111453_, p_111455_, p_387632_, p_386577_, additionalProperties)
++ : SimpleBakedModel.bakeElements(this.elements, p_387258_, p_388168_.sprites(), p_111453_, p_111455_, p_387632_, true, p_386577_,
++ additionalProperties.getOrDefault(net.neoforged.neoforge.client.model.NeoForgeModelProperties.TRANSFORM, com.mojang.math.Transformation.identity()),
++ additionalProperties.getOrDefault(net.neoforged.neoforge.client.model.NeoForgeModelProperties.RENDER_TYPE, net.neoforged.neoforge.client.RenderTypeGroup.EMPTY));
}
-@@ -146,6 +_,10 @@
- } else {
- SimpleBakedModel.Builder simplebakedmodel$builder = new SimpleBakedModel.Builder(this, p_111455_).particle(textureatlassprite);
+ @Nullable
+@@ -125,6 +_,16 @@
+ return this.parentLocation;
+ }
-+ if (!this.customData.getRootTransform().isIdentity()) {
-+ p_111453_ = net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper.composeRootTransformIntoModelState(p_111453_, this.customData.getRootTransform());
-+ }
++ @Override
++ public void fillAdditionalProperties(net.minecraft.util.context.ContextMap.Builder propertiesBuilder) {
++ if (this.transformation != null) {
++ propertiesBuilder.withParameter(net.neoforged.neoforge.client.model.NeoForgeModelProperties.TRANSFORM, this.transformation);
++ }
++ if (!this.renderTypeGroup.isEmpty()) {
++ propertiesBuilder.withParameter(net.neoforged.neoforge.client.model.NeoForgeModelProperties.RENDER_TYPE, this.renderTypeGroup);
++ }
++ }
+
- for (BlockElement blockelement : this.getElements()) {
- for (Direction direction : blockelement.faces.keySet()) {
- BlockElementFace blockelementface = blockelement.faces.get(direction);
-@@ -161,7 +_,11 @@
- }
+ @OnlyIn(Dist.CLIENT)
+ public static class Deserializer implements JsonDeserializer {
+ public BlockModel deserialize(JsonElement p_111498_, Type p_111499_, JsonDeserializationContext p_111500_) throws JsonParseException {
+@@ -145,7 +_,20 @@
}
-- return simplebakedmodel$builder.build();
-+ var renderTypes = net.neoforged.neoforge.client.RenderTypeGroup.EMPTY;
-+ if (this.customData.getRenderTypeHint() != null) {
-+ renderTypes = this.customData.getRenderType(this.customData.getRenderTypeHint());
-+ }
-+ return simplebakedmodel$builder.build(renderTypes);
- }
- }
-
-@@ -229,7 +_,18 @@
- ItemTransform itemtransform5 = this.getTransform(ItemDisplayContext.GUI);
- ItemTransform itemtransform6 = this.getTransform(ItemDisplayContext.GROUND);
- ItemTransform itemtransform7 = this.getTransform(ItemDisplayContext.FIXED);
-- return new ItemTransforms(itemtransform, itemtransform1, itemtransform2, itemtransform3, itemtransform4, itemtransform5, itemtransform6, itemtransform7);
+ ResourceLocation resourcelocation = s.isEmpty() ? null : ResourceLocation.parse(s);
+- return new BlockModel(resourcelocation, list, textureslots$data, obool, unbakedmodel$guilight, itemtransforms);
+
-+ var builder = com.google.common.collect.ImmutableMap.builder();
-+ for(ItemDisplayContext type : ItemDisplayContext.values()) {
-+ if (type.isModded()) {
-+ var transform = this.getTransform(type);
-+ if (transform != ItemTransform.NO_TRANSFORM) {
-+ builder.put(type, transform);
-+ }
++ com.mojang.math.Transformation rootTransform = null;
++ if (jsonobject.has("transform")) {
++ JsonElement transform = jsonobject.get("transform");
++ rootTransform = p_111500_.deserialize(transform, com.mojang.math.Transformation.class);
+ }
-+ }
+
-+ return new ItemTransforms(itemtransform, itemtransform1, itemtransform2, itemtransform3, itemtransform4, itemtransform5, itemtransform6, itemtransform7, builder.build());
- }
-
- private ItemTransform getTransform(ItemDisplayContext p_270662_) {
-@@ -347,6 +_,10 @@
-
- public boolean lightLikeBlock() {
- return this == SIDE;
-+ }
++ var renderTypeGroup = net.neoforged.neoforge.client.RenderTypeGroup.EMPTY;
++ if (jsonobject.has("render_type")) {
++ var renderTypeHintName = GsonHelper.getAsString(jsonobject, "render_type");
++ renderTypeGroup = net.neoforged.neoforge.client.NamedRenderTypeManager.get(ResourceLocation.parse(renderTypeHintName));
++ }
+
-+ public String getSerializedName() {
-+ return name;
++ return new BlockModel(resourcelocation, list, textureslots$data, obool, unbakedmodel$guilight, itemtransforms, rootTransform, renderTypeGroup);
}
- }
- }
+
+ private TextureSlots.Data getTextureMap(JsonObject p_111510_) {
diff --git a/patches/net/minecraft/client/renderer/block/model/FaceBakery.java.patch b/patches/net/minecraft/client/renderer/block/model/FaceBakery.java.patch
index 71b9c9943e..bcf2bbca9c 100644
--- a/patches/net/minecraft/client/renderer/block/model/FaceBakery.java.patch
+++ b/patches/net/minecraft/client/renderer/block/model/FaceBakery.java.patch
@@ -1,7 +1,7 @@
--- a/net/minecraft/client/renderer/block/model/FaceBakery.java
+++ b/net/minecraft/client/renderer/block/model/FaceBakery.java
@@ -58,7 +_,14 @@
- this.recalculateWinding(aint, direction);
+ recalculateWinding(aint, direction);
}
- return new BakedQuad(aint, p_111603_.tintIndex(), direction, p_111604_, p_111608_, p_364857_);
diff --git a/patches/net/minecraft/client/renderer/block/model/ItemModelGenerator.java.patch b/patches/net/minecraft/client/renderer/block/model/ItemModelGenerator.java.patch
index 199561dd6b..4028b1df80 100644
--- a/patches/net/minecraft/client/renderer/block/model/ItemModelGenerator.java.patch
+++ b/patches/net/minecraft/client/renderer/block/model/ItemModelGenerator.java.patch
@@ -1,21 +1,52 @@
--- a/net/minecraft/client/renderer/block/model/ItemModelGenerator.java
+++ b/net/minecraft/client/renderer/block/model/ItemModelGenerator.java
-@@ -32,13 +_,16 @@
+@@ -20,7 +_,7 @@
+ import org.joml.Vector3f;
- Material material = p_111672_.getMaterial(s);
- map.put(s, Either.left(material));
-- SpriteContents spritecontents = p_111671_.apply(material).contents();
+ @OnlyIn(Dist.CLIENT)
+-public class ItemModelGenerator implements UnbakedModel {
++public class ItemModelGenerator implements UnbakedModel, net.neoforged.neoforge.client.model.ExtendedUnbakedModel {
+ public static final ResourceLocation GENERATED_ITEM_MODEL_ID = ResourceLocation.withDefaultNamespace("builtin/generated");
+ public static final List LAYERS = List.of("layer0", "layer1", "layer2", "layer3", "layer4");
+ private static final float MIN_Z = 7.5F;
+@@ -43,13 +_,23 @@
+ }
+
+ @Override
+- public BakedModel bake(TextureSlots p_386773_, ModelBaker p_386770_, ModelState p_386536_, boolean p_388612_, boolean p_387457_, ItemTransforms p_388565_) {
+- return this.bake(p_386773_, p_386770_.sprites(), p_386536_, p_388612_, p_387457_, p_388565_);
++ public BakedModel bake(TextureSlots p_386773_, ModelBaker p_386770_, ModelState p_386536_, boolean p_388612_, boolean p_387457_, ItemTransforms p_388565_, net.minecraft.util.context.ContextMap additionalProperties) {
++ return this.bake(p_386773_, p_386770_.sprites(), p_386536_, p_388612_, p_387457_, p_388565_, additionalProperties.getOrDefault(net.neoforged.neoforge.client.model.NeoForgeModelProperties.RENDER_TYPE, net.neoforged.neoforge.client.RenderTypeGroup.EMPTY));
+ }
+
++ /**
++ * @deprecated Neo: use {@link #bake(TextureSlots, SpriteGetter, ModelState, boolean, boolean, ItemTransforms, net.neoforged.neoforge.client.RenderTypeGroup)} instead
++ */
++ @Deprecated
+ private BakedModel bake(
+ TextureSlots p_387202_, SpriteGetter p_387257_, ModelState p_387172_, boolean p_388328_, boolean p_387288_, ItemTransforms p_388238_
+ ) {
++ return this.bake(p_387202_, p_387257_, p_387172_, p_388328_, p_387288_, p_388238_, net.neoforged.neoforge.client.RenderTypeGroup.EMPTY);
++ }
++
++ private BakedModel bake(
++ TextureSlots p_387202_, SpriteGetter p_387257_, ModelState p_387172_, boolean p_388328_, boolean p_387288_, ItemTransforms p_388238_, net.neoforged.neoforge.client.RenderTypeGroup renderTypes
++ ) {
+ TextureSlots.Data.Builder textureslots$data$builder = new TextureSlots.Data.Builder();
+ List list = new ArrayList<>();
+
+@@ -61,11 +_,11 @@
+ }
+
+ textureslots$data$builder.addTexture(s, material);
+- SpriteContents spritecontents = p_387257_.get(material).contents();
- list.addAll(this.processFrames(i, s, spritecontents));
-+ TextureAtlasSprite sprite = p_111671_.apply(material);
-+ // Neo: fix MC-73186 on generated item models
++ net.minecraft.client.renderer.texture.TextureAtlasSprite sprite = p_387257_.get(material);
+ list.addAll(net.neoforged.neoforge.client.ClientHooks.fixItemModelSeams(this.processFrames(i, s, sprite.contents()), sprite));
}
- map.put("particle", p_111672_.hasTexture("particle") ? Either.left(p_111672_.getMaterial("particle")) : map.get("layer0"));
- BlockModel blockmodel = new BlockModel(null, list, map, false, p_111672_.getGuiLight(), p_111672_.getTransforms(), p_111672_.getOverrides());
- blockmodel.name = p_111672_.name;
-+ blockmodel.customData.copyFrom(p_111672_.customData);
-+ blockmodel.customData.setGui3d(false);
- return blockmodel;
+- return SimpleBakedModel.bakeElements(list, p_387202_, p_387257_, p_387172_, p_388328_, p_387288_, false, p_388238_);
++ return SimpleBakedModel.bakeElements(list, p_387202_, p_387257_, p_387172_, p_388328_, p_387288_, false, p_388238_, com.mojang.math.Transformation.identity(), renderTypes);
}
+ public List processFrames(int p_111639_, String p_111640_, SpriteContents p_251768_) {
diff --git a/patches/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch b/patches/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch
index a341611fbb..9ca5df5e9c 100644
--- a/patches/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch
+++ b/patches/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch
@@ -1,51 +1,32 @@
--- a/net/minecraft/client/renderer/block/model/ItemTransforms.java
+++ b/net/minecraft/client/renderer/block/model/ItemTransforms.java
-@@ -21,6 +_,7 @@
- public final ItemTransform gui;
- public final ItemTransform ground;
- public final ItemTransform fixed;
-+ public final com.google.common.collect.ImmutableMap moddedTransforms;
-
- private ItemTransforms() {
- this(
-@@ -44,8 +_,10 @@
- this.gui = p_111807_.gui;
- this.ground = p_111807_.ground;
- this.fixed = p_111807_.fixed;
-+ this.moddedTransforms = p_111807_.moddedTransforms;
- }
-
+@@ -19,8 +_,23 @@
+ ItemTransform head,
+ ItemTransform gui,
+ ItemTransform ground,
+- ItemTransform fixed
++ ItemTransform fixed,
++ com.google.common.collect.ImmutableMap moddedTransforms
+ ) {
+ @Deprecated
- public ItemTransforms(
- ItemTransform p_111798_,
- ItemTransform p_111799_,
-@@ -56,6 +_,20 @@
- ItemTransform p_111804_,
- ItemTransform p_111805_
- ) {
-+ this(p_111798_, p_111799_, p_111800_, p_111801_, p_111802_, p_111803_, p_111804_, p_111805_, com.google.common.collect.ImmutableMap.of());
-+ }
-+
+ public ItemTransforms(
-+ ItemTransform p_111798_,
-+ ItemTransform p_111799_,
-+ ItemTransform p_111800_,
-+ ItemTransform p_111801_,
-+ ItemTransform p_111802_,
-+ ItemTransform p_111803_,
-+ ItemTransform p_111804_,
-+ ItemTransform p_111805_,
-+ com.google.common.collect.ImmutableMap moddedTransforms
++ ItemTransform thirdPersonLeftHand,
++ ItemTransform thirdPersonRightHand,
++ ItemTransform firstPersonLeftHand,
++ ItemTransform firstPersonRightHand,
++ ItemTransform head,
++ ItemTransform gui,
++ ItemTransform ground,
++ ItemTransform fixed
+ ) {
- this.thirdPersonLeftHand = p_111798_;
- this.thirdPersonRightHand = p_111799_;
- this.firstPersonLeftHand = p_111800_;
-@@ -64,9 +_,21 @@
- this.gui = p_111803_;
- this.ground = p_111804_;
- this.fixed = p_111805_;
-+ this.moddedTransforms = moddedTransforms;
- }
++ this(thirdPersonLeftHand, thirdPersonRightHand, firstPersonLeftHand, firstPersonRightHand, head, gui, ground, fixed, com.google.common.collect.ImmutableMap.of());
++ }
++
+ public static final ItemTransforms NO_TRANSFORMS = new ItemTransforms(
+ ItemTransform.NO_TRANSFORM,
+ ItemTransform.NO_TRANSFORM,
+@@ -33,6 +_,17 @@
+ );
public ItemTransform getTransform(ItemDisplayContext p_270619_) {
+ if (p_270619_.isModded()) {
@@ -62,7 +43,7 @@
return switch (p_270619_) {
case THIRD_PERSON_LEFT_HAND -> this.thirdPersonLeftHand;
case THIRD_PERSON_RIGHT_HAND -> this.thirdPersonRightHand;
-@@ -104,9 +_,23 @@
+@@ -66,9 +_,23 @@
ItemTransform itemtransform5 = this.getTransform(p_111822_, jsonobject, ItemDisplayContext.GUI);
ItemTransform itemtransform6 = this.getTransform(p_111822_, jsonobject, ItemDisplayContext.GROUND);
ItemTransform itemtransform7 = this.getTransform(p_111822_, jsonobject, ItemDisplayContext.FIXED);
diff --git a/patches/net/minecraft/client/renderer/block/model/MultiVariant.java.patch b/patches/net/minecraft/client/renderer/block/model/MultiVariant.java.patch
deleted file mode 100644
index bd74eaf20d..0000000000
--- a/patches/net/minecraft/client/renderer/block/model/MultiVariant.java.patch
+++ /dev/null
@@ -1,17 +0,0 @@
---- a/net/minecraft/client/renderer/block/model/MultiVariant.java
-+++ b/net/minecraft/client/renderer/block/model/MultiVariant.java
-@@ -45,12 +_,12 @@
- public BakedModel bake(ModelBaker p_249016_, Function p_111851_, ModelState p_111852_) {
- if (this.variants.size() == 1) {
- Variant variant1 = this.variants.getFirst();
-- return p_249016_.bake(variant1.getModelLocation(), variant1);
-+ return p_249016_.bake(variant1.getModelLocation(), variant1, p_111851_);
- } else {
- SimpleWeightedRandomList.Builder builder = SimpleWeightedRandomList.builder();
-
- for (Variant variant : this.variants) {
-- BakedModel bakedmodel = p_249016_.bake(variant.getModelLocation(), variant);
-+ BakedModel bakedmodel = p_249016_.bake(variant.getModelLocation(), variant, p_111851_);
- builder.add(bakedmodel, variant.getWeight());
- }
-
diff --git a/patches/net/minecraft/client/renderer/blockentity/SignRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/AbstractSignRenderer.java.patch
similarity index 52%
rename from patches/net/minecraft/client/renderer/blockentity/SignRenderer.java.patch
rename to patches/net/minecraft/client/renderer/blockentity/AbstractSignRenderer.java.patch
index 23ffcf89c2..658b82318d 100644
--- a/patches/net/minecraft/client/renderer/blockentity/SignRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/blockentity/AbstractSignRenderer.java.patch
@@ -1,18 +1,16 @@
---- a/net/minecraft/client/renderer/blockentity/SignRenderer.java
-+++ b/net/minecraft/client/renderer/blockentity/SignRenderer.java
-@@ -251,6 +_,15 @@
- return LayerDefinition.create(meshdefinition, 64, 32);
+--- a/net/minecraft/client/renderer/blockentity/AbstractSignRenderer.java
++++ b/net/minecraft/client/renderer/blockentity/AbstractSignRenderer.java
+@@ -195,4 +_,13 @@
+ return ARGB.color(0, j, k, l);
+ }
}
-
++
+ @Override
+ public net.minecraft.world.phys.AABB getRenderBoundingBox(SignBlockEntity blockEntity) {
-+ if (blockEntity.getBlockState().getBlock() instanceof StandingSignBlock) {
++ if (blockEntity.getBlockState().getBlock() instanceof net.minecraft.world.level.block.StandingSignBlock) {
+ net.minecraft.core.BlockPos pos = blockEntity.getBlockPos();
+ return new net.minecraft.world.phys.AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1.0, pos.getY() + 1.125, pos.getZ() + 1.0);
+ }
+ return BlockEntityRenderer.super.getRenderBoundingBox(blockEntity);
+ }
-+
- @OnlyIn(Dist.CLIENT)
- static record Models(Model standing, Model wall) {
- }
+ }
diff --git a/patches/net/minecraft/client/renderer/blockentity/BannerRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/BannerRenderer.java.patch
index 81cc3164d7..43ec30aece 100644
--- a/patches/net/minecraft/client/renderer/blockentity/BannerRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/blockentity/BannerRenderer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/blockentity/BannerRenderer.java
+++ b/net/minecraft/client/renderer/blockentity/BannerRenderer.java
-@@ -141,4 +_,11 @@
+@@ -135,4 +_,11 @@
int i = p_332728_.getTextureDiffuseColor();
p_332732_.render(p_332737_, p_332704_.buffer(p_332758_, RenderType::entityNoOutline), p_332821_, p_332828_, i);
}
diff --git a/patches/net/minecraft/client/renderer/blockentity/DecoratedPotRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/DecoratedPotRenderer.java.patch
index 279790622c..54c1ba440f 100644
--- a/patches/net/minecraft/client/renderer/blockentity/DecoratedPotRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/blockentity/DecoratedPotRenderer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/blockentity/DecoratedPotRenderer.java
+++ b/net/minecraft/client/renderer/blockentity/DecoratedPotRenderer.java
-@@ -141,4 +_,10 @@
+@@ -153,4 +_,10 @@
private void renderSide(ModelPart p_273495_, PoseStack p_272899_, MultiBufferSource p_273582_, int p_273242_, int p_273108_, Material p_273173_) {
p_273495_.render(p_272899_, p_273173_.buffer(p_273582_, RenderType::entitySolid), p_273242_, p_273108_);
}
diff --git a/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch
index b1bd51f952..c5bdbc66de 100644
--- a/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch
@@ -1,7 +1,7 @@
--- a/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java
+++ b/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java
-@@ -55,6 +_,12 @@
- p_112480_.popPose();
+@@ -57,6 +_,12 @@
+ p_388735_.popPose();
}
+ @Override
diff --git a/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch
index d738262caa..2d8d009060 100644
--- a/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch
@@ -1,18 +1,9 @@
--- a/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java
+++ b/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java
-@@ -55,6 +_,7 @@
- builder.put(SkullBlock.Types.CREEPER, new SkullModel(p_173662_.bakeLayer(ModelLayers.CREEPER_HEAD)));
- builder.put(SkullBlock.Types.DRAGON, new DragonHeadModel(p_173662_.bakeLayer(ModelLayers.DRAGON_SKULL)));
- builder.put(SkullBlock.Types.PIGLIN, new PiglinHeadModel(p_173662_.bakeLayer(ModelLayers.PIGLIN_HEAD)));
-+ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.EntityRenderersEvent.CreateSkullModels(builder, p_173662_));
- return builder.build();
+@@ -115,4 +_,14 @@
+ )
+ : RenderType.entityCutoutNoCullZOffset(p_389624_ != null ? p_389624_ : SKIN_BY_TYPE.get(p_389566_));
}
-
-@@ -108,5 +_,15 @@
- } else {
- return RenderType.entityCutoutNoCullZOffset(resourcelocation);
- }
-+ }
+
+ @Override
+ public net.minecraft.world.phys.AABB getRenderBoundingBox(SkullBlockEntity blockEntity) {
@@ -22,5 +13,5 @@
+ return new net.minecraft.world.phys.AABB(pos.getX() - .75, pos.getY() - .35, pos.getZ() - .75, pos.getX() + 1.75, pos.getY() + 1.0, pos.getZ() + 1.75);
+ }
+ return BlockEntityRenderer.super.getRenderBoundingBox(blockEntity);
- }
++ }
}
diff --git a/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch b/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch
index 83ea06baae..5b7ff9be4f 100644
--- a/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch
+++ b/patches/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java
+++ b/net/minecraft/client/renderer/chunk/SectionRenderDispatcher.java
-@@ -393,9 +_,10 @@
+@@ -399,9 +_,10 @@
public SectionRenderDispatcher.RenderSection.CompileTask createCompileTask(RenderRegionCache p_295324_) {
this.cancelTasks();
@@ -13,7 +13,7 @@
return this.lastRebuildTask;
}
-@@ -464,10 +_,17 @@
+@@ -470,10 +_,17 @@
class RebuildTask extends SectionRenderDispatcher.RenderSection.CompileTask {
@Nullable
protected volatile RenderChunkRegion region;
@@ -31,7 +31,7 @@
}
@Override
-@@ -498,7 +_,7 @@
+@@ -499,7 +_,7 @@
SectionCompiler.Results sectioncompiler$results;
try (Zone zone = Profiler.get().zone("Compile Section")) {
sectioncompiler$results = SectionRenderDispatcher.this.sectionCompiler
diff --git a/patches/net/minecraft/client/renderer/entity/EntityRenderDispatcher.java.patch b/patches/net/minecraft/client/renderer/entity/EntityRenderDispatcher.java.patch
index 49c5906036..aaca1f7592 100644
--- a/patches/net/minecraft/client/renderer/entity/EntityRenderDispatcher.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/EntityRenderDispatcher.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/entity/EntityRenderDispatcher.java
+++ b/net/minecraft/client/renderer/entity/EntityRenderDispatcher.java
-@@ -243,12 +_,12 @@
+@@ -246,12 +_,12 @@
) {
AABB aabb = p_114444_.getBoundingBox().move(-p_114444_.getX(), -p_114444_.getY(), -p_114444_.getZ());
ShapeRenderer.renderLineBox(p_114442_, p_114443_, aabb, p_353064_, p_353059_, p_353042_, 1.0F);
@@ -15,7 +15,7 @@
p_114442_.pushPose();
double d3 = d0 + Mth.lerp((double)p_114445_, enderdragonpart.xOld, enderdragonpart.getX());
double d4 = d1 + Mth.lerp((double)p_114445_, enderdragonpart.yOld, enderdragonpart.getY());
-@@ -474,6 +_,10 @@
+@@ -477,6 +_,10 @@
return this.itemInHandRenderer;
}
@@ -26,7 +26,7 @@
@Override
public void onResourceManagerReload(ResourceManager p_174004_) {
EntityRendererProvider.Context entityrendererprovider$context = new EntityRendererProvider.Context(
-@@ -481,5 +_,6 @@
+@@ -484,5 +_,6 @@
);
this.renderers = EntityRenderers.createEntityRenderers(entityrendererprovider$context);
this.playerRenderers = EntityRenderers.createPlayerRenderers(entityrendererprovider$context);
diff --git a/patches/net/minecraft/client/renderer/entity/EntityRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/EntityRenderer.java.patch
index 2e7b86b284..4381fedeb5 100644
--- a/patches/net/minecraft/client/renderer/entity/EntityRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/EntityRenderer.java.patch
@@ -9,21 +9,29 @@
this.renderNameTag(p_364816_, p_364816_.nameTag, p_114488_, p_114489_, p_114490_);
}
}
-@@ -270,7 +_,12 @@
+@@ -249,6 +_,7 @@
+ public final S createRenderState(T p_361382_, float p_360885_) {
+ S s = this.reusedState;
+ this.extractRenderState(p_361382_, s, p_360885_);
++ net.neoforged.neoforge.client.renderstate.RenderStateExtensions.onUpdateEntityRenderState(this, p_361382_, s);
+ return s;
+ }
+
+@@ -274,9 +_,11 @@
}
p_361028_.distanceToCameraSq = this.entityRenderDispatcher.distanceToSqr(p_362104_);
- boolean flag = p_361028_.distanceToCameraSq < 4096.0 && this.shouldShowName(p_362104_, p_361028_.distanceToCameraSq);
-+ boolean flag = p_361028_.distanceToCameraSq < 4096.0;
-+ if (flag) {
-+ var event = new net.neoforged.neoforge.client.event.RenderNameTagEvent.CanRender(p_362104_, p_361028_, p_362104_.getDisplayName(), this, p_362204_);
-+ net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(event);
-+ flag = event.canRender().isTrue() || (event.canRender().isDefault() && this.shouldShowName(p_362104_, p_361028_.distanceToCameraSq));
-+ }
++ var event = new net.neoforged.neoforge.client.event.RenderNameTagEvent.CanRender(p_362104_, p_361028_, this.getNameTag(p_362104_), this, p_362204_);
++ net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(event);
++ boolean flag = event.canRender().isTrue() || (event.canRender().isDefault() && p_361028_.distanceToCameraSq < 4096.0 && this.shouldShowName(p_362104_, p_361028_.distanceToCameraSq));
if (flag) {
- p_361028_.nameTag = this.getNameTag(p_362104_);
+- p_361028_.nameTag = this.getNameTag(p_362104_);
++ p_361028_.nameTag = event.getContent();
p_361028_.nameTagAttachment = p_362104_.getAttachments().getNullable(EntityAttachment.NAME_TAG, 0, p_362104_.getYRot(p_362204_));
-@@ -302,5 +_,7 @@
+ } else {
+ p_361028_.nameTag = null;
+@@ -306,5 +_,7 @@
}
p_361028_.displayFireAnimation = p_362104_.displayFireAnimation();
diff --git a/patches/net/minecraft/client/renderer/entity/FishingHookRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/FishingHookRenderer.java.patch
index 6747d9fda7..fead956b8d 100644
--- a/patches/net/minecraft/client/renderer/entity/FishingHookRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/FishingHookRenderer.java.patch
@@ -1,11 +1,11 @@
--- a/net/minecraft/client/renderer/entity/FishingHookRenderer.java
+++ b/net/minecraft/client/renderer/entity/FishingHookRenderer.java
-@@ -63,7 +_,7 @@
- private Vec3 getPlayerHandPos(Player p_340935_, float p_340872_, float p_341261_) {
- int i = p_340935_.getMainArm() == HumanoidArm.RIGHT ? 1 : -1;
- ItemStack itemstack = p_340935_.getMainHandItem();
-- if (!itemstack.is(Items.FISHING_ROD)) {
-+ if (!itemstack.canPerformAction(net.neoforged.neoforge.common.ItemAbilities.FISHING_ROD_CAST)) {
- i = -i;
- }
+@@ -60,7 +_,7 @@
+ }
+
+ public static HumanoidArm getHoldingArm(Player p_386900_) {
+- return p_386900_.getMainHandItem().getItem() instanceof FishingRodItem ? p_386900_.getMainArm() : p_386900_.getMainArm().getOpposite();
++ return p_386900_.getMainHandItem().canPerformAction(net.neoforged.neoforge.common.ItemAbilities.FISHING_ROD_CAST) ? p_386900_.getMainArm() : p_386900_.getMainArm().getOpposite();
+ }
+ private Vec3 getPlayerHandPos(Player p_340935_, float p_340872_, float p_341261_) {
diff --git a/patches/net/minecraft/client/renderer/entity/HumanoidMobRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/HumanoidMobRenderer.java.patch
index fa5624769b..e9e02707fe 100644
--- a/patches/net/minecraft/client/renderer/entity/HumanoidMobRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/HumanoidMobRenderer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/entity/HumanoidMobRenderer.java
+++ b/net/minecraft/client/renderer/entity/HumanoidMobRenderer.java
-@@ -40,7 +_,7 @@
+@@ -51,7 +_,7 @@
p_362998_.isCrouching = p_365104_.isCrouching();
p_362998_.isFallFlying = p_365104_.isFallFlying();
p_362998_.isVisuallySwimming = p_365104_.isVisuallySwimming();
diff --git a/patches/net/minecraft/client/renderer/entity/ItemEntityRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/ItemEntityRenderer.java.patch
index 52951841f3..1e35d7dac3 100644
--- a/patches/net/minecraft/client/renderer/entity/ItemEntityRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/ItemEntityRenderer.java.patch
@@ -1,24 +1,29 @@
--- a/net/minecraft/client/renderer/entity/ItemEntityRenderer.java
+++ b/net/minecraft/client/renderer/entity/ItemEntityRenderer.java
-@@ -54,7 +_,8 @@
- this.random.setSeed((long)getSeedForItemStack(itemstack));
- boolean flag = bakedmodel.isGui3d();
+@@ -38,6 +_,8 @@
+ super.extractRenderState(p_362393_, p_361441_, p_360849_);
+ p_361441_.ageInTicks = (float)p_362393_.getAge() + p_360849_;
+ p_361441_.bobOffset = p_362393_.bobOffs;
++ p_361441_.shouldBob = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(p_362393_.getItem()).shouldBobAsEntity(p_362393_.getItem());
++ p_361441_.shouldSpread = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(p_362393_.getItem()).shouldSpreadAsEntity(p_362393_.getItem());
+ p_361441_.extractItemGroupRenderState(p_362393_, p_362393_.getItem(), this.itemModelResolver);
+ }
+
+@@ -45,7 +_,7 @@
+ if (!p_362172_.item.isEmpty()) {
+ p_115030_.pushPose();
float f = 0.25F;
- float f1 = Mth.sin(p_362172_.ageInTicks / 10.0F + p_362172_.bobOffset) * 0.1F + 0.1F;
-+ boolean shouldBob = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(itemstack).shouldBobAsEntity(itemstack);
-+ float f1 = shouldBob ? Mth.sin(p_362172_.ageInTicks / 10.0F + p_362172_.bobOffset) * 0.1F + 0.1F : 0;
- float f2 = bakedmodel.getTransforms().getTransform(ItemDisplayContext.GROUND).scale.y();
++ float f1 = p_362172_.shouldBob ? Mth.sin(p_362172_.ageInTicks / 10.0F + p_362172_.bobOffset) * 0.1F + 0.1F : 0;
+ float f2 = p_362172_.item.transform().scale.y();
p_115030_.translate(0.0F, f1 + 0.25F * f2, 0.0F);
float f3 = ItemEntity.getSpin(p_362172_.ageInTicks, p_362172_.bobOffset);
-@@ -110,9 +_,10 @@
- p_323733_.translate(f3, f4, f5);
- }
+@@ -75,7 +_,7 @@
-+ boolean shouldSpread = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(p_323718_).shouldSpreadAsEntity(p_323718_);
for (int j = 0; j < i; j++) {
- p_323733_.pushPose();
+ p_323763_.pushPose();
- if (j > 0) {
-+ if (j > 0 && shouldSpread) {
- if (p_324462_) {
- float f7 = (p_324565_.nextFloat() * 2.0F - 1.0F) * 0.15F;
- float f9 = (p_324565_.nextFloat() * 2.0F - 1.0F) * 0.15F;
++ if (j > 0 && p_388704_.shouldSpread) {
+ if (flag) {
+ float f7 = (p_324507_.nextFloat() * 2.0F - 1.0F) * 0.15F;
+ float f9 = (p_324507_.nextFloat() * 2.0F - 1.0F) * 0.15F;
diff --git a/patches/net/minecraft/client/renderer/entity/ItemFrameRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/ItemFrameRenderer.java.patch
index 7fe0bf497d..02db78f347 100644
--- a/patches/net/minecraft/client/renderer/entity/ItemFrameRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/ItemFrameRenderer.java.patch
@@ -1,23 +1,23 @@
--- a/net/minecraft/client/renderer/entity/ItemFrameRenderer.java
+++ b/net/minecraft/client/renderer/entity/ItemFrameRenderer.java
-@@ -100,6 +_,7 @@
+@@ -95,6 +_,7 @@
+ p_115079_.translate(0.0F, 0.0F, 0.4375F);
+ }
- int j = mapid != null ? p_364723_.rotation % 4 * 2 : p_364723_.rotation;
++ if (!net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.client.event.RenderItemInFrameEvent(p_364723_, this, p_115079_, p_115080_, p_115081_)).isCanceled()) {
+ if (p_364723_.mapId != null) {
+ int j = p_364723_.rotation % 4 * 2;
p_115079_.mulPose(Axis.ZP.rotationDegrees((float)j * 360.0F / 8.0F));
-+ if (!net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.client.event.RenderItemInFrameEvent(p_364723_, this, p_115079_, p_115080_, p_115081_)).isCanceled()) {
- if (mapid != null) {
- p_115079_.mulPose(Axis.ZP.rotationDegrees(180.0F));
- float f2 = 0.0078125F;
-@@ -113,6 +_,7 @@
- p_115079_.scale(0.5F, 0.5F, 0.5F);
- this.itemRenderer.render(itemstack, ItemDisplayContext.FIXED, false, p_115079_, p_115080_, k, OverlayTexture.NO_OVERLAY, p_364723_.itemModel);
- }
-+ }
+@@ -111,6 +_,7 @@
+ p_115079_.scale(0.5F, 0.5F, 0.5F);
+ p_364723_.item.render(p_115079_, p_115080_, k, OverlayTexture.NO_OVERLAY);
}
++ }
p_115079_.popPose();
-@@ -161,7 +_,7 @@
- if (!p_362907_.itemStack.isEmpty()) {
+ }
+@@ -154,7 +_,7 @@
+ if (!itemstack.isEmpty()) {
MapId mapid = p_363125_.getFramedMapId(itemstack);
if (mapid != null) {
- MapItemSavedData mapitemsaveddata = p_363125_.level().getMapData(mapid);
diff --git a/patches/net/minecraft/client/renderer/entity/ItemRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/ItemRenderer.java.patch
index a53ce7e08e..714221b7bc 100644
--- a/patches/net/minecraft/client/renderer/entity/ItemRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/ItemRenderer.java.patch
@@ -1,54 +1,11 @@
--- a/net/minecraft/client/renderer/entity/ItemRenderer.java
+++ b/net/minecraft/client/renderer/entity/ItemRenderer.java
-@@ -156,7 +_,7 @@
- float p_371782_
- ) {
- p_371635_.pushPose();
-- p_371384_.getTransforms().getTransform(p_371250_).apply(p_371248_, p_371635_);
-+ p_371384_ = net.neoforged.neoforge.client.ClientHooks.handleCameraTransforms(p_371635_, p_371384_, p_371250_, p_371248_);
- p_371635_.translate(-0.5F, -0.5F, p_371782_);
- this.renderItem(p_371318_, p_371250_, p_371635_, p_371946_, p_371752_, p_371508_, p_371384_, p_371718_);
- p_371635_.popPose();
-@@ -173,7 +_,8 @@
- boolean p_364829_
- ) {
- if (!p_363970_.isCustomRenderer() && (!p_361397_.is(Items.TRIDENT) || p_364829_)) {
-- RenderType rendertype = ItemBlockRenderTypes.getRenderType(p_361397_);
-+ for (var model : p_363970_.getRenderPasses(p_361397_)) {
-+ for (var rendertype : model.getRenderTypes(p_361397_)) {
- VertexConsumer vertexconsumer;
- if (hasAnimatedTexture(p_361397_) && p_361397_.hasFoil()) {
- PoseStack.Pose posestack$pose = p_360423_.last().copy();
-@@ -188,9 +_,11 @@
- vertexconsumer = getFoilBuffer(p_360415_, rendertype, true, p_361397_.hasFoil());
+@@ -132,7 +_,7 @@
+ f3 = 1.0F;
}
-- this.renderModelLists(p_363970_, p_361397_, p_361265_, p_364771_, p_360423_, vertexconsumer);
-+ this.renderModelLists(model, p_361397_, p_361265_, p_364771_, p_360423_, vertexconsumer);
-+ }
-+ }
- } else {
-- this.blockEntityRenderer.renderByItem(p_361397_, p_361627_, p_360423_, p_360415_, p_361265_, p_364771_);
-+ net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(p_361397_).getCustomRenderer().renderByItem(p_361397_, p_361627_, p_360423_, p_360415_, p_361265_, p_364771_);
- }
- }
-
-@@ -238,7 +_,7 @@
- float f1 = (float)ARGB.red(i) / 255.0F;
- float f2 = (float)ARGB.green(i) / 255.0F;
- float f3 = (float)ARGB.blue(i) / 255.0F;
- p_115164_.putBulkData(posestack$pose, bakedquad, f1, f2, f3, f, p_115167_, p_115168_);
-+ p_115164_.putBulkData(posestack$pose, bakedquad, f1, f2, f3, f, p_115167_, p_115168_, true); // Neo: pass readExistingColor=true
++ p_115164_.putBulkData(posestack$pose, bakedquad, f1, f2, f3, f, p_115167_, p_115168_, true);
}
}
-@@ -294,5 +_,9 @@
- ClientLevel clientlevel = p_372942_ instanceof ClientLevel ? (ClientLevel)p_372942_ : null;
- BakedModel bakedmodel = p_373094_.overrides().findOverride(p_372923_, clientlevel, p_373099_, p_372933_);
- return bakedmodel == null ? p_373094_ : bakedmodel;
-+ }
-+
-+ public BlockEntityWithoutLevelRenderer getBlockEntityRenderer() {
-+ return blockEntityRenderer;
- }
- }
diff --git a/patches/net/minecraft/client/renderer/entity/LivingEntityRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/LivingEntityRenderer.java.patch
index 8322d59b63..8998226b08 100644
--- a/patches/net/minecraft/client/renderer/entity/LivingEntityRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/LivingEntityRenderer.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/entity/LivingEntityRenderer.java
+++ b/net/minecraft/client/renderer/entity/LivingEntityRenderer.java
-@@ -69,6 +_,7 @@
+@@ -73,6 +_,7 @@
}
public void render(S p_361886_, PoseStack p_115311_, MultiBufferSource p_115312_, int p_115313_) {
@@ -8,7 +8,7 @@
p_115311_.pushPose();
if (p_361886_.hasPose(Pose.SLEEPING)) {
Direction direction = p_361886_.bedOrientation;
-@@ -104,6 +_,7 @@
+@@ -108,6 +_,7 @@
p_115311_.popPose();
super.render(p_361886_, p_115311_, p_115312_, p_115313_);
@@ -16,7 +16,7 @@
}
protected boolean shouldRenderLayers(S p_364697_) {
-@@ -201,7 +_,7 @@
+@@ -205,7 +_,7 @@
protected boolean shouldShowName(T p_363517_, double p_365448_) {
if (p_363517_.isDiscrete()) {
float f = 32.0F;
@@ -25,12 +25,12 @@
return false;
}
}
-@@ -284,7 +_,7 @@
-
- p_360515_.isFullyFrozen = p_362733_.isFullyFrozen();
- p_360515_.isBaby = p_362733_.isBaby();
-- p_360515_.isInWater = p_362733_.isInWater();
-+ p_360515_.isInWater = p_362733_.isInWater() || p_362733_.isInFluidType((fluidType, height) -> p_362733_.canSwimInFluidType(fluidType));
- p_360515_.isAutoSpinAttack = p_362733_.isAutoSpinAttack();
- p_360515_.hasRedOverlay = p_362733_.hurtTime > 0 || p_362733_.deathTime > 0;
- ItemStack itemstack1 = p_362733_.getItemBySlot(EquipmentSlot.HEAD);
+@@ -289,7 +_,7 @@
+ label48: {
+ p_360515_.isFullyFrozen = p_362733_.isFullyFrozen();
+ p_360515_.isBaby = p_362733_.isBaby();
+- p_360515_.isInWater = p_362733_.isInWater();
++ p_360515_.isInWater = p_362733_.isInWater() || p_362733_.isInFluidType((fluidType, height) -> p_362733_.canSwimInFluidType(fluidType));
+ p_360515_.isAutoSpinAttack = p_362733_.isAutoSpinAttack();
+ p_360515_.hasRedOverlay = p_362733_.hurtTime > 0 || p_362733_.deathTime > 0;
+ ItemStack itemstack = p_362733_.getItemBySlot(EquipmentSlot.HEAD);
diff --git a/patches/net/minecraft/client/renderer/entity/layers/EquipmentLayerRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/layers/EquipmentLayerRenderer.java.patch
index d9bc50306b..54ebe1b713 100644
--- a/patches/net/minecraft/client/renderer/entity/layers/EquipmentLayerRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/layers/EquipmentLayerRenderer.java.patch
@@ -1,11 +1,11 @@
--- a/net/minecraft/client/renderer/entity/layers/EquipmentLayerRenderer.java
+++ b/net/minecraft/client/renderer/entity/layers/EquipmentLayerRenderer.java
-@@ -65,21 +_,26 @@
+@@ -66,21 +_,26 @@
int p_371309_,
- @Nullable ResourceLocation p_371587_
+ @Nullable ResourceLocation p_371639_
) {
-+ p_371731_ = getArmorModelHook(p_371670_, p_371854_, p_371731_);
- List list = this.equipmentModels.get(p_371639_).getLayers(p_371854_);
++ p_371731_ = getArmorModelHook(p_371670_, p_387484_, p_371731_);
+ List list = this.equipmentAssets.get(p_387603_).getLayers(p_387484_);
if (!list.isEmpty()) {
- int i = p_371670_.is(ItemTags.DYEABLE) ? DyedItemColor.getOrDefault(p_371670_, 0) : 0;
+ net.neoforged.neoforge.client.extensions.common.IClientItemExtensions extensions = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(p_371670_);
@@ -13,14 +13,14 @@
boolean flag = p_371670_.hasFoil();
+ int idx = 0;
- for (EquipmentModel.Layer equipmentmodel$layer : list) {
-- int j = getColorForLayer(equipmentmodel$layer, i);
-+ int j = extensions.getArmorLayerTintColor(p_371670_, equipmentmodel$layer, idx, i);
+ for (EquipmentClientInfo.Layer equipmentclientinfo$layer : list) {
+- int j = getColorForLayer(equipmentclientinfo$layer, i);
++ int j = extensions.getArmorLayerTintColor(p_371670_, equipmentclientinfo$layer, idx, i);
if (j != 0) {
- ResourceLocation resourcelocation = equipmentmodel$layer.usePlayerTexture() && p_371587_ != null
- ? p_371587_
- : this.layerTextureLookup.apply(new EquipmentLayerRenderer.LayerTextureKey(p_371854_, equipmentmodel$layer));
-+ resourcelocation = net.neoforged.neoforge.client.ClientHooks.getArmorTexture(p_371670_, p_371854_, equipmentmodel$layer, resourcelocation);
+ ResourceLocation resourcelocation = equipmentclientinfo$layer.usePlayerTexture() && p_371639_ != null
+ ? p_371639_
+ : this.layerTextureLookup.apply(new EquipmentLayerRenderer.LayerTextureKey(p_387484_, equipmentclientinfo$layer));
++ resourcelocation = net.neoforged.neoforge.client.ClientHooks.getArmorTexture(p_371670_, p_387484_, equipmentclientinfo$layer, resourcelocation);
VertexConsumer vertexconsumer = ItemRenderer.getArmorFoilBuffer(p_371286_, RenderType.armorCutoutNoCull(resourcelocation), flag);
p_371731_.renderToBuffer(p_371767_, vertexconsumer, p_371309_, OverlayTexture.NO_OVERLAY, j);
flag = false;
@@ -29,7 +29,7 @@
}
ArmorTrim armortrim = p_371670_.get(DataComponents.TRIM);
-@@ -99,6 +_,13 @@
+@@ -100,6 +_,13 @@
} else {
return -1;
}
@@ -38,7 +38,7 @@
+ /**
+ * Hook to allow item-sensitive armor model. for HumanoidArmorLayer.
+ */
-+ protected net.minecraft.client.model.Model getArmorModelHook(ItemStack itemStack, EquipmentModel.LayerType layerType, Model model) {
++ protected net.minecraft.client.model.Model getArmorModelHook(ItemStack itemStack, EquipmentClientInfo.LayerType layerType, Model model) {
+ return net.neoforged.neoforge.client.ClientHooks.getArmorModel(itemStack, layerType, model);
}
diff --git a/patches/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch b/patches/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch
index d2fb4f4901..a36dc98a5e 100644
--- a/patches/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/player/PlayerRenderer.java.patch
@@ -1,16 +1,39 @@
--- a/net/minecraft/client/renderer/entity/player/PlayerRenderer.java
+++ b/net/minecraft/client/renderer/entity/player/PlayerRenderer.java
-@@ -127,6 +_,9 @@
- } else if (!p_363098_.swinging && p_364742_.holdsChargedCrossbow) {
- return HumanoidModel.ArmPose.CROSSBOW_HOLD;
- }
-+ if (p_364742_.customArmPose != null) {
-+ return p_364742_.customArmPose;
-+ }
-
- return HumanoidModel.ArmPose.ITEM;
+@@ -86,8 +_,10 @@
+ private static HumanoidModel.ArmPose getArmPose(AbstractClientPlayer p_386861_, HumanoidArm p_373044_) {
+ ItemStack itemstack = p_386861_.getItemInHand(InteractionHand.MAIN_HAND);
+ ItemStack itemstack1 = p_386861_.getItemInHand(InteractionHand.OFF_HAND);
+- HumanoidModel.ArmPose humanoidmodel$armpose = getArmPose(p_386861_, itemstack, InteractionHand.MAIN_HAND);
+- HumanoidModel.ArmPose humanoidmodel$armpose1 = getArmPose(p_386861_, itemstack1, InteractionHand.OFF_HAND);
++ var extensionsMainHand = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(itemstack);
++ var extensionsOffHand = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(itemstack1);
++ HumanoidModel.ArmPose humanoidmodel$armpose = getArmPose(p_386861_, itemstack, InteractionHand.MAIN_HAND, extensionsMainHand.getArmPose(p_386861_, InteractionHand.MAIN_HAND, itemstack));
++ HumanoidModel.ArmPose humanoidmodel$armpose1 = getArmPose(p_386861_, itemstack1, InteractionHand.OFF_HAND, extensionsOffHand.getArmPose(p_386861_, InteractionHand.OFF_HAND, itemstack1));
+ if (humanoidmodel$armpose.isTwoHanded()) {
+ humanoidmodel$armpose1 = itemstack1.isEmpty() ? HumanoidModel.ArmPose.EMPTY : HumanoidModel.ArmPose.ITEM;
}
-@@ -141,6 +_,13 @@
+@@ -95,7 +_,19 @@
+ return p_386861_.getMainArm() == p_373044_ ? humanoidmodel$armpose : humanoidmodel$armpose1;
+ }
+
++ /**
++ * @deprecated Neo: use {@link #getArmPose(Player, ItemStack, InteractionHand, HumanoidModel.ArmPose)} instead
++ */
++ @Deprecated
+ private static HumanoidModel.ArmPose getArmPose(Player p_386775_, ItemStack p_388403_, InteractionHand p_117796_) {
++ return getArmPose(p_386775_, p_388403_, p_117796_, null);
++ }
++
++ private static HumanoidModel.ArmPose getArmPose(Player p_386775_, ItemStack p_388403_, InteractionHand p_117796_, @Nullable HumanoidModel.ArmPose pose) {
++ if (pose != null) {
++ return pose;
++ }
++
+ if (p_388403_.isEmpty()) {
+ return HumanoidModel.ArmPose.EMPTY;
+ } else {
+@@ -145,6 +_,13 @@
p_117799_.scale(0.9375F, 0.9375F, 0.9375F);
}
@@ -24,17 +47,7 @@
protected void renderNameTag(PlayerRenderState p_363185_, Component p_117809_, PoseStack p_117810_, MultiBufferSource p_117811_, int p_117812_) {
p_117810_.pushPose();
if (p_363185_.scoreText != null) {
-@@ -217,7 +_,8 @@
- ItemStack itemstack = p_364516_.getItemInHand(p_364304_);
- p_360817_.isEmpty = itemstack.isEmpty();
- p_360817_.useAnimation = !itemstack.isEmpty() ? itemstack.getUseAnimation() : null;
-- p_360817_.holdsChargedCrossbow = itemstack.is(Items.CROSSBOW) && CrossbowItem.isCharged(itemstack);
-+ p_360817_.holdsChargedCrossbow = itemstack.getItem() instanceof CrossbowItem && CrossbowItem.isCharged(itemstack);
-+ p_360817_.customArmPose = net.neoforged.neoforge.client.extensions.common.IClientItemExtensions.of(itemstack).getArmPose(p_364516_, p_364304_, itemstack);
- }
-
- private static void extractCapeState(AbstractClientPlayer p_364691_, PlayerRenderState p_360814_, float p_364460_) {
-@@ -247,11 +_,29 @@
+@@ -251,11 +_,29 @@
: null;
}
diff --git a/patches/net/minecraft/client/renderer/entity/state/EntityRenderState.java.patch b/patches/net/minecraft/client/renderer/entity/state/EntityRenderState.java.patch
index a9fd80707c..7614a11b9f 100644
--- a/patches/net/minecraft/client/renderer/entity/state/EntityRenderState.java.patch
+++ b/patches/net/minecraft/client/renderer/entity/state/EntityRenderState.java.patch
@@ -1,5 +1,14 @@
--- a/net/minecraft/client/renderer/entity/state/EntityRenderState.java
+++ b/net/minecraft/client/renderer/entity/state/EntityRenderState.java
+@@ -7,7 +_,7 @@
+ import net.neoforged.api.distmarker.OnlyIn;
+
+ @OnlyIn(Dist.CLIENT)
+-public class EntityRenderState {
++public class EntityRenderState extends net.neoforged.neoforge.client.renderstate.BaseRenderState {
+ public double x;
+ public double y;
+ public double z;
@@ -27,6 +_,7 @@
public Vec3 nameTagAttachment;
@Nullable
diff --git a/patches/net/minecraft/client/renderer/entity/state/ItemClusterRenderState.java.patch b/patches/net/minecraft/client/renderer/entity/state/ItemClusterRenderState.java.patch
new file mode 100644
index 0000000000..1de5b0c358
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/entity/state/ItemClusterRenderState.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/client/renderer/entity/state/ItemClusterRenderState.java
++++ b/net/minecraft/client/renderer/entity/state/ItemClusterRenderState.java
+@@ -14,6 +_,7 @@
+ public final ItemStackRenderState item = new ItemStackRenderState();
+ public int count;
+ public int seed;
++ public boolean shouldSpread;
+
+ public void extractItemGroupRenderState(Entity p_386526_, ItemStack p_386486_, ItemModelResolver p_387036_) {
+ p_387036_.updateForNonLiving(this.item, p_386486_, ItemDisplayContext.GROUND, p_386526_);
diff --git a/patches/net/minecraft/client/renderer/entity/state/ItemEntityRenderState.java.patch b/patches/net/minecraft/client/renderer/entity/state/ItemEntityRenderState.java.patch
new file mode 100644
index 0000000000..7b3eb62dce
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/entity/state/ItemEntityRenderState.java.patch
@@ -0,0 +1,8 @@
+--- a/net/minecraft/client/renderer/entity/state/ItemEntityRenderState.java
++++ b/net/minecraft/client/renderer/entity/state/ItemEntityRenderState.java
+@@ -6,4 +_,5 @@
+ @OnlyIn(Dist.CLIENT)
+ public class ItemEntityRenderState extends ItemClusterRenderState {
+ public float bobOffset;
++ public boolean shouldBob;
+ }
diff --git a/patches/net/minecraft/client/renderer/entity/state/PlayerRenderState.java.patch b/patches/net/minecraft/client/renderer/entity/state/PlayerRenderState.java.patch
deleted file mode 100644
index 870f5bd850..0000000000
--- a/patches/net/minecraft/client/renderer/entity/state/PlayerRenderState.java.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/net/minecraft/client/renderer/entity/state/PlayerRenderState.java
-+++ b/net/minecraft/client/renderer/entity/state/PlayerRenderState.java
-@@ -52,5 +_,7 @@
- @Nullable
- public ItemUseAnimation useAnimation;
- public boolean holdsChargedCrossbow;
-+ @Nullable
-+ public net.minecraft.client.model.HumanoidModel.ArmPose customArmPose;
- }
- }
diff --git a/patches/net/minecraft/client/renderer/item/BlockModelWrapper.java.patch b/patches/net/minecraft/client/renderer/item/BlockModelWrapper.java.patch
new file mode 100644
index 0000000000..0ffe05b0dc
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/item/BlockModelWrapper.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/client/renderer/item/BlockModelWrapper.java
++++ b/net/minecraft/client/renderer/item/BlockModelWrapper.java
+@@ -41,22 +_,25 @@
+ @Nullable LivingEntity p_387263_,
+ int p_388300_
+ ) {
+- ItemStackRenderState.LayerRenderState itemstackrenderstate$layerrenderstate = p_386488_.newLayer();
+- if (p_386443_.hasFoil()) {
+- itemstackrenderstate$layerrenderstate.setFoilType(
+- hasSpecialAnimatedTexture(p_386443_) ? ItemStackRenderState.FoilType.SPECIAL : ItemStackRenderState.FoilType.STANDARD
+- );
+- }
+-
+- int i = this.tints.size();
+- int[] aint = itemstackrenderstate$layerrenderstate.prepareTintLayers(i);
+-
+- for (int j = 0; j < i; j++) {
+- aint[j] = this.tints.get(j).calculate(p_386443_, p_387522_, p_387263_);
+- }
+-
+- RenderType rendertype = ItemBlockRenderTypes.getRenderType(p_386443_);
+- itemstackrenderstate$layerrenderstate.setupBlockModel(this.model, rendertype);
++ final int[] tints = new int[this.tints.size()];
++ for (int j = 0; j < tints.length; j++) {
++ tints[j] = this.tints.get(j).calculate(p_386443_, p_387522_, p_387263_);
++ }
++ final ItemStackRenderState.FoilType foilType = hasSpecialAnimatedTexture(p_386443_) ? ItemStackRenderState.FoilType.SPECIAL : ItemStackRenderState.FoilType.STANDARD;
++
++ this.model.getRenderPasses(p_386443_).forEach(pass -> {
++ ItemStackRenderState.LayerRenderState itemstackrenderstate$layerrenderstate = p_386488_.newLayer();
++ if (p_386443_.hasFoil()) {
++ itemstackrenderstate$layerrenderstate.setFoilType(
++ foilType
++ );
++ }
++
++ int[] aint = itemstackrenderstate$layerrenderstate.prepareTintLayers(tints.length);
++ System.arraycopy(tints, 0, aint, 0, tints.length);
++
++ itemstackrenderstate$layerrenderstate.setupBlockModel(this.model, pass.getRenderType(p_386443_));
++ });
+ }
+
+ private static boolean hasSpecialAnimatedTexture(ItemStack p_387217_) {
diff --git a/patches/net/minecraft/client/renderer/item/ItemModels.java.patch b/patches/net/minecraft/client/renderer/item/ItemModels.java.patch
new file mode 100644
index 0000000000..7e2649b4c9
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/item/ItemModels.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/client/renderer/item/ItemModels.java
++++ b/net/minecraft/client/renderer/item/ItemModels.java
+@@ -21,5 +_,7 @@
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("bundle/selected_item"), BundleSelectedItemSpecialRenderer.Unbaked.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("select"), SelectItemModel.Unbaked.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("condition"), ConditionalItemModel.Unbaked.MAP_CODEC);
++
++ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.RegisterItemModelsEvent(ID_MAPPER));
+ }
+ }
diff --git a/patches/net/minecraft/client/renderer/item/ItemProperties.java.patch b/patches/net/minecraft/client/renderer/item/ItemProperties.java.patch
deleted file mode 100644
index 95f1563e80..0000000000
--- a/patches/net/minecraft/client/renderer/item/ItemProperties.java.patch
+++ /dev/null
@@ -1,23 +0,0 @@
---- a/net/minecraft/client/renderer/item/ItemProperties.java
-+++ b/net/minecraft/client/renderer/item/ItemProperties.java
-@@ -42,6 +_,9 @@
- private static final Map
- > PROPERTIES = Maps.newHashMap();
-
- private static ClampedItemPropertyFunction registerGeneric(ResourceLocation p_174582_, ClampedItemPropertyFunction p_174583_) {
-+ return (ClampedItemPropertyFunction) registerGeneric(p_174582_, (ItemPropertyFunction) p_174583_);
-+ }
-+ public static ItemPropertyFunction registerGeneric(ResourceLocation p_174582_, ItemPropertyFunction p_174583_) {
- GENERIC_PROPERTIES.put(p_174582_, p_174583_);
- return p_174583_;
- }
-@@ -51,6 +_,10 @@
- }
-
- private static void register(Item p_174571_, ResourceLocation p_174572_, ClampedItemPropertyFunction p_174573_) {
-+ register(p_174571_, p_174572_, (ItemPropertyFunction) p_174573_);
-+ }
-+
-+ public static void register(Item p_174571_, ResourceLocation p_174572_, ItemPropertyFunction p_174573_) {
- PROPERTIES.computeIfAbsent(p_174571_, p_117828_ -> Maps.newHashMap()).put(p_174572_, p_174573_);
- }
-
diff --git a/patches/net/minecraft/client/renderer/item/ItemStackRenderState.java.patch b/patches/net/minecraft/client/renderer/item/ItemStackRenderState.java.patch
new file mode 100644
index 0000000000..9f4cd214c1
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/item/ItemStackRenderState.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/client/renderer/item/ItemStackRenderState.java
++++ b/net/minecraft/client/renderer/item/ItemStackRenderState.java
+@@ -72,7 +_,7 @@
+ return null;
+ } else {
+ BakedModel bakedmodel = this.layers[p_387539_.nextInt(this.activeLayerCount)].model;
+- return bakedmodel == null ? null : bakedmodel.getParticleIcon();
++ return bakedmodel == null ? null : bakedmodel.getParticleIcon(net.neoforged.neoforge.client.model.data.ModelData.EMPTY);
+ }
+ }
+
+@@ -149,6 +_,9 @@
+
+ void render(PoseStack p_387607_, MultiBufferSource p_386763_, int p_387589_, int p_388775_) {
+ p_387607_.pushPose();
++ if (model != null)
++ net.neoforged.neoforge.client.ClientHooks.handleCameraTransforms(p_387607_, model, displayContext, ItemStackRenderState.this.isLeftHand);
++ else
+ this.transform().apply(ItemStackRenderState.this.isLeftHand, p_387607_);
+ p_387607_.translate(-0.5F, -0.5F, -0.5F);
+ if (this.specialRenderer != null) {
diff --git a/patches/net/minecraft/client/renderer/item/properties/conditional/ConditionalItemModelProperties.java.patch b/patches/net/minecraft/client/renderer/item/properties/conditional/ConditionalItemModelProperties.java.patch
new file mode 100644
index 0000000000..080464ae1f
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/item/properties/conditional/ConditionalItemModelProperties.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/client/renderer/item/properties/conditional/ConditionalItemModelProperties.java
++++ b/net/minecraft/client/renderer/item/properties/conditional/ConditionalItemModelProperties.java
+@@ -25,5 +_,7 @@
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("extended_view"), ExtendedView.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("keybind_down"), IsKeybindDown.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("view_entity"), IsViewEntity.MAP_CODEC);
++
++ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.RegisterConditionalItemModelPropertyEvent(ID_MAPPER));
+ }
+ }
diff --git a/patches/net/minecraft/client/renderer/item/properties/numeric/RangeSelectItemModelProperties.java.patch b/patches/net/minecraft/client/renderer/item/properties/numeric/RangeSelectItemModelProperties.java.patch
new file mode 100644
index 0000000000..cd56d12d98
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/item/properties/numeric/RangeSelectItemModelProperties.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/client/renderer/item/properties/numeric/RangeSelectItemModelProperties.java
++++ b/net/minecraft/client/renderer/item/properties/numeric/RangeSelectItemModelProperties.java
+@@ -23,5 +_,7 @@
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("use_cycle"), UseCycle.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("use_duration"), UseDuration.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("count"), Count.MAP_CODEC);
++
++ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.RegisterRangeSelectItemModelPropertyEvent(ID_MAPPER));
+ }
+ }
diff --git a/patches/net/minecraft/client/renderer/item/properties/select/SelectItemModelProperties.java.patch b/patches/net/minecraft/client/renderer/item/properties/select/SelectItemModelProperties.java.patch
new file mode 100644
index 0000000000..7ba6478101
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/item/properties/select/SelectItemModelProperties.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/client/renderer/item/properties/select/SelectItemModelProperties.java
++++ b/net/minecraft/client/renderer/item/properties/select/SelectItemModelProperties.java
+@@ -21,5 +_,7 @@
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("local_time"), LocalTime.TYPE);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("context_entity_type"), ContextEntityType.TYPE);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("context_dimension"), ContextDimension.TYPE);
++
++ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.RegisterSelectItemModelPropertyEvent(ID_MAPPER));
+ }
+ }
diff --git a/patches/net/minecraft/client/renderer/special/SpecialModelRenderers.java.patch b/patches/net/minecraft/client/renderer/special/SpecialModelRenderers.java.patch
new file mode 100644
index 0000000000..ea6de530f0
--- /dev/null
+++ b/patches/net/minecraft/client/renderer/special/SpecialModelRenderers.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/client/renderer/special/SpecialModelRenderers.java
++++ b/net/minecraft/client/renderer/special/SpecialModelRenderers.java
+@@ -171,6 +_,8 @@
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("decorated_pot"), DecoratedPotSpecialRenderer.Unbaked.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("standing_sign"), StandingSignSpecialRenderer.Unbaked.MAP_CODEC);
+ ID_MAPPER.put(ResourceLocation.withDefaultNamespace("hanging_sign"), HangingSignSpecialRenderer.Unbaked.MAP_CODEC);
++
++ net.neoforged.fml.ModLoader.postEvent(new net.neoforged.neoforge.client.event.RegisterSpecialModelRendererEvent(ID_MAPPER));
+ }
+
+ public static Map> createBlockRenderers(EntityModelSet p_387779_) {
diff --git a/patches/net/minecraft/client/renderer/state/MapRenderState.java.patch b/patches/net/minecraft/client/renderer/state/MapRenderState.java.patch
index 2ac034ae3d..cadb13bc87 100644
--- a/patches/net/minecraft/client/renderer/state/MapRenderState.java.patch
+++ b/patches/net/minecraft/client/renderer/state/MapRenderState.java.patch
@@ -1,9 +1,18 @@
--- a/net/minecraft/client/renderer/state/MapRenderState.java
+++ b/net/minecraft/client/renderer/state/MapRenderState.java
-@@ -17,6 +_,7 @@
+@@ -10,13 +_,14 @@
+ import net.neoforged.api.distmarker.OnlyIn;
+
+ @OnlyIn(Dist.CLIENT)
+-public class MapRenderState {
++public class MapRenderState extends net.neoforged.neoforge.client.renderstate.BaseRenderState {
+ @Nullable
+ public ResourceLocation texture;
+ public final List decorations = new ArrayList<>();
@OnlyIn(Dist.CLIENT)
- public static class MapDecorationRenderState {
+- public static class MapDecorationRenderState {
++ public static class MapDecorationRenderState extends net.neoforged.neoforge.client.renderstate.BaseRenderState {
+ public net.minecraft.core.Holder type;
@Nullable
public TextureAtlasSprite atlasSprite;
diff --git a/patches/net/minecraft/client/renderer/texture/AbstractTexture.java.patch b/patches/net/minecraft/client/renderer/texture/AbstractTexture.java.patch
index ce14e13dcf..ad9784c4ea 100644
--- a/patches/net/minecraft/client/renderer/texture/AbstractTexture.java.patch
+++ b/patches/net/minecraft/client/renderer/texture/AbstractTexture.java.patch
@@ -1,11 +1,15 @@
--- a/net/minecraft/client/renderer/texture/AbstractTexture.java
+++ b/net/minecraft/client/renderer/texture/AbstractTexture.java
-@@ -15,9 +_,13 @@
+@@ -12,6 +_,8 @@
public static final int NOT_ASSIGNED = -1;
protected int id = -1;
protected boolean defaultBlur;
+ protected boolean blur;
+ protected boolean mipmap;
+ private int wrapS = 10497;
+ private int wrapT = 10497;
+ private int minFilter = 9986;
+@@ -51,6 +_,8 @@
public void setFilter(boolean p_117961_, boolean p_117962_) {
RenderSystem.assertOnRenderThreadOrInit();
@@ -14,10 +18,10 @@
int i;
int j;
if (p_117961_) {
-@@ -31,6 +_,20 @@
- this.bind();
- GlStateManager._texParameter(3553, 10241, i);
- GlStateManager._texParameter(3553, 10240, j);
+@@ -75,6 +_,20 @@
+ this.magFilter = j;
+ }
+ }
+ }
+
+ // FORGE: This seems to have been stripped out, but we need it
diff --git a/patches/net/minecraft/client/renderer/texture/SpriteContents.java.patch b/patches/net/minecraft/client/renderer/texture/SpriteContents.java.patch
index 115bee7e58..5505e4800a 100644
--- a/patches/net/minecraft/client/renderer/texture/SpriteContents.java.patch
+++ b/patches/net/minecraft/client/renderer/texture/SpriteContents.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/texture/SpriteContents.java
+++ b/net/minecraft/client/renderer/texture/SpriteContents.java
-@@ -46,6 +_,10 @@
+@@ -47,6 +_,10 @@
this.byMipLevel = new NativeImage[]{this.originalImage};
}
@@ -11,18 +11,16 @@
public void increaseMipLevel(int p_248864_) {
try {
this.byMipLevel = MipmapGenerator.generateMipLevels(this.byMipLevel, p_248864_);
-@@ -120,6 +_,10 @@
+@@ -130,6 +_,8 @@
void upload(int p_248895_, int p_250245_, int p_250458_, int p_251337_, NativeImage[] p_248825_) {
for (int i = 0; i < this.byMipLevel.length; i++) {
-+ // Forge: Skip uploading if the texture would be made invalid by mip level
-+ if ((this.width >> i) <= 0 || (this.height >> i) <= 0)
-+ break;
-+
- p_248825_[i]
- .upload(i, p_248895_ >> i, p_250245_ >> i, p_250458_ >> i, p_251337_ >> i, this.width >> i, this.height >> i, this.byMipLevel.length > 1, false);
++ // NeoForge: Skip uploading if the texture would be made invalid by mip level
++ if ((this.width >> i) <= 0 || (this.height >> i) <= 0) break;
+ p_248825_[i].upload(i, p_248895_ >> i, p_250245_ >> i, p_250458_ >> i, p_251337_ >> i, this.width >> i, this.height >> i, false);
}
-@@ -242,7 +_,8 @@
+ }
+@@ -244,7 +_,8 @@
for (int i = 0; i < this.activeFrame.length; i++) {
int j = SpriteContents.this.width >> i;
int k = SpriteContents.this.height >> i;
@@ -32,7 +30,7 @@
}
}
-@@ -257,6 +_,9 @@
+@@ -259,6 +_,9 @@
for (int k = 0; k < this.activeFrame.length; k++) {
int l = SpriteContents.this.width >> k;
int i1 = SpriteContents.this.height >> k;
diff --git a/patches/net/minecraft/client/renderer/texture/TextureAtlas.java.patch b/patches/net/minecraft/client/renderer/texture/TextureAtlas.java.patch
index ffc654c26a..f0b8aec905 100644
--- a/patches/net/minecraft/client/renderer/texture/TextureAtlas.java.patch
+++ b/patches/net/minecraft/client/renderer/texture/TextureAtlas.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/renderer/texture/TextureAtlas.java
+++ b/net/minecraft/client/renderer/texture/TextureAtlas.java
-@@ -87,6 +_,8 @@
+@@ -82,6 +_,8 @@
this.sprites = List.copyOf(list);
this.animatedTextures = List.copyOf(list1);
}
@@ -9,10 +9,10 @@
}
@Override
-@@ -168,5 +_,9 @@
+@@ -159,5 +_,9 @@
- public void updateFilter(SpriteLoader.Preparations p_251993_) {
- this.setFilter(false, p_251993_.mipLevel() > 0);
+ int getHeight() {
+ return this.height;
+ }
+
+ public Map getTextures() {
diff --git a/patches/net/minecraft/client/renderer/texture/atlas/SpriteResourceLoader.java.patch b/patches/net/minecraft/client/renderer/texture/atlas/SpriteResourceLoader.java.patch
index 9051e59ca1..c281c0ab4b 100644
--- a/patches/net/minecraft/client/renderer/texture/atlas/SpriteResourceLoader.java.patch
+++ b/patches/net/minecraft/client/renderer/texture/atlas/SpriteResourceLoader.java.patch
@@ -1,24 +1,21 @@
--- a/net/minecraft/client/renderer/texture/atlas/SpriteResourceLoader.java
+++ b/net/minecraft/client/renderer/texture/atlas/SpriteResourceLoader.java
-@@ -24,7 +_,7 @@
+@@ -25,7 +_,7 @@
Logger LOGGER = LogUtils.getLogger();
- static SpriteResourceLoader create(Collection> p_296204_) {
-- return (p_293680_, p_293681_) -> {
-+ return (p_293680_, p_293681_, constructor) -> {
+ static SpriteResourceLoader create(Collection> p_296204_) {
+- return (p_389362_, p_389363_) -> {
++ return (p_389362_, p_389363_, constructor) -> {
ResourceMetadata resourcemetadata;
try {
- resourcemetadata = p_293681_.metadata().copySections(p_296204_);
-@@ -45,7 +_,7 @@
- .orElse(AnimationMetadataSection.EMPTY);
- FrameSize framesize = animationmetadatasection.calculateFrameSize(nativeimage.getWidth(), nativeimage.getHeight());
- if (Mth.isMultipleOf(nativeimage.getWidth(), framesize.width()) && Mth.isMultipleOf(nativeimage.getHeight(), framesize.height())) {
-- return new SpriteContents(p_293680_, framesize, nativeimage, resourcemetadata);
-+ return constructor.create(p_293680_, framesize, nativeimage, resourcemetadata);
- } else {
- LOGGER.error(
- "Image {} size {},{} is not multiple of frame size {},{}",
-@@ -62,5 +_,10 @@
+ resourcemetadata = p_389363_.metadata().copySections(p_296204_);
+@@ -62,10 +_,15 @@
+ framesize = new FrameSize(nativeimage.getWidth(), nativeimage.getHeight());
+ }
+
+- return new SpriteContents(p_389362_, framesize, nativeimage, resourcemetadata);
++ return constructor.create(p_389362_, framesize, nativeimage, resourcemetadata);
+ };
}
@Nullable
diff --git a/patches/net/minecraft/client/resources/model/BakedModel.java.patch b/patches/net/minecraft/client/resources/model/BakedModel.java.patch
index 15490942f9..e2ff981485 100644
--- a/patches/net/minecraft/client/resources/model/BakedModel.java.patch
+++ b/patches/net/minecraft/client/resources/model/BakedModel.java.patch
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/resources/model/BakedModel.java
+++ b/net/minecraft/client/resources/model/BakedModel.java
-@@ -13,7 +_,9 @@
+@@ -12,7 +_,9 @@
import net.neoforged.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
@@ -11,18 +11,15 @@
List getQuads(@Nullable BlockState p_235039_, @Nullable Direction p_235040_, RandomSource p_235041_);
boolean useAmbientOcclusion();
-@@ -24,9 +_,13 @@
+@@ -21,7 +_,11 @@
- boolean isCustomRenderer();
+ boolean usesBlockLight();
+ /**@deprecated Forge: Use {@link #getParticleIcon(net.neoforged.neoforge.client.model.data.ModelData)}*/
+ @Deprecated
TextureAtlasSprite getParticleIcon();
-- ItemTransforms getTransforms();
+ /**@deprecated Forge: Use {@link #applyTransform(net.minecraft.world.item.ItemDisplayContext, com.mojang.blaze3d.vertex.PoseStack, boolean)} instead */
+ @Deprecated
-+ default ItemTransforms getTransforms() { return ItemTransforms.NO_TRANSFORMS; }
-
- default BakedOverrides overrides() {
- return BakedOverrides.EMPTY;
+ ItemTransforms getTransforms();
+ }
diff --git a/patches/net/minecraft/client/resources/model/DelegateBakedModel.java.patch b/patches/net/minecraft/client/resources/model/DelegateBakedModel.java.patch
index 5c8eacbbe9..572e01296c 100644
--- a/patches/net/minecraft/client/resources/model/DelegateBakedModel.java.patch
+++ b/patches/net/minecraft/client/resources/model/DelegateBakedModel.java.patch
@@ -28,7 +28,7 @@
public boolean isGui3d() {
return this.parent.isGui3d();
}
-@@ -45,12 +_,44 @@
+@@ -40,12 +_,44 @@
}
@Override
@@ -64,8 +64,8 @@
+ }
+
+ @Override
-+ public List getRenderTypes(net.minecraft.world.item.ItemStack itemStack) {
-+ return this.parent.getRenderTypes(itemStack);
++ public net.minecraft.client.renderer.RenderType getRenderType(net.minecraft.world.item.ItemStack itemStack) {
++ return this.parent.getRenderType(itemStack);
+ }
+
+ @Override
diff --git a/patches/net/minecraft/client/resources/model/ItemModel.java.patch b/patches/net/minecraft/client/resources/model/ItemModel.java.patch
deleted file mode 100644
index 10d96b4e0a..0000000000
--- a/patches/net/minecraft/client/resources/model/ItemModel.java.patch
+++ /dev/null
@@ -1,16 +0,0 @@
---- a/net/minecraft/client/resources/model/ItemModel.java
-+++ b/net/minecraft/client/resources/model/ItemModel.java
-@@ -29,11 +_,11 @@
-
- @Override
- public BakedModel bake(ModelBaker p_371426_, Function p_371750_, ModelState p_371674_) {
-- BakedModel bakedmodel = p_371426_.bake(this.id, p_371674_);
-+ BakedModel bakedmodel = p_371426_.bake(this.id, p_371674_, p_371750_);
- if (this.overrides.isEmpty()) {
- return bakedmodel;
- } else {
-- BakedOverrides bakedoverrides = new BakedOverrides(p_371426_, this.overrides);
-+ BakedOverrides bakedoverrides = new BakedOverrides(p_371426_, this.overrides, p_371750_);
- return new ItemModel.BakedModelWithOverrides(bakedmodel, bakedoverrides);
- }
- }
diff --git a/patches/net/minecraft/client/resources/model/ModelBaker.java.patch b/patches/net/minecraft/client/resources/model/ModelBaker.java.patch
index 36016793f0..db7b52b0bf 100644
--- a/patches/net/minecraft/client/resources/model/ModelBaker.java.patch
+++ b/patches/net/minecraft/client/resources/model/ModelBaker.java.patch
@@ -1,14 +1,11 @@
--- a/net/minecraft/client/resources/model/ModelBaker.java
+++ b/net/minecraft/client/resources/model/ModelBaker.java
-@@ -5,6 +_,10 @@
+@@ -6,7 +_,7 @@
import net.neoforged.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
-public interface ModelBaker {
+public interface ModelBaker extends net.neoforged.neoforge.client.extensions.IModelBakerExtension {
-+ /**
-+ * @deprecated Forge: Use {@link #bake(ResourceLocation, ModelState, java.util.function.Function)} instead.
-+ */
-+ @Deprecated
BakedModel bake(ResourceLocation p_250776_, ModelState p_251280_);
- }
+
+ SpriteGetter sprites();
diff --git a/patches/net/minecraft/client/resources/model/ModelBakery.java.patch b/patches/net/minecraft/client/resources/model/ModelBakery.java.patch
index ed116d3ffe..694309384e 100644
--- a/patches/net/minecraft/client/resources/model/ModelBakery.java.patch
+++ b/patches/net/minecraft/client/resources/model/ModelBakery.java.patch
@@ -1,52 +1,84 @@
--- a/net/minecraft/client/resources/model/ModelBakery.java
+++ b/net/minecraft/client/resources/model/ModelBakery.java
-@@ -94,25 +_,46 @@
- }
-
- @Override
-+ @org.jetbrains.annotations.Nullable
-+ public UnbakedModel getTopLevelModel(ModelResourceLocation location) {
-+ return topModels.get(location);
-+ }
-+
-+ @Override
-+ public Function getModelTextureGetter() {
-+ return this.modelTextureGetter;
-+ }
+@@ -46,19 +_,36 @@
+ private final Map clientInfos;
+ final Map unbakedPlainModels;
+ final UnbakedModel missingModel;
++ private final Map standaloneModels;
+
-+ @Override
- public BakedModel bake(ResourceLocation p_252176_, ModelState p_249765_) {
-+ return bake(p_252176_, p_249765_, this.modelTextureGetter);
-+ }
-+
-+ @Override
-+ public BakedModel bake(ResourceLocation p_252176_, ModelState p_249765_, Function sprites) {
- ModelBakery.BakedCacheKey modelbakery$bakedcachekey = new ModelBakery.BakedCacheKey(p_252176_, p_249765_.getRotation(), p_249765_.isUvLocked());
- BakedModel bakedmodel = ModelBakery.this.bakedCache.get(modelbakery$bakedcachekey);
- if (bakedmodel != null) {
- return bakedmodel;
- } else {
- UnbakedModel unbakedmodel = this.getModel(p_252176_);
-- BakedModel bakedmodel1 = this.bakeUncached(unbakedmodel, p_249765_);
-+ BakedModel bakedmodel1 = this.bakeUncached(unbakedmodel, p_249765_, sprites);
- ModelBakery.this.bakedCache.put(modelbakery$bakedcachekey, bakedmodel1);
- return bakedmodel1;
- }
- }
++ /**
++ * @deprecated Neo: use {@link #ModelBakery(EntityModelSet, Map, Map, Map, UnbakedModel, Map)} ModelBakery instead}
++ */
++ @Deprecated
++ public ModelBakery(
++ EntityModelSet p_388903_,
++ Map p_251087_,
++ Map p_250416_,
++ Map p_388404_,
++ UnbakedModel p_360944_
++ ) {
++ this(p_388903_, p_251087_, p_250416_, p_388404_, p_360944_, Map.of());
++ }
- BakedModel bakeUncached(UnbakedModel p_352386_, ModelState p_352194_) {
-+ return bakeUncached(p_352386_, p_352194_, this.modelTextureGetter);
-+ }
-+
-+ @Override
-+ public BakedModel bakeUncached(UnbakedModel p_352386_, ModelState p_352194_, Function sprites) {
- if (p_352386_ instanceof BlockModel blockmodel && blockmodel.getRootModel() == SpecialModels.GENERATED_MARKER) {
-- return ModelBakery.ITEM_MODEL_GENERATOR.generateBlockModel(this.modelTextureGetter, blockmodel).bake(this.modelTextureGetter, p_352194_, false);
-+ return ModelBakery.ITEM_MODEL_GENERATOR.generateBlockModel(sprites, blockmodel).bake(sprites, p_352194_, false);
+ public ModelBakery(
+ EntityModelSet p_388903_,
+ Map p_251087_,
+ Map p_250416_,
+ Map p_388404_,
+- UnbakedModel p_360944_
++ UnbakedModel p_360944_,
++ Map standaloneModels
+ ) {
+ this.entityModelSet = p_388903_;
+ this.unbakedBlockStateModels = p_251087_;
+ this.clientInfos = p_250416_;
+ this.unbakedPlainModels = p_388404_;
+ this.missingModel = p_360944_;
++ this.standaloneModels = standaloneModels;
+ }
+
+ public ModelBakery.BakingResult bakeModels(ModelBakery.TextureGetter p_352431_) {
+@@ -92,7 +_,18 @@
+ LOGGER.warn("Unable to bake item model: '{}'", p_390101_, exception);
}
+ });
+- return new ModelBakery.BakingResult(bakedmodel, map, itemmodel, map1, map2);
++ Map bakedStandaloneModels = new HashMap<>(this.standaloneModels.size());
++ this.standaloneModels.forEach((location, model) -> {
++ try {
++ ModelDebugName debugName = () -> location + "#standalone";
++ ModelBakerImpl modelBaker = new ModelBakerImpl(p_352431_, debugName);
++ BakedModel bakedModel = UnbakedModel.bakeWithTopModelValues(model, modelBaker, BlockModelRotation.X0_Y0);
++ bakedStandaloneModels.put(location, bakedModel);
++ } catch (Exception exception) {
++ LOGGER.warn("Unable to bake standalone model: '{}': {}", location, exception);
++ }
++ });
++ return new ModelBakery.BakingResult(bakedmodel, map, itemmodel, map1, map2, bakedStandaloneModels);
+ }
-- return p_352386_.bake(this, this.modelTextureGetter, p_352194_);
-+ return p_352386_.bake(this, sprites, p_352194_);
- }
+ @OnlyIn(Dist.CLIENT)
+@@ -105,8 +_,22 @@
+ Map blockStateModels,
+ ItemModel missingItemModel,
+ Map itemStackModels,
+- Map itemProperties
++ Map itemProperties,
++ Map standaloneModels
+ ) {
++ /**
++ * @deprecated Neo: use {@link #BakingResult(BakedModel, Map, ItemModel, Map, Map, Map)}} instead
++ */
++ @Deprecated
++ public BakingResult(
++ BakedModel missingModel,
++ Map blockStateModels,
++ ItemModel missingItemModel,
++ Map itemStackModels,
++ Map itemProperties
++ ) {
++ this(missingModel, blockStateModels, missingItemModel, itemStackModels, itemProperties, Map.of());
++ }
}
+ @OnlyIn(Dist.CLIENT)
diff --git a/patches/net/minecraft/client/resources/model/ModelDiscovery.java.patch b/patches/net/minecraft/client/resources/model/ModelDiscovery.java.patch
index 7c9de8482a..dba9824817 100644
--- a/patches/net/minecraft/client/resources/model/ModelDiscovery.java.patch
+++ b/patches/net/minecraft/client/resources/model/ModelDiscovery.java.patch
@@ -1,26 +1,23 @@
--- a/net/minecraft/client/resources/model/ModelDiscovery.java
+++ b/net/minecraft/client/resources/model/ModelDiscovery.java
-@@ -49,6 +_,7 @@
- });
- set.add(ItemRenderer.TRIDENT_MODEL);
- set.add(ItemRenderer.SPYGLASS_MODEL);
-+ net.neoforged.neoforge.client.ClientHooks.onRegisterAdditionalModels(set);
- return set;
+@@ -22,6 +_,7 @@
+ final UnbakedModel missingModel;
+ private final List topModels = new ArrayList<>();
+ private final Map referencedModels = new HashMap<>();
++ final Map