diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 85337569e1e..3721eefed44 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -8,6 +8,6 @@ assignees: '' --- - + diff --git a/.github/workflows/build-prs.yml b/.github/workflows/build-prs.yml index f59e4f6be33..a43d5697ed3 100644 --- a/.github/workflows/build-prs.yml +++ b/.github/workflows/build-prs.yml @@ -7,6 +7,10 @@ on: - opened - ready_for_review - reopened + push: + branches: + - 'feature/**' + workflow_dispatch: jobs: build: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..5f879c00b60 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: Release + +on: + push: + branches: + - '1.*' + +permissions: + contents: read + statuses: write + +jobs: + release: + uses: neoforged/actions/.github/workflows/gradle-publish.yml@main + with: + java: 17 + pre_gradle_tasks: setup + gradle_tasks: publish + version_labels: -beta, -stable + + # We only publish the main repository. This way we avoid publishing Kits + if: ${{ github.repository == 'neoforged/NeoForge' }} + secrets: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + MAVEN_USER: ${{ secrets.MAVEN_USER }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + GPG_SUBKEY: ${{ secrets.GPG_SUBKEY }} + GPG_SUBKEY_ID: ${{ secrets.GPG_SUBKEY_ID }} + GPG_SUBKEY_PASSWORD: ${{ secrets.GPG_SUBKEY_PASSWORD }} diff --git a/.github/workflows/test-prs.yml b/.github/workflows/test-prs.yml index a3ea6f047a4..06f6bb8d17a 100644 --- a/.github/workflows/test-prs.yml +++ b/.github/workflows/test-prs.yml @@ -7,6 +7,10 @@ on: - opened - ready_for_review - reopened + push: + branches: + - 'feature/**' + workflow_dispatch: jobs: build: diff --git a/.teamcity/patches/buildTypes/MinecraftForge_MinecraftForge__BuildSecondaryBranches.kts b/.teamcity/patches/buildTypes/MinecraftForge_MinecraftForge__BuildSecondaryBranches.kts deleted file mode 100644 index 01a16b8dedb..00000000000 --- a/.teamcity/patches/buildTypes/MinecraftForge_MinecraftForge__BuildSecondaryBranches.kts +++ /dev/null @@ -1,29 +0,0 @@ -package patches.buildTypes - -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.ui.* - -/* -This patch script was generated by TeamCity on settings change in UI. -To apply the patch, change the buildType with id = 'MinecraftForge_MinecraftForge__BuildSecondaryBranches' -accordingly, and delete the patch script. -*/ -changeBuildType(RelativeId("MinecraftForge_MinecraftForge__BuildSecondaryBranches")) { - vcs { - - check(branchFilter == "+:*") { - "Unexpected option value: branchFilter = $branchFilter" - } - branchFilter = """ - -:master - -:main - -: - -:%git_main_branch% - -:main* - -:master* - -:default - -:1.* - +:* - """.trimIndent() - } -} diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml deleted file mode 100644 index a8a6f382b45..00000000000 --- a/.teamcity/pom.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - 4.0.0 - MinecraftForge_Forge Config DSL Script - MinecraftForge_Forge - MinecraftForge_Forge_dsl - 1.0-SNAPSHOT - - - org.jetbrains.teamcity - configs-dsl-kotlin-parent - 1.0-SNAPSHOT - - - - - jetbrains-all - https://download.jetbrains.com/teamcity-repository - - true - - - - teamcity-server - https://teamcity.minecraftforge.net/app/dsl-plugins-repository - - true - - - - - - - JetBrains - https://download.jetbrains.com/teamcity-repository - - - - - ${basedir} - - - kotlin-maven-plugin - org.jetbrains.kotlin - ${kotlin.version} - - - - - compile - process-sources - - compile - - - - test-compile - process-test-sources - - test-compile - - - - - - org.jetbrains.teamcity - teamcity-configs-maven-plugin - ${teamcity.dsl.version} - - kotlin - target/generated-configs - - - - - - - - org.jetbrains.teamcity - configs-dsl-kotlin - ${teamcity.dsl.version} - compile - - - org.jetbrains.teamcity - configs-dsl-kotlin-plugins - 1.0-SNAPSHOT - pom - compile - - - org.jetbrains.kotlin - kotlin-stdlib-jdk8 - ${kotlin.version} - compile - - - org.jetbrains.kotlin - kotlin-script-runtime - ${kotlin.version} - compile - - - \ No newline at end of file diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts deleted file mode 100644 index 1b1057bef2b..00000000000 --- a/.teamcity/settings.kts +++ /dev/null @@ -1,106 +0,0 @@ -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubIssues -import jetbrains.buildServer.configs.kotlin.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle - -/* -The settings script is an entry point for defining a TeamCity -project hierarchy. The script should contain a single call to the -project() function with a Project instance or an init function as -an argument. - -VcsRoots, BuildTypes, Templates, and subprojects can be -registered inside the project using the vcsRoot(), buildType(), -template(), and subProject() methods respectively. - -To debug settings scripts in command-line, run the - - mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate - -command and attach your debugger to the port 8000. - -To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View --> Tool Windows -> Maven Projects), find the generate task node -(Plugins -> teamcity-configs -> teamcity-configs:generate), the -'Debug' option is available in the context menu for the task. -*/ - -version = "2021.2" - -project { - - buildType(Build) - buildType(BuildSecondaryBranches) - - params { - text("docker_jdk_version", "17", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false) - text("docker_gradle_version", "7.5.1", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false) - text("git_main_branch", "1.20.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false) - text("git_branch_spec", """ - +:refs/heads/(main*) - +:refs/heads/(master*) - +:refs/heads/(develop|release|staging|main|master) - +:refs/heads/(1.*) - """.trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true) - text("github_repository_name", "NeoForge", label = "The github repository name. Used to connect to it in VCS Roots.", description = "This is the repository slug on github. So for example `MinecraftForge` or `MinecraftForge`. It is interpolated into the global VCS Roots.", display = ParameterDisplay.HIDDEN, allowEmpty = false) - text("env.PUBLISHED_JAVA_ARTIFACT_ID", "forge", label = "Published artifact id", description = "The maven coordinate artifact id that has been published by this build. Can not be empty.", allowEmpty = false) - text("env.PUBLISHED_JAVA_FML_ARTIFACT_ID", "fmlonly", label = "Published fmlonly artifact id", description = "The maven coordinate artifact id for fml only that has been published by this build. Can not be empty.", allowEmpty = false) - text("env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION", "0.0.0-SNAPSHOT", label = "Published fmlonly artifact version", description = "The version for fml only that has been published by this build. Can not be empty.", allowEmpty = false) - text("env.PUBLISHED_JAVA_GROUP", "net.neoforged", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false) - //These are references and not actually keys - password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN) - password("env.KEYSTORE_URL", "credentialsJSON:a7ae1c82-8058-4061-8d12-7f6bc2618d2e", display = ParameterDisplay.HIDDEN) - password("env.KEYSTORE_PASSWORD", "credentialsJSON:d7b964e3-a1fd-47a8-b892-6f601fe47479", display = ParameterDisplay.HIDDEN) - text("additional_publishing_gradle_parameters", "-PcrowdinKey=%env.CROWDIN_KEY% -PkeystoreKeyPass=%env.KEYSTORE_PASSWORD% -PkeystoreStorePass=%env.KEYSTORE_PASSWORD% -Pkeystore=%env.KEYSTORE_URL%", label = "Additional gradle parameters for publish", description = "Contains the additional gradle parameters used during publishing.", display = ParameterDisplay.HIDDEN, allowEmpty = true) - - checkbox("should_execute_build", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN, - checked = "true", unchecked = "false") - checkbox("should_execute_test", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN, - checked = "true", unchecked = "false") - } - - features { - githubIssues { - id = "MinecraftForge_MinecraftForge__IssueTracker" - displayName = "neoforged/NeoForge" - repositoryURL = "https://github.com/neoforged/NeoForge" - } - } -} - -object Build : BuildType({ - templates(AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"), AbsoluteId("MinecraftForge_PublishProjectUsingGradle"), AbsoluteId("MinecraftForge_TriggersStaticFilesWebpageGenerator")) - id("MinecraftForge_MinecraftForge__Build") - name = "Build" - description = "Builds and Publishes the main branches of the project." - - features { - feature { - id = "trigger_fml_only_files_generator" - type = "triggerBuildFeature" - param("triggers", "MinecraftForge_FilesGenerator_GeneratePages") - param("parameters", """ - env.PUBLISHED_JAVA_FML_ARTIFACT_ID~env.PUBLISHED_JAVA_ARTIFACT_ID - env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION~env.PUBLISHED_JAVA_ARTIFACT_VERSION - env.PUBLISHED_JAVA_GROUP - """.trimIndent()) - } - } -}) - -object BuildSecondaryBranches : BuildType({ - templates(AbsoluteId("MinecraftForge_ExcludesBuildingDefaultBranch"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle")) - id("MinecraftForge_MinecraftForge__BuildSecondaryBranches") - name = "Build - Secondary Branches" - description = "Builds and Publishes the secondary branches of the project." - params { - text("git_branch_spec", """ - -:refs/heads/(main*) - -:refs/heads/(master*) - -:refs/heads/(develop|release|staging|main|master) - -:refs/heads/(1.*) - +:refs/heads/(*) - -:refs/heads/noci* - """.trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true) - } -}) diff --git a/build.gradle b/build.gradle index b0738dc8c0c..ec7517b1c09 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'net.neoforged.gradleutils' version '3.0.0-alpha.1' apply false + id 'net.neoforged.gradleutils' version '3.0.0-alpha.8' apply false id 'com.diffplug.spotless' version '6.22.0' apply false id 'net.neoforged.licenser' version '0.7.2' apply false } @@ -68,6 +68,15 @@ spotless { toggleOffOn() eclipse().configFile file('codeformat/formatter-config.xml') importOrder() + + // courtesy of diffplug/spotless#240 + // https://github.com/diffplug/spotless/issues/240#issuecomment-385206606 + custom 'noWildcardImports', { String fileContents -> + if (fileContents.contains('*;\n')) { + throw new GradleException('No wildcard imports are allowed!') + } + } + bumpThisNumberIfACustomStepChanges(1) } format 'patches', { target rootProject.fileTree("patches") diff --git a/gradle.properties b/gradle.properties index 4092c9c614f..3a0a84adf36 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ java_version=17 minecraft_version=1.20.4 neoform_version=20231207.154220 -spi_version=8.0.0 +spi_version=9.0.2 mergetool_version=2.0.0 accesstransformers_version=10.0.1 coremods_version=6.0.2 @@ -30,7 +30,7 @@ jetbrains_annotations_version=24.0.1 slf4j_api_version=2.0.7 apache_maven_artifact_version=3.8.5 jarjar_version=0.4.0 -fancy_mod_loader_version=1.0.17 +fancy_mod_loader_version=2.0.3 mojang_logging_version=1.1.1 log4j_version=2.19.0 guava_version=31.1.2-jre @@ -42,7 +42,7 @@ unsafe_version=0.2.+ typetools_version=0.6.3 nashorn_core_version=15.3 lwjgl_glfw_version=3.3.2 -mixin_extras_version=0.3.1 +mixin_extras_version=0.3.2 jupiter_api_version=5.7.0 vintage_engine_version=5.+ diff --git a/patches/net/minecraft/advancements/critereon/ItemPredicate.java.patch b/patches/net/minecraft/advancements/critereon/ItemPredicate.java.patch index faa9aa3deff..66e8af717ca 100644 --- a/patches/net/minecraft/advancements/critereon/ItemPredicate.java.patch +++ b/patches/net/minecraft/advancements/critereon/ItemPredicate.java.patch @@ -17,17 +17,16 @@ p_297904_ -> p_297904_.group( ExtraCodecs.strictOptionalField(TagKey.codec(Registries.ITEM), "tag").forGetter(ItemPredicate::tag), ExtraCodecs.strictOptionalField(ITEMS_CODEC, "items").forGetter(ItemPredicate::items), -@@ -51,8 +_,34 @@ +@@ -51,8 +_,33 @@ ) .apply(p_297904_, ItemPredicate::new) ); + public static Codec CODEC = ExtraCodecs.either( -+ // Use dispatchUnsafe to always inline the dispatched type parameters into the root ingredient object, next to the "type" -+ net.neoforged.neoforge.common.util.NeoForgeExtraCodecs.dispatchUnsafe( -+ net.neoforged.neoforge.registries.NeoForgeRegistries.ITEM_PREDICATE_SERIALIZERS.byNameCodec(), -+ net.neoforged.neoforge.common.advancements.critereon.ICustomItemPredicate::codec, -+ java.util.function.Function.identity()), -+ VANILLA_CODEC ++ net.neoforged.neoforge.registries.NeoForgeRegistries.ITEM_PREDICATE_SERIALIZERS.byNameCodec() ++ .dispatch( ++ net.neoforged.neoforge.common.advancements.critereon.ICustomItemPredicate::codec, ++ java.util.function.Function.identity()), ++ VANILLA_CODEC + ).xmap(either -> either.map(ItemPredicate::new, p -> p), predicate -> { + // Serialize using dispatch codec if custom logic is present, otherwise use vanilla codec + if (predicate.customLogic.isPresent()) { diff --git a/patches/net/minecraft/client/Options.java.patch b/patches/net/minecraft/client/Options.java.patch index 778f6a6b2cc..63c153e9881 100644 --- a/patches/net/minecraft/client/Options.java.patch +++ b/patches/net/minecraft/client/Options.java.patch @@ -12,6 +12,15 @@ && !Minecraft.getInstance().getSoundManager().getAvailableSoundDevices().contains(p_232011_) ? Optional.empty() : Optional.of(p_232011_), +@@ -787,7 +_,7 @@ + this.incompatibleResourcePacks.clear(); + + for(Pack pack : p_275268_.getSelectedPacks()) { +- if (!pack.isFixedPosition()) { ++ if (!pack.isFixedPosition() && !pack.isHidden()) { + this.resourcePacks.add(pack.getId()); + if (!pack.getCompatibility().isCompatible()) { + this.incompatibleResourcePacks.add(pack.getId()); @@ -1058,6 +_,7 @@ } diff --git a/patches/net/minecraft/client/gui/components/DebugScreenOverlay.java.patch b/patches/net/minecraft/client/gui/components/DebugScreenOverlay.java.patch index f03d0c03272..8553dd94b5b 100644 --- a/patches/net/minecraft/client/gui/components/DebugScreenOverlay.java.patch +++ b/patches/net/minecraft/client/gui/components/DebugScreenOverlay.java.patch @@ -40,6 +40,20 @@ this.renderLines(p_281261_, list, false); } +@@ -508,6 +_,13 @@ + GlUtil.getRenderer(), + GlUtil.getOpenGLVersion() + ); ++ if (this.minecraft.isDemo()) { ++ if (this.minecraft.level.getGameTime() >= 120500L) { ++ list.add(0, net.minecraft.network.chat.Component.translatable("demo.demoExpired").getString()); ++ } else { ++ list.add(0, net.minecraft.network.chat.Component.translatable("demo.remainingTime", net.minecraft.util.StringUtil.formatTickDuration((int)(120500L - this.minecraft.level.getGameTime()), this.minecraft.level.tickRateManager().tickrate())).getString()); ++ } ++ } + if (this.minecraft.showOnlyReducedInfo()) { + return list; + } else { @@ -544,6 +_,7 @@ list.add(""); list.add(ChatFormatting.UNDERLINE + "Targeted Entity"); diff --git a/patches/net/minecraft/client/gui/screens/controls/KeyBindsList.java.patch b/patches/net/minecraft/client/gui/screens/controls/KeyBindsList.java.patch index c68037a8df1..c532d3adfd9 100644 --- a/patches/net/minecraft/client/gui/screens/controls/KeyBindsList.java.patch +++ b/patches/net/minecraft/client/gui/screens/controls/KeyBindsList.java.patch @@ -1,42 +1,5 @@ --- a/net/minecraft/client/gui/screens/controls/KeyBindsList.java +++ b/net/minecraft/client/gui/screens/controls/KeyBindsList.java -@@ -30,6 +_,8 @@ - final KeyBindsScreen keyBindsScreen; - int maxNameWidth; - -+ final static int NAME_SPLIT_LENGTH = 185; -+ - public KeyBindsList(KeyBindsScreen p_193861_, Minecraft p_193862_) { - super(p_193862_, p_193861_.width + 45, p_193861_.height - 52, 20, 20); - this.keyBindsScreen = p_193861_; -@@ -47,7 +_,8 @@ - Component component = Component.translatable(keymapping.getName()); - int i = p_193862_.font.width(component); - if (i > this.maxNameWidth) { -- this.maxNameWidth = i; -+ // Neo: max width for the keybind descriptions to make all readable -+ this.maxNameWidth = Math.min(i, NAME_SPLIT_LENGTH); - } - - this.addEntry(new KeyBindsList.KeyEntry(keymapping, component)); -@@ -65,7 +_,7 @@ - - @Override - protected int getScrollbarPosition() { -- return super.getScrollbarPosition() + 15; -+ return super.getScrollbarPosition() + 15 + 20; - } - - @Override -@@ -157,7 +_,7 @@ - KeyBindsList.this.keyBindsScreen.selectedKey = p_193916_; - KeyBindsList.this.resetMappingAndUpdateButtons(); - }) -- .bounds(0, 0, 75, 20) -+ .bounds(0, 0, 75 + 20, 20) //Forge: Add a space (the + 20) so the formatting works better. - .createNarration( - p_253311_ -> p_193916_.isUnbound() - ? Component.translatable("narrator.controls.unbound", p_193917_) @@ -165,6 +_,7 @@ ) .build(); @@ -45,29 +8,24 @@ KeyBindsList.this.minecraft.options.setKey(p_193916_, p_193916_.getDefaultKey()); KeyBindsList.this.resetMappingAndUpdateButtons(); }).bounds(0, 0, 50, 20).createNarration(p_253313_ -> Component.translatable("narrator.controls.reset", p_193917_)).build(); -@@ -185,8 +_,14 @@ +@@ -184,8 +_,9 @@ + boolean p_282605_, float p_281432_ ) { - int k = p_281373_ + 90 - KeyBindsList.this.maxNameWidth; +- int k = p_281373_ + 90 - KeyBindsList.this.maxNameWidth; - p_281805_.drawString(KeyBindsList.this.minecraft.font, this.name, k, p_282357_ + p_281932_ / 2 - 9 / 2, 16777215, false); -- this.resetButton.setX(p_281373_ + 190); -+ // Neo: Trim strings that are too long, and show a tooltip if the mouse is over the trimmed string -+ List lines = KeyBindsList.this.minecraft.font.getSplitter().splitLines(this.name, NAME_SPLIT_LENGTH, net.minecraft.network.chat.Style.EMPTY); -+ Component nameComponent = lines.size() > 1 ? Component.literal(lines.get(0).getString() + "...") : this.name; -+ if(lines.size() > 1 && this.isMouseOver(p_282224_ + 95, p_282053_) && p_282224_ < p_281373_ - 90 + KeyBindsList.this.maxNameWidth) { -+ KeyBindsList.this.keyBindsScreen.setTooltipForNextRenderPass(net.minecraft.locale.Language.getInstance().getVisualOrder(lines)); -+ } -+ p_281805_.drawString(KeyBindsList.this.minecraft.font, nameComponent, k, p_282357_ + p_281932_ / 2 - 9 / 2, 16777215, false); -+ this.resetButton.setX(p_281373_ + 190 + 20); ++ int textMaxWidth = p_281805_.guiWidth() / 2; ++ int k = p_281373_ + 90 - Math.min(KeyBindsList.this.maxNameWidth, textMaxWidth - 40); ++ net.minecraft.client.gui.components.AbstractWidget.renderScrollingString(p_281805_, KeyBindsList.this.minecraft.font, this.name, k, k, p_282357_ + p_281932_ / 2 - 9 / 2, textMaxWidth, p_282357_ + p_281932_ / 2 - 9 / 2 + 16, 16777215); // Neo: Makes descriptions start scrolling if too long + this.resetButton.setX(p_281373_ + 190); this.resetButton.setY(p_282357_); this.resetButton.render(p_281805_, p_282224_, p_282053_, p_281432_); - this.changeButton.setX(p_281373_ + 105); @@ -218,7 +_,7 @@ MutableComponent mutablecomponent = Component.empty(); if (!this.key.isUnbound()) { for(KeyMapping keymapping : KeyBindsList.this.minecraft.options.keyMappings) { - if (keymapping != this.key && this.key.same(keymapping)) { -+ if ((keymapping != this.key && this.key.same(keymapping)) || keymapping.hasKeyModifierConflict(this.key)) { // FORGE: gracefully handle conflicts like SHIFT vs SHIFT+G ++ if ((keymapping != this.key && this.key.same(keymapping)) || keymapping.hasKeyModifierConflict(this.key)) { // Neo: gracefully handle conflicts like SHIFT vs SHIFT+G if (this.hasCollision) { mutablecomponent.append(", "); } diff --git a/patches/net/minecraft/client/gui/screens/packs/PackSelectionModel.java.patch b/patches/net/minecraft/client/gui/screens/packs/PackSelectionModel.java.patch index 7693d01088a..98bea19a024 100644 --- a/patches/net/minecraft/client/gui/screens/packs/PackSelectionModel.java.patch +++ b/patches/net/minecraft/client/gui/screens/packs/PackSelectionModel.java.patch @@ -1,23 +1,11 @@ --- a/net/minecraft/client/gui/screens/packs/PackSelectionModel.java +++ b/net/minecraft/client/gui/screens/packs/PackSelectionModel.java -@@ -106,6 +_,8 @@ - boolean canMoveUp(); - - boolean canMoveDown(); -+ -+ default boolean notHidden() { return true; } - } - - @OnlyIn(Dist.CLIENT) -@@ -205,6 +_,11 @@ - @Override - public void moveDown() { - this.move(1); -+ } -+ -+ @Override -+ public boolean notHidden() { -+ return !pack.isHidden(); - } - } - +@@ -31,7 +_,7 @@ + this.onListChanged = p_99909_; + this.iconGetter = p_99910_; + this.repository = p_99911_; +- this.selected = Lists.newArrayList(p_99911_.getSelectedPacks()); ++ this.selected = Lists.newArrayList(p_99911_.getSelectedPacks().stream().filter(p -> !p.isHidden()).toList()); + Collections.reverse(this.selected); + this.unselected = Lists.newArrayList(p_99911_.getAvailablePacks()); + this.unselected.removeAll(this.selected); diff --git a/patches/net/minecraft/client/gui/screens/packs/PackSelectionScreen.java.patch b/patches/net/minecraft/client/gui/screens/packs/PackSelectionScreen.java.patch deleted file mode 100644 index cf2aa31c079..00000000000 --- a/patches/net/minecraft/client/gui/screens/packs/PackSelectionScreen.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/client/gui/screens/packs/PackSelectionScreen.java -+++ b/net/minecraft/client/gui/screens/packs/PackSelectionScreen.java -@@ -144,7 +_,7 @@ - TransferableSelectionList.PackEntry transferableselectionlist$packentry = p_100014_.getSelected(); - String s = transferableselectionlist$packentry == null ? "" : transferableselectionlist$packentry.getPackId(); - p_100014_.setSelected(null); -- p_100015_.forEach( -+ p_100015_.filter(PackSelectionModel.Entry::notHidden).forEach( - p_313437_ -> { - TransferableSelectionList.PackEntry transferableselectionlist$packentry1 = new TransferableSelectionList.PackEntry( - this.minecraft, p_100014_, p_313437_ 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 890774ac5df..8fe49180936 100644 --- a/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch +++ b/patches/net/minecraft/client/gui/screens/worldselection/CreateWorldScreen.java.patch @@ -32,7 +32,7 @@ if (path != null) { if (this.tempDataPackRepository == null) { this.tempDataPackRepository = ServerPacksSource.createPackRepository(path, this.packValidator); -+ net.neoforged.neoforge.resource.ResourcePackLoader.loadResourcePacks(this.tempDataPackRepository, net.neoforged.neoforge.server.ServerLifecycleHooks::buildPackFinder); ++ net.neoforged.neoforge.resource.ResourcePackLoader.loadResourcePacks(this.tempDataPackRepository, map -> net.neoforged.neoforge.resource.ResourcePackLoader.buildPackFinder(map, net.minecraft.server.packs.PackType.SERVER_DATA)); this.tempDataPackRepository.reload(); } diff --git a/patches/net/minecraft/client/renderer/GameRenderer.java.patch b/patches/net/minecraft/client/renderer/GameRenderer.java.patch index fadb0f73383..62ac6a5530d 100644 --- a/patches/net/minecraft/client/renderer/GameRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/GameRenderer.java.patch @@ -17,22 +17,26 @@ } catch (IOException ioexception) { list1.forEach(p_172729_ -> p_172729_.getFirst().close()); throw new RuntimeException("could not reload shaders", ioexception); -@@ -834,12 +_,19 @@ +@@ -834,12 +_,25 @@ this.minecraft.getProfiler().push("pick"); this.minecraft.crosshairPickEntity = null; double d0 = (double)this.minecraft.gameMode.getPickRange(); -- this.minecraft.hitResult = entity.pick(d0, p_109088_, false); + double entityReach = this.minecraft.player.getEntityReach(); // Note - MC-76493 - We must validate players cannot click-through objects. -+ this.minecraft.hitResult = entity.pick(Math.max(d0, entityReach), p_109088_, false); // Run pick() with the max of the two, so we can prevent click-through. ++ d0 = Math.max(d0, entityReach); // Calculate hit results with the max of both reach distances, and compare against the individual ones later. This prevents click-through. + this.minecraft.hitResult = entity.pick(d0, p_109088_, false); Vec3 vec3 = entity.getEyePosition(p_109088_); -- boolean flag = this.minecraft.gameMode.hasFarPickRange(); -+ boolean flag = false && this.minecraft.gameMode.hasFarPickRange(); ++ if (false) { // These flags are unused with attributes. + boolean flag = this.minecraft.gameMode.hasFarPickRange(); d0 = flag ? 6.0 : d0; boolean flag1 = !flag; - double d1 = this.minecraft.hitResult != null ? this.minecraft.hitResult.getLocation().distanceToSqr(vec3) : d0 * d0; -+ double d1 = this.minecraft.hitResult != null && this.minecraft.hitResult.getType() != HitResult.Type.MISS ? this.minecraft.hitResult.getLocation().distanceToSqr(vec3) : d0 * d0; ++ } ++ // If we picked a block, we need to ignore entities past that block. Added != MISS check to not truncate on failed picks. ++ // Also fixes MC-250858 ++ // Note: If vanilla ever actually fixes MC-250858 and adds this != MISS check, IPlayerExtension#getEntityReach needs to be updated from +2 to +3. ++ double d1 = this.minecraft.hitResult != null && this.minecraft.hitResult.getType() != HitResult.Type.MISS ? this.minecraft.hitResult.getLocation().distanceToSqr(vec3) : d0 * d0; + double blockReach = this.minecraft.player.getBlockReach(); -+ // Discard the result as a miss if it is outside the block reach. ++ // Discard the block pick result as a miss if it is outside the block reach. + if (d1 > blockReach * blockReach) { + Vec3 pos = this.minecraft.hitResult.getLocation(); + this.minecraft.hitResult = BlockHitResult.miss(pos, Direction.getNearest(vec3.x, vec3.y, vec3.z), BlockPos.containing(pos)); diff --git a/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch index ff968db6364..f2fda5062ba 100644 --- a/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/blockentity/PistonHeadRenderer.java.patch @@ -11,3 +11,14 @@ RenderType rendertype = ItemBlockRenderTypes.getMovingBlockRenderType(p_112460_); VertexConsumer vertexconsumer = p_112462_.getBuffer(rendertype); this.blockRenderer +@@ -86,5 +_,10 @@ + @Override + public int getViewDistance() { + return 68; ++ } ++ ++ @Override ++ public net.minecraft.world.phys.AABB getRenderBoundingBox(PistonMovingBlockEntity blockEntity) { ++ return INFINITE_EXTENT_AABB; + } + } diff --git a/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch index da40479869f..88dd656992a 100644 --- a/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/blockentity/ShulkerBoxRenderer.java.patch @@ -8,6 +8,6 @@ + @Override + public net.minecraft.world.phys.AABB getRenderBoundingBox(ShulkerBoxBlockEntity blockEntity) { + 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.5, pos.getZ() + 1.0); ++ return new net.minecraft.world.phys.AABB(pos.getX() - 0.5, pos.getY() - 0.5, pos.getZ() - 0.5, pos.getX() + 1.5, pos.getY() + 1.5, pos.getZ() + 1.5); + } } diff --git a/patches/net/minecraft/client/renderer/blockentity/SpawnerRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/SpawnerRenderer.java.patch new file mode 100644 index 00000000000..351d861b081 --- /dev/null +++ b/patches/net/minecraft/client/renderer/blockentity/SpawnerRenderer.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/client/renderer/blockentity/SpawnerRenderer.java ++++ b/net/minecraft/client/renderer/blockentity/SpawnerRenderer.java +@@ -57,4 +_,10 @@ + p_312703_.render(p_312223_, 0.0, 0.0, 0.0, 0.0F, p_311943_, p_312805_, p_312394_, p_311996_); + p_312805_.popPose(); + } ++ ++ @Override ++ public net.minecraft.world.phys.AABB getRenderBoundingBox(SpawnerBlockEntity blockEntity) { ++ net.minecraft.core.BlockPos pos = blockEntity.getBlockPos(); ++ return new net.minecraft.world.phys.AABB(pos.getX() - 1.0, pos.getY() - 1.0, pos.getZ() - 1.0, pos.getX() + 2.0, pos.getY() + 2.0, pos.getZ() + 2.0); ++ } + } diff --git a/patches/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java.patch new file mode 100644 index 00000000000..e495d572fdf --- /dev/null +++ b/patches/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java ++++ b/net/minecraft/client/renderer/blockentity/TheEndGatewayRenderer.java +@@ -51,4 +_,9 @@ + public int getViewDistance() { + return 256; + } ++ ++ @Override ++ public net.minecraft.world.phys.AABB getRenderBoundingBox(TheEndGatewayBlockEntity blockEntity) { ++ return blockEntity.isSpawning() || blockEntity.isCoolingDown() ? INFINITE_EXTENT_AABB : super.getRenderBoundingBox(blockEntity); ++ } + } diff --git a/patches/net/minecraft/client/renderer/blockentity/TrialSpawnerRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/TrialSpawnerRenderer.java.patch new file mode 100644 index 00000000000..2808364eff6 --- /dev/null +++ b/patches/net/minecraft/client/renderer/blockentity/TrialSpawnerRenderer.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/client/renderer/blockentity/TrialSpawnerRenderer.java ++++ b/net/minecraft/client/renderer/blockentity/TrialSpawnerRenderer.java +@@ -32,4 +_,10 @@ + } + } + } ++ ++ @Override ++ public net.minecraft.world.phys.AABB getRenderBoundingBox(TrialSpawnerBlockEntity blockEntity) { ++ net.minecraft.core.BlockPos pos = blockEntity.getBlockPos(); ++ return new net.minecraft.world.phys.AABB(pos.getX() - 1.0, pos.getY() - 1.0, pos.getZ() - 1.0, pos.getX() + 2.0, pos.getY() + 2.0, pos.getZ() + 2.0); ++ } + } diff --git a/patches/net/minecraft/core/NonNullList.java.patch b/patches/net/minecraft/core/NonNullList.java.patch new file mode 100644 index 00000000000..66a90fdc40e --- /dev/null +++ b/patches/net/minecraft/core/NonNullList.java.patch @@ -0,0 +1,31 @@ +--- a/net/minecraft/core/NonNullList.java ++++ b/net/minecraft/core/NonNullList.java +@@ -9,6 +_,28 @@ + import org.apache.commons.lang3.Validate; + + public class NonNullList extends AbstractList { ++ ++ /** ++ * Neo: utility method to construct a Codec for a NonNullList ++ * @param entryCodec the codec to use for the elements ++ * @param the element type ++ * @return a codec that encodes as a list, and decodes into NonNullList ++ */ ++ public static com.mojang.serialization.Codec> codecOf(com.mojang.serialization.Codec entryCodec) { ++ return entryCodec.listOf().xmap(NonNullList::copyOf, java.util.function.Function.identity()); ++ } ++ ++ /** ++ * Neo: utility method to construct an immutable NonNullList from a given collection ++ * @param entries the collection to make a copy of ++ * @param the type of the elements in the list ++ * @return a new immutable NonNullList ++ * @throws NullPointerException if entries is null, or if it contains any nulls ++ */ ++ public static NonNullList copyOf(java.util.Collection entries) { ++ return new NonNullList<>(List.copyOf(entries), null); ++ } ++ + private final List list; + @Nullable + private final E defaultValue; diff --git a/patches/net/minecraft/server/MinecraftServer.java.patch b/patches/net/minecraft/server/MinecraftServer.java.patch index b405f24ddb3..35b946b9187 100644 --- a/patches/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/net/minecraft/server/MinecraftServer.java.patch @@ -188,15 +188,15 @@ public static WorldDataConfiguration configurePackRepository( PackRepository p_248681_, DataPackConfig p_248920_, boolean p_249869_, FeatureFlagSet p_251243_ ) { -+ net.neoforged.neoforge.resource.ResourcePackLoader.loadResourcePacks(p_248681_, net.neoforged.neoforge.server.ServerLifecycleHooks::buildPackFinder); ++ net.neoforged.neoforge.resource.ResourcePackLoader.loadResourcePacks(p_248681_, map -> net.neoforged.neoforge.resource.ResourcePackLoader.buildPackFinder(map, PackType.SERVER_DATA)); p_248681_.reload(); -+ DataPackConfig.DEFAULT.addModPacks(net.neoforged.neoforge.common.CommonHooks.getModPacks()); -+ p_248920_.addModPacks(net.neoforged.neoforge.common.CommonHooks.getModPacks()); ++ DataPackConfig.DEFAULT.addModPacks(net.neoforged.neoforge.common.CommonHooks.getModDataPacks()); ++ p_248920_.addModPacks(net.neoforged.neoforge.common.CommonHooks.getModDataPacks()); if (p_249869_) { - p_248681_.setSelected(Collections.singleton("vanilla")); - return WorldDataConfiguration.DEFAULT; -+ p_248681_.setSelected(net.neoforged.neoforge.common.CommonHooks.getModPacksWithVanilla()); -+ return new WorldDataConfiguration(new DataPackConfig(net.neoforged.neoforge.common.CommonHooks.getModPacksWithVanilla(), ImmutableList.of()), FeatureFlags.DEFAULT_FLAGS); ++ p_248681_.setSelected(net.neoforged.neoforge.common.CommonHooks.getModDataPacksWithVanilla()); ++ return new WorldDataConfiguration(new DataPackConfig(net.neoforged.neoforge.common.CommonHooks.getModDataPacksWithVanilla(), ImmutableList.of()), FeatureFlags.DEFAULT_FLAGS); } else { Set set = Sets.newLinkedHashSet(); diff --git a/patches/net/minecraft/server/commands/DataPackCommand.java.patch b/patches/net/minecraft/server/commands/DataPackCommand.java.patch new file mode 100644 index 00000000000..ec062a79daa --- /dev/null +++ b/patches/net/minecraft/server/commands/DataPackCommand.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/server/commands/DataPackCommand.java ++++ b/net/minecraft/server/commands/DataPackCommand.java +@@ -183,7 +_,7 @@ + private static int listEnabledPacks(CommandSourceStack p_136866_) { + PackRepository packrepository = p_136866_.getServer().getPackRepository(); + packrepository.reload(); +- Collection collection = packrepository.getSelectedPacks(); ++ Collection collection = packrepository.getSelectedPacks().stream().filter(p -> !p.isHidden()).toList(); + if (collection.isEmpty()) { + p_136866_.sendSuccess(() -> Component.translatable("commands.datapack.list.enabled.none"), false); + } else { diff --git a/patches/net/minecraft/server/packs/metadata/pack/PackMetadataSection.java.patch b/patches/net/minecraft/server/packs/metadata/pack/PackMetadataSection.java.patch index 3fed65a708f..76317be154b 100644 --- a/patches/net/minecraft/server/packs/metadata/pack/PackMetadataSection.java.patch +++ b/patches/net/minecraft/server/packs/metadata/pack/PackMetadataSection.java.patch @@ -1,64 +1,11 @@ --- a/net/minecraft/server/packs/metadata/pack/PackMetadataSection.java +++ b/net/minecraft/server/packs/metadata/pack/PackMetadataSection.java -@@ -9,14 +_,59 @@ - import net.minecraft.server.packs.metadata.MetadataSectionType; - import net.minecraft.util.InclusiveRange; - --public record PackMetadataSection(Component description, int packFormat, Optional> supportedFormats) { -+public record PackMetadataSection(Component description, @Deprecated int packFormat, Optional> supportedFormats, Optional neoForgeData) { - public static final Codec CODEC = RecordCodecBuilder.create( - p_304321_ -> p_304321_.group( - ComponentSerialization.CODEC.fieldOf("description").forGetter(PackMetadataSection::description), - Codec.INT.fieldOf("pack_format").forGetter(PackMetadataSection::packFormat), -- InclusiveRange.codec(Codec.INT).optionalFieldOf("supported_formats").forGetter(PackMetadataSection::supportedFormats) -+ InclusiveRange.codec(Codec.INT).optionalFieldOf("supported_formats").forGetter(PackMetadataSection::supportedFormats), -+ NeoForgeData.CODEC.optionalFieldOf("neoforge").forGetter(PackMetadataSection::neoForgeData) - ) +@@ -19,4 +_,8 @@ .apply(p_304321_, PackMetadataSection::new) ); public static final MetadataSectionType TYPE = MetadataSectionType.fromCodec("pack", CODEC); + -+ @Deprecated -+ public PackMetadataSection(Component description, int packFormat, Optional> supportedFormats) { -+ this(description, packFormat, supportedFormats, Optional.empty()); -+ } -+ -+ public PackMetadataSection(Component description, int packFormat, InclusiveRange supportedFormats) { -+ this(description, packFormat, Optional.of(supportedFormats), Optional.empty()); -+ } -+ -+ public PackMetadataSection(Component description, int packFormat, NeoForgeData neoForgeData) { -+ this(description, packFormat, Optional.empty(), Optional.of(neoForgeData)); -+ } -+ -+ public PackMetadataSection(Component p_10371_, int p_10372_, java.util.Map packTypeVersions) { -+ this(p_10371_, p_10372_, new NeoForgeData(packTypeVersions)); -+ } -+ + public PackMetadataSection(Component description, int packVersion) { -+ this(description, packVersion, Optional.empty(), Optional.empty()); -+ } -+ -+ public int packFormat(net.minecraft.server.packs.PackType packType) { -+ return neoForgeData().flatMap(d -> d.packFormat(packType)).orElse(packFormat()); -+ } -+ -+ public record NeoForgeData(Optional> formatPerPackType) { -+ -+ private static final Codec CODEC = RecordCodecBuilder.create(builder -> builder.group( -+ Codec.simpleMap( -+ net.minecraft.util.StringRepresentable.fromEnum(net.minecraft.server.packs.PackType::values), -+ Codec.INT, -+ com.mojang.serialization.Keyable.forStrings(() -> java.util.Arrays.stream(net.minecraft.server.packs.PackType.values()).map(net.minecraft.util.StringRepresentable::getSerializedName)) -+ ).codec().optionalFieldOf("versions").forGetter(NeoForgeData::formatPerPackType) -+ ).apply(builder, NeoForgeData::new)); -+ -+ public NeoForgeData(java.util.Map supportedFormats) { -+ this(Optional.of(supportedFormats)); -+ } -+ -+ public Optional packFormat(net.minecraft.server.packs.PackType packType) { -+ return formatPerPackType.map(p -> p.get(packType)); -+ } ++ this(description, packVersion, Optional.empty()); + } } diff --git a/patches/net/minecraft/server/packs/repository/Pack.java.patch b/patches/net/minecraft/server/packs/repository/Pack.java.patch index f7d6359f34a..ee720496691 100644 --- a/patches/net/minecraft/server/packs/repository/Pack.java.patch +++ b/patches/net/minecraft/server/packs/repository/Pack.java.patch @@ -1,48 +1,97 @@ --- a/net/minecraft/server/packs/repository/Pack.java +++ b/net/minecraft/server/packs/repository/Pack.java -@@ -29,6 +_,7 @@ +@@ -29,6 +_,8 @@ private final Pack.Position defaultPosition; private final boolean required; private final boolean fixedPosition; -+ private final boolean hidden; // Forge: Allow packs to be hidden from the UI entirely ++ private final boolean hidden; // Neo: Allow packs to be hidden from the UI entirely ++ private final List children; // Neo: Allows packs to specify packs which will always be placed beneath them; must be hidden private final PackSource packSource; @Nullable -@@ -77,6 +_,7 @@ - this.defaultPosition = p_251298_; - this.fixedPosition = p_249753_; - this.packSource = p_251608_; +@@ -69,6 +_,33 @@ + boolean p_249753_, + PackSource p_251608_ + ) { ++ this(p_252218_, p_248829_, p_249377_, p_251718_, p_250162_, p_251298_, p_249753_, p_251608_, List.of()); ++ } ++ ++ private Pack( ++ String p_252218_, ++ boolean p_248829_, ++ Pack.ResourcesSupplier p_249377_, ++ Component p_251718_, ++ Pack.Info p_250162_, ++ Pack.Position p_251298_, ++ boolean p_249753_, ++ PackSource p_251608_, ++ List children ++ ) { ++ List flattenedChildren = new java.util.ArrayList<>(); ++ List remainingChildren = children; ++ // recursively flatten children ++ while (!remainingChildren.isEmpty()) { ++ List oldChildren = remainingChildren; ++ remainingChildren = new java.util.ArrayList<>(); ++ for (Pack child : oldChildren) { ++ flattenedChildren.add(child.withChildren(List.of()).hidden()); ++ remainingChildren.addAll(child.getChildren()); ++ } ++ } ++ this.children = List.copyOf(flattenedChildren); + this.hidden = p_250162_.isHidden(); - } - - @Nullable + this.id = p_252218_; + this.resources = p_249377_; + this.title = p_251718_; @@ -96,7 +_,7 @@ PackCompatibility packcompatibility = PackCompatibility.forVersion(inclusiverange, p_294759_); OverlayMetadataSection overlaymetadatasection = packresources.getMetadataSection(OverlayMetadataSection.TYPE); List list = overlaymetadatasection != null ? overlaymetadatasection.overlaysForVersion(p_294759_) : List.of(); - pack$info = new Pack.Info(packmetadatasection.description(), packcompatibility, featureflagset, list); -+ pack$info = new Pack.Info(packmetadatasection.description(), packmetadatasection.packFormat(PackType.SERVER_DATA), packmetadatasection.packFormat(PackType.CLIENT_RESOURCES), packcompatibility, featureflagset, list, packresources.isHidden()); ++ pack$info = new Pack.Info(packmetadatasection.description(), packcompatibility, featureflagset, list, packresources.isHidden()); } return pack$info; -@@ -172,6 +_,8 @@ +@@ -172,6 +_,33 @@ return this.packSource; } + public boolean isHidden() { return hidden; } ++ ++ public List getChildren() { return children; } ++ ++ /** ++ * {@return a copy of the pack with the provided children in place of any children this pack currently has} ++ */ ++ public Pack withChildren(List children) { ++ return new Pack(this.id, this.required, this.resources, this.title, this.info, this.defaultPosition, this.fixedPosition, this.packSource, children); ++ } ++ ++ /** ++ * {@return a copy of the pack that is hidden} ++ */ ++ public Pack hidden() { ++ return new Pack( ++ this.id, this.required, this.resources, this.title, ++ new Info( ++ this.info.description(), ++ info.compatibility(), ++ info.requestedFeatures(), ++ info.overlays(), ++ true ++ ), ++ this.defaultPosition, this.fixedPosition, this.packSource, this.children); ++ } + @Override public boolean equals(Object p_10448_) { if (this == p_10448_) { -@@ -189,7 +_,10 @@ +@@ -189,7 +_,7 @@ return this.id.hashCode(); } - public static record Info(Component description, PackCompatibility compatibility, FeatureFlagSet requestedFeatures, List overlays) { -+ public static record Info(Component description, int dataFormat, int resourceFormat, PackCompatibility compatibility, FeatureFlagSet requestedFeatures, List overlays, boolean isHidden) { -+ public int getFormat(PackType type) { -+ return type == PackType.SERVER_DATA ? this.dataFormat : this.resourceFormat; -+ } ++ public static record Info(Component description, PackCompatibility compatibility, FeatureFlagSet requestedFeatures, List overlays, boolean isHidden) { } public static enum Position { diff --git a/patches/net/minecraft/server/packs/repository/PackRepository.java.patch b/patches/net/minecraft/server/packs/repository/PackRepository.java.patch index 593d452b444..c16e20b3918 100644 --- a/patches/net/minecraft/server/packs/repository/PackRepository.java.patch +++ b/patches/net/minecraft/server/packs/repository/PackRepository.java.patch @@ -9,6 +9,30 @@ } public void reload() { +@@ -71,11 +_,12 @@ + } + + private List rebuildSelected(Collection p_10518_) { +- List list = this.getAvailablePacks(p_10518_).collect(Collectors.toList()); ++ List list = this.getAvailablePacks(p_10518_).flatMap(p -> Stream.concat(Stream.of(p), p.getChildren().stream())).collect(Collectors.toList()); + + for(Pack pack : this.available.values()) { + if (pack.isRequired() && !list.contains(pack)) { +- pack.getDefaultPosition().insert(list, pack, Functions.identity(), false); ++ int i = pack.getDefaultPosition().insert(list, pack, Functions.identity(), false); ++ list.addAll(i + 1, pack.getChildren()); + } + } + +@@ -95,7 +_,7 @@ + } + + public Collection getSelectedIds() { +- return this.selected.stream().map(Pack::getId).collect(ImmutableSet.toImmutableSet()); ++ return this.selected.stream().filter(p -> !p.isHidden()).map(Pack::getId).collect(ImmutableSet.toImmutableSet()); + } + + public FeatureFlagSet getRequestedFeatureFlags() { @@ -109,6 +_,10 @@ @Nullable public Pack getPack(String p_10508_) { diff --git a/patches/net/minecraft/server/packs/resources/FallbackResourceManager.java.patch b/patches/net/minecraft/server/packs/resources/FallbackResourceManager.java.patch deleted file mode 100644 index 02503d9915c..00000000000 --- a/patches/net/minecraft/server/packs/resources/FallbackResourceManager.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/net/minecraft/server/packs/resources/FallbackResourceManager.java -+++ b/net/minecraft/server/packs/resources/FallbackResourceManager.java -@@ -103,8 +_,11 @@ - - for(int i = this.fallbacks.size() - 1; i >= 0; --i) { - FallbackResourceManager.PackEntry fallbackresourcemanager$packentry = this.fallbacks.get(i); -- PackResources packresources = fallbackresourcemanager$packentry.resources; -- if (packresources != null) { -+ PackResources pack = fallbackresourcemanager$packentry.resources; -+ if (pack != null) { -+ var children = pack.getChildren(); -+ var packs = children == null ? List.of(pack) : children; -+ for (final PackResources packresources : packs) { - IoSupplier iosupplier = packresources.getResource(this.type, p_215367_); - if (iosupplier != null) { - IoSupplier iosupplier1; -@@ -118,6 +_,7 @@ - } - - list.add(new Resource(packresources, iosupplier, iosupplier1)); -+ } - } - } - diff --git a/patches/net/minecraft/stats/StatsCounter.java.patch b/patches/net/minecraft/stats/StatsCounter.java.patch new file mode 100644 index 00000000000..e67449d5b8e --- /dev/null +++ b/patches/net/minecraft/stats/StatsCounter.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/stats/StatsCounter.java ++++ b/net/minecraft/stats/StatsCounter.java +@@ -18,7 +_,8 @@ + } + + public void setValue(Player p_13020_, Stat p_13021_, int p_13022_) { +- this.stats.put(p_13021_, p_13022_); ++ net.neoforged.neoforge.event.StatAwardEvent event = net.neoforged.neoforge.event.EventHooks.onStatAward(p_13020_, p_13021_,p_13022_); ++ if (!event.isCanceled()) this.stats.put(event.getStat(), event.getValue()); + } + + public int getValue(StatType p_13018_, T p_13019_) { diff --git a/patches/net/minecraft/world/effect/MobEffectInstance.java.patch b/patches/net/minecraft/world/effect/MobEffectInstance.java.patch index d1af12b5397..82196a218b4 100644 --- a/patches/net/minecraft/world/effect/MobEffectInstance.java.patch +++ b/patches/net/minecraft/world/effect/MobEffectInstance.java.patch @@ -1,19 +1,19 @@ --- a/net/minecraft/world/effect/MobEffectInstance.java +++ b/net/minecraft/world/effect/MobEffectInstance.java -@@ -19,7 +_,7 @@ - import net.minecraft.world.entity.LivingEntity; - import org.slf4j.Logger; +@@ -78,6 +_,7 @@ + this.showIcon = p_216892_; + this.hiddenEffect = p_216893_; + this.factorData = p_216894_; ++ this.effect.fillEffectCures(this.cures, this); + } --public class MobEffectInstance implements Comparable { -+public class MobEffectInstance implements Comparable, net.neoforged.neoforge.common.extensions.IMobEffectInstanceExtension { - private static final Logger LOGGER = LogUtils.getLogger(); - public static final int INFINITE_DURATION = -1; - private static final String TAG_ID = "id"; -@@ -96,6 +_,7 @@ + public MobEffectInstance(MobEffectInstance p_19543_) { +@@ -96,6 +_,8 @@ this.ambient = p_19549_.ambient; this.visible = p_19549_.visible; this.showIcon = p_19549_.showIcon; -+ this.curativeItems = p_19549_.curativeItems == null ? null : new java.util.ArrayList(p_19549_.curativeItems); ++ this.cures.clear(); ++ this.cures.addAll(p_19549_.cures); } public boolean update(MobEffectInstance p_19559_) { @@ -25,64 +25,66 @@ this.writeDetailsTo(p_19556_); return p_19556_; } -@@ -287,6 +_,7 @@ - this.hiddenEffect.save(compoundtag); - p_19568_.put("hidden_effect", compoundtag); - } -+ writeCurativeItems(p_19568_); +@@ -295,6 +_,8 @@ + .resultOrPartial(LOGGER::error) + .ifPresent(p_216906_ -> p_19568_.put("factor_calculation_data", p_216906_)) + ); ++ ++ writeCures(p_19568_); + } - this.factorData - .ifPresent( -@@ -332,9 +_,9 @@ + @Nullable +@@ -332,7 +_,7 @@ optional = Optional.empty(); } - return new MobEffectInstance(p_19546_, j, Math.max(i, 0), flag, flag1, flag2, mobeffectinstance, optional); -+ return readCurativeItems(new MobEffectInstance(p_19546_, j, Math.max(0, i), flag, flag1, flag2, mobeffectinstance, optional), p_19547_); ++ return new MobEffectInstance(p_19546_, j, Math.max(i, 0), flag, flag1, flag2, mobeffectinstance, optional).readCures(p_19547_); } -- -+ + public int compareTo(MobEffectInstance p_19566_) { - int i = 32147; - return (this.getDuration() <= 32147 || p_19566_.getDuration() <= 32147) && (!this.isAmbient() || !p_19566_.isAmbient()) -@@ -342,12 +_,38 @@ +@@ -342,12 +_,42 @@ .compareFalseFirst(this.isAmbient(), p_19566_.isAmbient()) .compareFalseFirst(this.isInfiniteDuration(), p_19566_.isInfiniteDuration()) .compare(this.getDuration(), p_19566_.getDuration()) - .compare(this.getEffect().getColor(), p_19566_.getEffect().getColor()) -+ .compare(this.getEffect().getSortOrder(this), p_19566_.getEffect().getSortOrder(this)) ++ .compare(this.getEffect().getSortOrder(this), p_19566_.getEffect().getSortOrder(p_19566_)) .result() : ComparisonChain.start() .compare(this.isAmbient(), p_19566_.isAmbient()) - .compare(this.getEffect().getColor(), p_19566_.getEffect().getColor()) -+ .compare(this.getEffect().getSortOrder(this), p_19566_.getEffect().getSortOrder(this)) ++ .compare(this.getEffect().getSortOrder(this), p_19566_.getEffect().getSortOrder(p_19566_)) .result(); + } + -+ //======================= FORGE START =========================== -+ private java.util.List curativeItems; ++ private final java.util.Set cures = com.google.common.collect.Sets.newIdentityHashSet(); + -+ @Override -+ public java.util.List getCurativeItems() { -+ if (this.curativeItems == null) //Lazy load this so that we don't create a circular dep on Items. -+ this.curativeItems = getEffect().getCurativeItems(); -+ return this.curativeItems; ++ /** ++ * {@return the {@link net.neoforged.neoforge.common.EffectCure}s which can cure the {@link MobEffect} held by this {@link MobEffectInstance}} ++ */ ++ public java.util.Set getCures() { ++ return cures; + } -+ @Override -+ public void setCurativeItems(java.util.List curativeItems) { -+ this.curativeItems = curativeItems; -+ } -+ private static MobEffectInstance readCurativeItems(MobEffectInstance effect, CompoundTag nbt) { -+ if (nbt.contains("CurativeItems", net.minecraft.nbt.Tag.TAG_LIST)) { -+ java.util.List items = new java.util.ArrayList(); -+ net.minecraft.nbt.ListTag list = nbt.getList("CurativeItems", net.minecraft.nbt.Tag.TAG_COMPOUND); ++ ++ private MobEffectInstance readCures(CompoundTag tag) { ++ cures.clear(); // Overwrite cures with ones stored in NBT, if any ++ if (tag.contains("neoforge:cures", Tag.TAG_LIST)) { ++ net.minecraft.nbt.ListTag list = tag.getList("neoforge:cures", Tag.TAG_STRING); + for (int i = 0; i < list.size(); i++) { -+ items.add(net.minecraft.world.item.ItemStack.of(list.getCompound(i))); ++ cures.add(net.neoforged.neoforge.common.EffectCure.get(list.getString(i))); + } -+ effect.setCurativeItems(items); + } ++ return this; ++ } + -+ return effect; ++ private void writeCures(CompoundTag tag) { ++ if (!cures.isEmpty()) { ++ net.minecraft.nbt.ListTag list = new net.minecraft.nbt.ListTag(); ++ for (net.neoforged.neoforge.common.EffectCure cure : cures) { ++ list.add(net.minecraft.nbt.StringTag.valueOf(cure.name())); ++ } ++ tag.put("neoforge:cures", list); ++ } } public static class FactorData { diff --git a/patches/net/minecraft/world/entity/Entity.java.patch b/patches/net/minecraft/world/entity/Entity.java.patch index 12ad7f6cec0..92039f8edd6 100644 --- a/patches/net/minecraft/world/entity/Entity.java.patch +++ b/patches/net/minecraft/world/entity/Entity.java.patch @@ -510,7 +510,7 @@ + /** + * Gets the value of the legacy {@link #maxUpStep} field. Only used by players when the modified value causes issues. -+ * @deprecated Use {@link IEntityExtension#getStepHeight()} to get the real step height value. ++ * @deprecated Use {@link net.neoforged.neoforge.common.extensions.IEntityExtension#getStepHeight()} to get the real step height value. + */ + @Deprecated public float maxUpStep() { @@ -532,7 +532,7 @@ public void setMaxUpStep(float p_275672_) { this.maxUpStep = p_275672_; } -@@ -3419,6 +_,118 @@ +@@ -3419,6 +_,119 @@ public boolean mayInteract(Level p_146843_, BlockPos p_146844_) { return true; } @@ -631,7 +631,8 @@ + } + + @Override -+ public final @Nullable T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { ++ @Nullable ++ public final T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { + // Entities are always saved, no setChanged() call is necessary. + return super.setData(type, data); + } diff --git a/patches/net/minecraft/world/entity/LivingEntity.java.patch b/patches/net/minecraft/world/entity/LivingEntity.java.patch index f415c0a1cb9..041747659c8 100644 --- a/patches/net/minecraft/world/entity/LivingEntity.java.patch +++ b/patches/net/minecraft/world/entity/LivingEntity.java.patch @@ -104,7 +104,7 @@ for(flag = false; iterator.hasNext(); flag = true) { - this.onEffectRemoved(iterator.next()); + MobEffectInstance effect = iterator.next(); -+ if(net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.entity.living.MobEffectEvent.Remove(this, effect)).isCanceled()) continue; ++ if(net.neoforged.neoforge.event.EventHooks.onEffectRemoved(this, effect, null)) continue; + this.onEffectRemoved(effect); iterator.remove(); } @@ -131,7 +131,7 @@ } public boolean removeEffect(MobEffect p_21196_) { -+ if (net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.entity.living.MobEffectEvent.Remove(this, p_21196_)).isCanceled()) return false; ++ if (net.neoforged.neoforge.event.EventHooks.onEffectRemoved(this, p_21196_, null)) return false; MobEffectInstance mobeffectinstance = this.removeEffectNoUpdate(p_21196_); if (mobeffectinstance != null) { this.onEffectRemoved(mobeffectinstance); @@ -212,7 +212,7 @@ itemstack = itemstack1.copy(); itemstack1.shrink(1); break; -@@ -1254,7 +_,7 @@ +@@ -1254,13 +_,13 @@ if (itemstack != null) { if (this instanceof ServerPlayer serverplayer) { @@ -221,6 +221,13 @@ CriteriaTriggers.USED_TOTEM.trigger(serverplayer, itemstack); this.gameEvent(GameEvent.ITEM_INTERACT_FINISH); } + + this.setHealth(1.0F); +- this.removeAllEffects(); ++ this.removeEffectsCuredBy(net.neoforged.neoforge.common.EffectCures.PROTECTED_BY_TOTEM); + this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 900, 1)); + this.addEffect(new MobEffectInstance(MobEffects.ABSORPTION, 100, 1)); + this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 800, 0)); @@ -1328,6 +_,7 @@ } @@ -669,17 +676,17 @@ + /* ==== FORGE START ==== */ + /*** -+ * Removes all potion effects that have curativeItem as a curative item for its effect -+ * @param curativeItem The itemstack we are using to cure potion effects ++ * Removes all potion effects that have the given {@link net.neoforged.neoforge.common.EffectCure} in their set of cures ++ * @param cure the EffectCure being used + */ -+ public boolean curePotionEffects(ItemStack curativeItem) { ++ public boolean removeEffectsCuredBy(net.neoforged.neoforge.common.EffectCure cure) { + if (this.level().isClientSide) + return false; + boolean ret = false; + Iterator itr = this.activeEffects.values().iterator(); + while (itr.hasNext()) { + MobEffectInstance effect = itr.next(); -+ if (effect.isCurativeItem(curativeItem) && !net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.entity.living.MobEffectEvent.Remove(this, effect)).isCanceled()) { ++ if (effect.getCures().contains(cure) && !net.neoforged.neoforge.event.EventHooks.onEffectRemoved(this, effect, cure)) { + this.onEffectRemoved(effect); + itr.remove(); + ret = true; diff --git a/patches/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/patches/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch index dfefe1aa752..6f600f2dc13 100644 --- a/patches/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch +++ b/patches/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch @@ -17,7 +17,7 @@ + ItemStack itemstack = this.getProjectile(this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, item -> item instanceof net.minecraft.world.item.BowItem))); AbstractArrow abstractarrow = this.getArrow(itemstack, p_32142_); + if (this.getMainHandItem().getItem() instanceof net.minecraft.world.item.BowItem) -+ abstractarrow = ((net.minecraft.world.item.BowItem)this.getMainHandItem().getItem()).customArrow(abstractarrow); ++ abstractarrow = ((net.minecraft.world.item.BowItem)this.getMainHandItem().getItem()).customArrow(abstractarrow, itemstack); double d0 = p_32141_.getX() - this.getX(); double d1 = p_32141_.getY(0.3333333333333333) - abstractarrow.getY(); double d2 = p_32141_.getZ() - this.getZ(); diff --git a/patches/net/minecraft/world/entity/monster/Illusioner.java.patch b/patches/net/minecraft/world/entity/monster/Illusioner.java.patch index cfe30f6a47a..e12536ba6f6 100644 --- a/patches/net/minecraft/world/entity/monster/Illusioner.java.patch +++ b/patches/net/minecraft/world/entity/monster/Illusioner.java.patch @@ -8,7 +8,7 @@ + ItemStack itemstack = this.getProjectile(this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, item -> item instanceof net.minecraft.world.item.BowItem))); AbstractArrow abstractarrow = ProjectileUtil.getMobArrow(this, itemstack, p_32919_); + if (this.getMainHandItem().getItem() instanceof net.minecraft.world.item.BowItem) -+ abstractarrow = ((net.minecraft.world.item.BowItem)this.getMainHandItem().getItem()).customArrow(abstractarrow); ++ abstractarrow = ((net.minecraft.world.item.BowItem)this.getMainHandItem().getItem()).customArrow(abstractarrow, itemstack); double d0 = p_32918_.getX() - this.getX(); double d1 = p_32918_.getY(0.3333333333333333) - abstractarrow.getY(); double d2 = p_32918_.getZ() - this.getZ(); diff --git a/patches/net/minecraft/world/entity/player/Player.java.patch b/patches/net/minecraft/world/entity/player/Player.java.patch index 9129918f92d..c3dcf3cad1f 100644 --- a/patches/net/minecraft/world/entity/player/Player.java.patch +++ b/patches/net/minecraft/world/entity/player/Player.java.patch @@ -243,11 +243,10 @@ } float f4 = 0.0F; -@@ -1193,11 +_,13 @@ +@@ -1193,11 +_,12 @@ for(LivingEntity livingentity : this.level() .getEntitiesOfClass(LivingEntity.class, p_36347_.getBoundingBox().inflate(1.0, 0.25, 1.0))) { -+ //PATCH 1.20.2: Entity reach distance was redone, this needs to be verified. + double entityReachSq = Mth.square(this.getEntityReach()); // Use entity reach instead of constant 9.0. Vanilla uses bottom center-to-center checks here, so don't update this to use canReach, since it uses closest-corner checks. if (livingentity != this && livingentity != p_36347_ diff --git a/patches/net/minecraft/world/item/BowItem.java.patch b/patches/net/minecraft/world/item/BowItem.java.patch index 6cef901d492..2350734da1f 100644 --- a/patches/net/minecraft/world/item/BowItem.java.patch +++ b/patches/net/minecraft/world/item/BowItem.java.patch @@ -22,7 +22,7 @@ if (!p_40668_.isClientSide) { ArrowItem arrowitem = (ArrowItem)(itemstack.getItem() instanceof ArrowItem ? itemstack.getItem() : Items.ARROW); AbstractArrow abstractarrow = arrowitem.createArrow(p_40668_, itemstack, player); -+ abstractarrow = customArrow(abstractarrow); ++ abstractarrow = customArrow(abstractarrow, itemstack); abstractarrow.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, f * 3.0F, 1.0F); if (f == 1.0F) { abstractarrow.setCritArrow(true); @@ -43,7 +43,7 @@ return ARROW_ONLY; + } + -+ public AbstractArrow customArrow(AbstractArrow arrow) { ++ public AbstractArrow customArrow(AbstractArrow arrow, ItemStack stack) { + return arrow; } diff --git a/patches/net/minecraft/world/item/BrushItem.java.patch b/patches/net/minecraft/world/item/BrushItem.java.patch new file mode 100644 index 00000000000..3b9e4d848bb --- /dev/null +++ b/patches/net/minecraft/world/item/BrushItem.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/item/BrushItem.java ++++ b/net/minecraft/world/item/BrushItem.java +@@ -148,4 +_,9 @@ + }; + } + } ++ ++ @Override ++ public boolean canPerformAction(ItemStack stack, net.neoforged.neoforge.common.ToolAction toolAction) { ++ return net.neoforged.neoforge.common.ToolActions.DEFAULT_BRUSH_ACTIONS.contains(toolAction); ++ } + } diff --git a/patches/net/minecraft/world/item/HoneyBottleItem.java.patch b/patches/net/minecraft/world/item/HoneyBottleItem.java.patch new file mode 100644 index 00000000000..ae550d25760 --- /dev/null +++ b/patches/net/minecraft/world/item/HoneyBottleItem.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/item/HoneyBottleItem.java ++++ b/net/minecraft/world/item/HoneyBottleItem.java +@@ -28,7 +_,7 @@ + } + + if (!p_41349_.isClientSide) { +- p_41350_.removeEffect(MobEffects.POISON); ++ p_41350_.removeEffectsCuredBy(net.neoforged.neoforge.common.EffectCures.HONEY); + } + + if (p_41348_.isEmpty()) { diff --git a/patches/net/minecraft/world/item/Item.java.patch b/patches/net/minecraft/world/item/Item.java.patch index 121226b989e..b8a2534143e 100644 --- a/patches/net/minecraft/world/item/Item.java.patch +++ b/patches/net/minecraft/world/item/Item.java.patch @@ -111,13 +111,14 @@ } protected static BlockHitResult getPlayerPOVHitResult(Level p_41436_, Player p_41437_, ClipContext.Fluid p_41438_) { -@@ -313,11 +_,13 @@ +@@ -313,11 +_,14 @@ float f5 = Mth.sin(-f * (float) (Math.PI / 180.0)); float f6 = f3 * f4; float f7 = f2 * f4; - double d0 = 5.0; - Vec3 vec31 = vec3.add((double)f6 * 5.0, (double)f5 * 5.0, (double)f7 * 5.0); + double d0 = p_41437_.getBlockReach(); ++ if (!p_41437_.isCreative() && d0 != 0) d0 += 0.5D; // The vanilla constant here was 5.0, but the default survival block reach is 4.5. Creative default is already 5.0. + Vec3 vec31 = vec3.add((double)f6 * d0, (double)f5 * d0, (double)f7 * d0); return p_41436_.clip(new ClipContext(vec3, vec31, ClipContext.Block.OUTLINE, p_41438_, p_41437_)); } diff --git a/patches/net/minecraft/world/item/MilkBucketItem.java.patch b/patches/net/minecraft/world/item/MilkBucketItem.java.patch index 46ead29802e..ad3855d4917 100644 --- a/patches/net/minecraft/world/item/MilkBucketItem.java.patch +++ b/patches/net/minecraft/world/item/MilkBucketItem.java.patch @@ -1,17 +1,11 @@ --- a/net/minecraft/world/item/MilkBucketItem.java +++ b/net/minecraft/world/item/MilkBucketItem.java -@@ -22,13 +_,10 @@ - CriteriaTriggers.CONSUME_ITEM.trigger(serverplayer, p_42923_); - serverplayer.awardStat(Stats.ITEM_USED.get(this)); +@@ -28,7 +_,7 @@ } -+ if (!p_42924_.isClientSide) p_42925_.curePotionEffects(p_42923_); // FORGE - move up so stack.shrink does not turn stack into air - if (p_42925_ instanceof Player && !((Player)p_42925_).getAbilities().instabuild) { - p_42923_.shrink(1); -- } -- -- if (!p_42924_.isClientSide) { + if (!p_42924_.isClientSide) { - p_42925_.removeAllEffects(); ++ p_42925_.removeEffectsCuredBy(net.neoforged.neoforge.common.EffectCures.MILK); } return p_42923_.isEmpty() ? new ItemStack(Items.BUCKET) : p_42923_; diff --git a/patches/net/minecraft/world/item/TridentItem.java.patch b/patches/net/minecraft/world/item/TridentItem.java.patch new file mode 100644 index 00000000000..e11c6abd628 --- /dev/null +++ b/patches/net/minecraft/world/item/TridentItem.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/item/TridentItem.java ++++ b/net/minecraft/world/item/TridentItem.java +@@ -151,4 +_,9 @@ + public int getEnchantmentValue() { + return 1; + } ++ ++ @Override ++ public boolean canPerformAction(ItemStack stack, net.neoforged.neoforge.common.ToolAction toolAction) { ++ return net.neoforged.neoforge.common.ToolActions.DEFAULT_TRIDENT_ACTIONS.contains(toolAction); ++ } + } diff --git a/patches/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch b/patches/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch new file mode 100644 index 00000000000..3ca300683b8 --- /dev/null +++ b/patches/net/minecraft/world/level/block/AbstractCauldronBlock.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/world/level/block/AbstractCauldronBlock.java ++++ b/net/minecraft/world/level/block/AbstractCauldronBlock.java +@@ -102,4 +_,22 @@ + + protected void receiveStalactiteDrip(BlockState p_151975_, Level p_151976_, BlockPos p_151977_, Fluid p_151978_) { + } ++ ++ @Override ++ public void onPlace(BlockState p_51978_, Level p_51979_, BlockPos p_51980_, BlockState p_51981_, boolean p_51982_) { ++ super.onPlace(p_51978_, p_51979_, p_51980_, p_51981_, p_51982_); ++ // Neo: Invalidate cauldron capabilities when a cauldron is added ++ if (net.neoforged.neoforge.fluids.CauldronFluidContent.getForBlock(p_51981_.getBlock()) == null) { ++ p_51979_.invalidateCapabilities(p_51980_); ++ } ++ } ++ ++ @Override ++ public void onRemove(BlockState p_60515_, Level p_60516_, BlockPos p_60517_, BlockState p_60518_, boolean p_60519_) { ++ super.onRemove(p_60515_, p_60516_, p_60517_, p_60518_, p_60519_); ++ // Neo: Invalidate cauldron capabilities when a cauldron is removed ++ if (net.neoforged.neoforge.fluids.CauldronFluidContent.getForBlock(p_60518_.getBlock()) == null) { ++ p_60516_.invalidateCapabilities(p_60517_); ++ } ++ } + } diff --git a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch index 0c7ddd58cc1..799a437e87f 100644 --- a/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch +++ b/patches/net/minecraft/world/level/block/entity/BlockEntity.java.patch @@ -47,7 +47,7 @@ } public boolean triggerEvent(int p_58889_, int p_58890_) { -@@ -185,6 +_,19 @@ +@@ -185,6 +_,20 @@ public BlockEntityType getType() { return this.type; @@ -61,7 +61,8 @@ + } + + @Override -+ public final @Nullable T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { ++ @Nullable ++ public final T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { + setChanged(); + return super.setData(type, data); } diff --git a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch index 5a28ef80ddc..c4d01dbd1b8 100644 --- a/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ b/patches/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -95,7 +95,7 @@ this.blockEntities.values().forEach(p_187988_ -> { Level level = this.level; if (level instanceof ServerLevel serverlevel) { -@@ -646,6 +_,26 @@ +@@ -646,6 +_,27 @@ return new LevelChunk.BoundTickingBlockEntity<>(p_156376_, p_156377_); } @@ -113,7 +113,8 @@ + } + + @Override -+ public @Nullable T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { ++ @Nullable ++ public T setData(net.neoforged.neoforge.attachment.AttachmentType type, T data) { + setUnsaved(true); + return attachmentHolder.setData(type, data); + } diff --git a/patches/net/minecraft/world/level/storage/loot/LootDataType.java.patch b/patches/net/minecraft/world/level/storage/loot/LootDataType.java.patch index 96037422df9..7a49b8ff9e8 100644 --- a/patches/net/minecraft/world/level/storage/loot/LootDataType.java.patch +++ b/patches/net/minecraft/world/level/storage/loot/LootDataType.java.patch @@ -58,3 +58,18 @@ } public static Stream> values() { +@@ -55,9 +_,12 @@ + } + + private static LootDataType.Validator createLootTableValidator() { +- return (p_279333_, p_279227_, p_279406_) -> p_279406_.validate( +- p_279333_.setParams(p_279406_.getParamSet()).enterElement("{" + p_279227_.type().directory + ":" + p_279227_.location() + "}", p_279227_) ++ return (p_279333_, p_279227_, p_279406_) -> { ++ p_279406_ = net.neoforged.neoforge.event.EventHooks.loadLootTable(p_279406_.getLootTableId(), p_279406_); ++ p_279406_.validate( ++ p_279333_.setParams(p_279406_.getParamSet()).enterElement("{" + p_279227_.type().directory + ":" + p_279227_.location() + "}", p_279227_) + ); ++ }; + } + + @FunctionalInterface diff --git a/patches/net/minecraft/world/level/storage/loot/LootTable.java.patch b/patches/net/minecraft/world/level/storage/loot/LootTable.java.patch index f468d3284d5..e4d8e7d2769 100644 --- a/patches/net/minecraft/world/level/storage/loot/LootTable.java.patch +++ b/patches/net/minecraft/world/level/storage/loot/LootTable.java.patch @@ -6,7 +6,7 @@ ExtraCodecs.strictOptionalField(ResourceLocation.CODEC, "random_sequence").forGetter(p_297998_ -> p_297998_.randomSequence), - ExtraCodecs.strictOptionalField(LootPool.CODEC.listOf(), "pools", List.of()).forGetter(p_298002_ -> p_298002_.pools), - ExtraCodecs.strictOptionalField(LootItemFunctions.CODEC.listOf(), "functions", List.of()).forGetter(p_298000_ -> p_298000_.functions) -+ ExtraCodecs.strictOptionalField(net.neoforged.neoforge.common.conditions.ConditionalOps.decodeListWithElementConditionsAndIndexedPeek(LootPool.CODEC, (pool, i) -> { if (pool.getName() == null) pool.setName("custom#" + i); }), "pools", List.of()).forGetter(p_298002_ -> p_298002_.pools), ++ ExtraCodecs.strictOptionalField(net.neoforged.neoforge.common.CommonHooks.lootPoolsCodec(LootPool::setName), "pools", List.of()).forGetter(p_298002_ -> p_298002_.pools), + ExtraCodecs.strictOptionalField(net.neoforged.neoforge.common.conditions.ConditionalOps.decodeListWithElementConditions(LootItemFunctions.CODEC), "functions", List.of()).forGetter(p_298000_ -> p_298000_.functions) ) .apply(p_297999_, LootTable::new) diff --git a/projects/neoforge/build.gradle b/projects/neoforge/build.gradle index 8ffb6f06d8c..571740a1153 100644 --- a/projects/neoforge/build.gradle +++ b/projects/neoforge/build.gradle @@ -3,6 +3,8 @@ plugins { id 'maven-publish' } +rootProject.gradleutils.setupSigning(project: project, signAllPublications: true) + dynamicProject { runtime("${project.minecraft_version}-${project.neoform_version}", rootProject.layout.projectDirectory.dir('patches'), diff --git a/src/generated/resources/pack.mcmeta b/src/generated/resources/pack.mcmeta index 015f8ce5310..f556448a7bd 100644 --- a/src/generated/resources/pack.mcmeta +++ b/src/generated/resources/pack.mcmeta @@ -3,12 +3,10 @@ "description": { "translate": "pack.neoforge.description" }, - "neoforge": { - "versions": { - "client_resources": 22, - "server_data": 26 - } - }, - "pack_format": 26 + "pack_format": 26, + "supported_formats": [ + 0, + 2147483647 + ] } } \ No newline at end of file diff --git a/src/main/java/net/neoforged/neoforge/capabilities/BlockCapability.java b/src/main/java/net/neoforged/neoforge/capabilities/BlockCapability.java index 564283229f2..2d45c45035e 100644 --- a/src/main/java/net/neoforged/neoforge/capabilities/BlockCapability.java +++ b/src/main/java/net/neoforged/neoforge/capabilities/BlockCapability.java @@ -126,7 +126,9 @@ public static BlockCapability createVoid(ResourceLocation name, Cla } // INTERNAL - private static final CapabilityRegistry> registry = new CapabilityRegistry<>(BlockCapability::new); + + // Requires explicitly-typed constructor due to ECJ inference failure. + private static final CapabilityRegistry> registry = new CapabilityRegistry>(BlockCapability::new); private BlockCapability(ResourceLocation name, Class typeClass, Class contextClass) { super(name, typeClass, contextClass); @@ -137,6 +139,9 @@ private BlockCapability(ResourceLocation name, Class typeClass, Class cont @ApiStatus.Internal @Nullable public T getCapability(Level level, BlockPos pos, @Nullable BlockState state, @Nullable BlockEntity blockEntity, C context) { + // Convert pos to immutable, it's easy to forget otherwise + pos = pos.immutable(); + // Get block state and block entity if they were not provided if (blockEntity == null) { if (state == null) diff --git a/src/main/java/net/neoforged/neoforge/capabilities/EntityCapability.java b/src/main/java/net/neoforged/neoforge/capabilities/EntityCapability.java index fa9820d1337..1a4f858dcdf 100644 --- a/src/main/java/net/neoforged/neoforge/capabilities/EntityCapability.java +++ b/src/main/java/net/neoforged/neoforge/capabilities/EntityCapability.java @@ -84,7 +84,9 @@ public static EntityCapability createVoid(ResourceLocation name, Cl } // INTERNAL - private static final CapabilityRegistry> registry = new CapabilityRegistry<>(EntityCapability::new); + + // Requires explicitly-typed constructor due to ECJ inference failure. + private static final CapabilityRegistry> registry = new CapabilityRegistry>(EntityCapability::new); private EntityCapability(ResourceLocation name, Class typeClass, Class contextClass) { super(name, typeClass, contextClass); diff --git a/src/main/java/net/neoforged/neoforge/capabilities/ItemCapability.java b/src/main/java/net/neoforged/neoforge/capabilities/ItemCapability.java index f023da4b13a..1088d8a099f 100644 --- a/src/main/java/net/neoforged/neoforge/capabilities/ItemCapability.java +++ b/src/main/java/net/neoforged/neoforge/capabilities/ItemCapability.java @@ -75,7 +75,9 @@ public static ItemCapability createVoid(ResourceLocation name, Clas } // INTERNAL - private static final CapabilityRegistry> registry = new CapabilityRegistry<>(ItemCapability::new); + + // Requires explicitly-typed constructor due to ECJ inference failure. + private static final CapabilityRegistry> registry = new CapabilityRegistry>(ItemCapability::new); private ItemCapability(ResourceLocation name, Class typeClass, Class contextClass) { super(name, typeClass, contextClass); diff --git a/src/main/java/net/neoforged/neoforge/client/ClientCommandHandler.java b/src/main/java/net/neoforged/neoforge/client/ClientCommandHandler.java index 766773bda88..742a76da5e6 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientCommandHandler.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientCommandHandler.java @@ -23,7 +23,11 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.synchronization.SuggestionProviders; -import net.minecraft.network.chat.*; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentUtils; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent; import net.neoforged.neoforge.common.NeoForge; diff --git a/src/main/java/net/neoforged/neoforge/client/gui/ModListScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/ModListScreen.java index 2f0c62efe78..a5bca764b94 100644 --- a/src/main/java/net/neoforged/neoforge/client/gui/ModListScreen.java +++ b/src/main/java/net/neoforged/neoforge/client/gui/ModListScreen.java @@ -34,6 +34,7 @@ import net.minecraft.network.chat.Style; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.resources.IoSupplier; import net.minecraft.util.FormattedCharSequence; import net.neoforged.fml.ModContainer; @@ -379,11 +380,11 @@ private void updateCache() { @SuppressWarnings("resource") Pair logoData = selectedMod.getLogoFile().map(logoFile -> { TextureManager tm = this.minecraft.getTextureManager(); - final PackResources resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId()) + final Pack.ResourcesSupplier resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId()) .orElse(ResourcePackLoader.getPackFor("neoforge").orElseThrow(() -> new RuntimeException("Can't find neoforge, WHAT!"))); - try { + try (PackResources packResources = resourcePack.openPrimary("mod:" + selectedMod.getModId())) { NativeImage logo = null; - IoSupplier logoResource = resourcePack.getRootResource(logoFile.split("[/\\\\]")); + IoSupplier logoResource = packResources.getRootResource(logoFile.split("[/\\\\]")); if (logoResource != null) logo = NativeImage.read(logoResource.get()); if (logo != null) { diff --git a/src/main/java/net/neoforged/neoforge/client/gui/overlay/VanillaGuiOverlay.java b/src/main/java/net/neoforged/neoforge/client/gui/overlay/VanillaGuiOverlay.java index 5c4b7a4a7ed..567db0921f5 100644 --- a/src/main/java/net/neoforged/neoforge/client/gui/overlay/VanillaGuiOverlay.java +++ b/src/main/java/net/neoforged/neoforge/client/gui/overlay/VanillaGuiOverlay.java @@ -137,6 +137,11 @@ public enum VanillaGuiOverlay { SLEEP_FADE("sleep_fade", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> { gui.renderSleepFade(screenWidth, screenHeight, guiGraphics); }), + DEMO_OVERLAY("demo_overlay", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> { + if (gui.getMinecraft().isDemo() && !gui.getDebugOverlay().showDebugScreen()) { + gui.renderDemoOverlay(guiGraphics); + } + }), POTION_ICONS("potion_icons", (gui, guiGraphics, partialTick, screenWidth, screenHeight) -> { gui.renderEffects(guiGraphics); }), diff --git a/src/main/java/net/neoforged/neoforge/client/loading/ClientModLoader.java b/src/main/java/net/neoforged/neoforge/client/loading/ClientModLoader.java index c6cbd61c739..3c3b0d7c0a8 100644 --- a/src/main/java/net/neoforged/neoforge/client/loading/ClientModLoader.java +++ b/src/main/java/net/neoforged/neoforge/client/loading/ClientModLoader.java @@ -6,19 +6,12 @@ package net.neoforged.neoforge.client.loading; import java.io.File; -import java.util.ArrayList; import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.*; -import java.util.function.Consumer; -import net.minecraft.SharedConstants; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.Component; -import net.minecraft.server.packs.PackResources; import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.metadata.pack.PackMetadataSection; -import net.minecraft.server.packs.repository.*; +import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.server.packs.resources.ReloadableResourceManager; import net.minecraft.server.packs.resources.ResourceManager; @@ -26,7 +19,12 @@ import net.minecraft.world.level.DataPackConfig; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; -import net.neoforged.fml.*; +import net.neoforged.fml.LoadingFailedException; +import net.neoforged.fml.Logging; +import net.neoforged.fml.ModList; +import net.neoforged.fml.ModLoader; +import net.neoforged.fml.ModWorkManager; +import net.neoforged.fml.VersionChecker; import net.neoforged.fml.loading.ImmediateWindowHandler; import net.neoforged.neoforge.client.gui.LoadingErrorScreen; import net.neoforged.neoforge.common.NeoForge; @@ -35,11 +33,8 @@ import net.neoforged.neoforge.event.AddPackFindersEvent; import net.neoforged.neoforge.internal.BrandingControl; import net.neoforged.neoforge.logging.CrashReportExtender; -import net.neoforged.neoforge.resource.DelegatingPackResources; import net.neoforged.neoforge.resource.ResourcePackLoader; import net.neoforged.neoforge.server.LanguageHook; -import net.neoforged.neoforgespi.language.IModInfo; -import net.neoforged.neoforgespi.locating.IModFile; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -61,9 +56,9 @@ public static void begin(final Minecraft minecraft, final PackRepository default LanguageHook.loadForgeAndMCLangs(); createRunnableWithCatch(() -> ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ImmediateWindowHandler::renderTick)).run(); if (error == null) { - ResourcePackLoader.loadResourcePacks(defaultResourcePacks, ClientModLoader::buildPackFinder); + ResourcePackLoader.loadResourcePacks(defaultResourcePacks, map -> ResourcePackLoader.buildPackFinder(map, PackType.CLIENT_RESOURCES)); ModLoader.get().postEvent(new AddPackFindersEvent(PackType.CLIENT_RESOURCES, defaultResourcePacks::addPackFinder)); - DataPackConfig.DEFAULT.addModPacks(ResourcePackLoader.getPackNames()); + DataPackConfig.DEFAULT.addModPacks(ResourcePackLoader.getDataPackNames()); mcResourceManager.registerReloadListener(ClientModLoader::onResourceReload); mcResourceManager.registerReloadListener(BrandingControl.resourceManagerReloadListener()); } @@ -142,39 +137,4 @@ public static boolean completeModLoading() { public static boolean isLoading() { return loading; } - - private static RepositorySource buildPackFinder(Map modResourcePacks) { - return packAcceptor -> clientPackFinder(modResourcePacks, packAcceptor); - } - - private static void clientPackFinder(Map modResourcePacks, Consumer packAcceptor) { - var hiddenPacks = new ArrayList(); - for (Entry e : modResourcePacks.entrySet()) { - IModInfo mod = e.getKey().getModInfos().get(0); - final String name = "mod:" + mod.getModId(); - final Pack modPack = Pack.readMetaAndCreate(name, Component.literal(e.getValue().packId()), false, BuiltInPackSource.fixedResources(e.getValue()), PackType.CLIENT_RESOURCES, Pack.Position.BOTTOM, PackSource.DEFAULT); - if (modPack == null) { - // Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong - ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey())); - continue; - } - LOGGER.debug(Logging.CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath()); - if (mod.getOwningFile().showAsResourcePack()) { - packAcceptor.accept(modPack); - } else { - hiddenPacks.add(e.getValue()); - } - } - - // Create a resource pack merging all mod resources that should be hidden - final Pack modResourcesPack = Pack.readMetaAndCreate("mod_resources", Component.literal("Mod Resources"), true, - BuiltInPackSource.fromName( - id -> new DelegatingPackResources(id, false, - new PackMetadataSection( - Component.translatable("fml.resources.modresources", hiddenPacks.size()), - SharedConstants.getCurrentVersion().getPackVersion(PackType.CLIENT_RESOURCES)), - hiddenPacks)), - PackType.CLIENT_RESOURCES, Pack.Position.BOTTOM, PackSource.DEFAULT); - packAcceptor.accept(modResourcesPack); - } } diff --git a/src/main/java/net/neoforged/neoforge/client/model/generators/loaders/CompositeModelBuilder.java b/src/main/java/net/neoforged/neoforge/client/model/generators/loaders/CompositeModelBuilder.java index 55d0b32128d..67c2f5b28f3 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/generators/loaders/CompositeModelBuilder.java +++ b/src/main/java/net/neoforged/neoforge/client/model/generators/loaders/CompositeModelBuilder.java @@ -8,7 +8,11 @@ import com.google.common.base.Preconditions; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import net.minecraft.resources.ResourceLocation; import net.neoforged.neoforge.client.model.generators.CustomLoaderBuilder; import net.neoforged.neoforge.client.model.generators.ModelBuilder; diff --git a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java index b5438ae39fa..31c45580a7a 100644 --- a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java +++ b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java @@ -9,12 +9,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import com.google.common.collect.Queues; import com.google.common.collect.Sets; -import com.google.gson.Gson; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; +import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.net.URI; @@ -23,7 +20,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Deque; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; @@ -31,6 +27,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -63,14 +60,11 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.packs.resources.Resource; -import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.stats.Stats; import net.minecraft.tags.BlockTags; import net.minecraft.tags.TagEntry; import net.minecraft.tags.TagKey; import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap; -import net.minecraft.util.GsonHelper; import net.minecraft.util.Mth; import net.minecraft.util.datafix.fixes.StructuresBecomeConfiguredFix; import net.minecraft.world.Container; @@ -97,7 +91,16 @@ import net.minecraft.world.inventory.ClickAction; import net.minecraft.world.inventory.ContainerLevelAccess; import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BucketItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.PotionItem; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.item.Tiers; +import net.minecraft.world.item.TippedArrowItem; import net.minecraft.world.item.alchemy.Potion; import net.minecraft.world.item.alchemy.PotionUtils; import net.minecraft.world.item.context.UseOnContext; @@ -123,13 +126,14 @@ import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.WorldData; import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.LootPool; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.neoforged.fml.ModList; import net.neoforged.fml.ModLoader; +import net.neoforged.neoforge.common.conditions.ConditionalOps; import net.neoforged.neoforge.common.extensions.IEntityExtension; import net.neoforged.neoforge.common.loot.IGlobalLootModifier; import net.neoforged.neoforge.common.loot.LootModifierManager; @@ -180,7 +184,6 @@ import net.neoforged.neoforge.registries.NeoForgeRegistries; import net.neoforged.neoforge.resource.ResourcePackLoader; import net.neoforged.neoforge.server.permission.PermissionAPI; -import org.apache.commons.lang3.function.TriFunction; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -728,82 +731,25 @@ public static GameType onChangeGameType(Player player, GameType currentGameType, return newGameType; } - private static final ThreadLocal> lootContext = new ThreadLocal<>(); - - private static LootTableContext getLootTableContext() { - LootTableContext ctx = lootContext.get().peek(); - - if (ctx == null) - throw new JsonParseException("Invalid call stack, could not grab json context!"); // Should I throw this? Do we care about custom deserializers outside the manager? - - return ctx; - } - - public static TriFunction> getLootTableDeserializer(Gson gson, String directory) { - return (location, data, resourceManager) -> { - try { - Resource resource = resourceManager.getResource(location.withPath(directory + "/" + location.getPath() + ".json")).orElse(null); - boolean custom = resource == null || !resource.isBuiltin(); - return Optional.ofNullable(loadLootTable(gson, location, data, custom)); - } catch (Exception exception) { - LOGGER.error("Couldn't parse element {}:{}", directory, location, exception); - return Optional.empty(); - } - }; - } - - public static LootTable loadLootTable(Gson gson, ResourceLocation name, JsonElement data, boolean custom) { - Deque que = lootContext.get(); - if (que == null) { - que = Queues.newArrayDeque(); - lootContext.set(que); - } - - LootTable ret; - try { - que.push(new LootTableContext(name, custom)); - ret = gson.fromJson(data, LootTable.class); - ret.setLootTableId(name); - } catch (JsonParseException e) { - throw e; - } finally { - que.pop(); - } - - if (!custom) - ret = EventHooks.loadLootTable(name, ret); - - if (ret != null) - ret.freeze(); - - return ret; - } - - private static class LootTableContext { - public final ResourceLocation name; - public final boolean vanilla; - public final boolean custom; - public int poolCount = 0; - - private LootTableContext(ResourceLocation name, boolean custom) { - this.name = name; - this.custom = custom; - this.vanilla = "minecraft".equals(this.name.getNamespace()); - } - } - - public static String readPoolName(JsonObject json) { - LootTableContext ctx = getLootTableContext(); - - if (json.has("name")) - return GsonHelper.getAsString(json, "name"); - - if (ctx.custom) - return "custom#" + json.hashCode(); // We don't care about custom ones modders shouldn't be editing them! - - ctx.poolCount++; + @ApiStatus.Internal + public static Codec> lootPoolsCodec(BiConsumer nameSetter) { + var decoder = ConditionalOps.createConditionalCodec(LootPool.CODEC).listOf() + .map(pools -> { + if (pools.size() == 1) { + if (pools.get(0).isPresent() && pools.get(0).get().getName() == null) { + nameSetter.accept(pools.get(0).get(), "main"); + } + } else { + for (int i = 0; i < pools.size(); ++i) { + if (pools.get(i).isPresent() && pools.get(i).get().getName() == null) { + nameSetter.accept(pools.get(i).get(), "pool" + i); + } + } + } - return ctx.poolCount == 1 ? "main" : "pool" + (ctx.poolCount - 1); + return pools.stream().filter(Optional::isPresent).map(Optional::get).toList(); + }); + return Codec.of(LootPool.CODEC.listOf(), decoder); } /** @@ -1033,15 +979,15 @@ public static ObjectArrayList modifyLoot(ResourceLocation lootTableId return generatedLoot; } - public static List getModPacks() { - List modpacks = ResourcePackLoader.getPackNames(); + public static List getModDataPacks() { + List modpacks = ResourcePackLoader.getDataPackNames(); if (modpacks.isEmpty()) throw new IllegalStateException("Attempted to retrieve mod packs before they were loaded in!"); return modpacks; } - public static List getModPacksWithVanilla() { - List modpacks = getModPacks(); + public static List getModDataPacksWithVanilla() { + List modpacks = getModDataPacks(); modpacks.add("vanilla"); return modpacks; } diff --git a/src/main/java/net/neoforged/neoforge/common/EffectCure.java b/src/main/java/net/neoforged/neoforge/common/EffectCure.java new file mode 100644 index 00000000000..db5f27a79f4 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/EffectCure.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common; + +import com.mojang.serialization.Codec; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; + +/** + * Defines a cure that is used to remove {@link MobEffect}s from a {@link LivingEntity}. + *

Cures can be added to or removed from your own effects via {@link MobEffect#fillEffectCures(Set, MobEffectInstance)} + * or any effect by modifying the set of cures on the {@link MobEffectInstance} in {@link MobEffectEvent.Added} + */ +public final class EffectCure { + private static final Map CURES = new ConcurrentHashMap<>(); + + public static Codec CODEC = Codec.STRING.xmap(EffectCure::get, EffectCure::name); + + /** + * {@return all registered cures} + * This collection can be kept around, and will update itself in response to changes to the map. + * See {@link ConcurrentHashMap#values()} for details. + */ + public static Collection getActions() { + return Collections.unmodifiableCollection(CURES.values()); + } + + /** + * Gets or creates a new EffectCure for the given name. + */ + public static EffectCure get(String name) { + return CURES.computeIfAbsent(name, EffectCure::new); + } + + /** + * {@return the name of this cure} + */ + public String name() { + return name; + } + + @Override + public String toString() { + return "EffectCure[" + name + "]"; + } + + private final String name; + + /** + * Use {@link #get(String)} to get or create an EffectCure + */ + private EffectCure(String name) { + this.name = name; + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/EffectCures.java b/src/main/java/net/neoforged/neoforge/common/EffectCures.java new file mode 100644 index 00000000000..4362015969c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/EffectCures.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common; + +import java.util.Set; + +public class EffectCures { + /** + * Cure used when a milk bucket is consumed. Cures any effect by default. + */ + public static final EffectCure MILK = EffectCure.get("milk"); + /** + * Cure used when a honey bottle is consumed. Only cures poison by default. + */ + public static final EffectCure HONEY = EffectCure.get("honey"); + /** + * Cure used when a totem of undying protects the player from death. Cures any effect by default. + */ + public static final EffectCure PROTECTED_BY_TOTEM = EffectCure.get("protected_by_totem"); + + public static final Set DEFAULT_CURES = Set.of(MILK, PROTECTED_BY_TOTEM); +} diff --git a/src/main/java/net/neoforged/neoforge/common/I18nExtension.java b/src/main/java/net/neoforged/neoforge/common/I18nExtension.java index 3e03e45914c..906a8128dee 100644 --- a/src/main/java/net/neoforged/neoforge/common/I18nExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/I18nExtension.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.BiConsumer; +import java.util.function.Supplier; import java.util.regex.Pattern; import net.neoforged.fml.Logging; import net.neoforged.fml.loading.StringUtils; @@ -50,6 +51,8 @@ public class I18nExtension { customFactories.put("featurebound", (name, formatString, locale) -> new CustomReadOnlyFormat(MavenVersionStringHelper::parseFeatureBoundValue)); // {0,i18n,fml.message} -> pass object to i18n string 'fml.message' customFactories.put("i18n", (name, formatString, locale) -> new CustomReadOnlyFormat((stringBuffer, o) -> stringBuffer.append(I18nExtension.parseMessage(formatString, o)))); + // {0,i18ntranslate} -> attempt to use the argument as a translation key + customFactories.put("i18ntranslate", (name, formatString, locale) -> new CustomReadOnlyFormat((stringBuffer, o) -> stringBuffer.append(I18nExtension.parseMessage((String) o)))); // {0,ornull,fml.absent} -> append String value of o, or i18n string 'fml.absent' (message format transforms nulls into the string literal "null") customFactories.put("ornull", ((name, formatString, locale) -> new CustomReadOnlyFormat((stringBuffer, o) -> stringBuffer.append(Objects.equals(String.valueOf(o), "null") ? I18nExtension.parseMessage(formatString) : String.valueOf(o))))); } @@ -72,8 +75,12 @@ private static void parseModInfo(final String formatString, final StringBuffer s } } - public static String getPattern(final String patternName) { - return i18n == null ? patternName : i18n.getOrDefault(patternName, patternName); + public static String getPattern(final String patternName, final Supplier fallback) { + if (i18n == null) { + return fallback.get(); + } + final var translated = i18n.get(patternName); + return translated == null ? fallback.get() : translated; } public static void loadLanguageData(final Map properties) { @@ -82,7 +89,11 @@ public static void loadLanguageData(final Map properties) { } public static String parseMessage(final String i18nMessage, Object... args) { - final String pattern = getPattern(i18nMessage); + return parseMessageWithFallback(i18nMessage, () -> i18nMessage, args); + } + + public static String parseMessageWithFallback(final String i18nMessage, final Supplier fallback, Object... args) { + final String pattern = getPattern(i18nMessage, fallback); try { return parseFormat(pattern, args); } catch (IllegalArgumentException e) { diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForge.java b/src/main/java/net/neoforged/neoforge/common/NeoForge.java index a8ea91f29ed..9242ea437f3 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForge.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForge.java @@ -15,7 +15,7 @@ public class NeoForge { * Also known as the "game" bus. */ public static final IEventBus EVENT_BUS = BusBuilder.builder().startShutdown().classChecker(eventType -> { - if (eventType.isAssignableFrom(IModBusEvent.class)) { + if (IModBusEvent.class.isAssignableFrom(eventType)) { throw new IllegalArgumentException("IModBusEvent events are not allowed on the common NeoForge bus! Use a mod bus instead."); } }).build(); diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java index aea37bb75c4..93f8ac8f148 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -19,7 +19,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; import net.minecraft.DetectedVersion; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BiomeColors; @@ -45,6 +44,7 @@ import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.InclusiveRange; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.Attribute; @@ -124,6 +124,7 @@ import net.neoforged.neoforge.data.event.GatherDataEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.fluids.BaseFlowingFluid; +import net.neoforged.neoforge.fluids.CauldronFluidContent; import net.neoforged.neoforge.fluids.FluidType; import net.neoforged.neoforge.forge.snapshots.ForgeSnapshotsMod; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; @@ -166,8 +167,8 @@ public class NeoForgeMod { private static final DeferredRegister> STRUCTURE_MODIFIER_SERIALIZERS = DeferredRegister.create(NeoForgeRegistries.Keys.STRUCTURE_MODIFIER_SERIALIZERS, "neoforge"); private static final DeferredRegister HOLDER_SET_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.HOLDER_SET_TYPES, "neoforge"); - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static final DeferredHolder, EnumArgument.Info> ENUM_COMMAND_ARGUMENT_TYPE = COMMAND_ARGUMENT_TYPES.register("enum", () -> ArgumentTypeInfos.registerByClass(EnumArgument.class, new EnumArgument.Info())); + @SuppressWarnings({ "unchecked", "rawtypes" }) // Uses Holder instead of DeferredHolder as the type due to weirdness between ECJ and javac. + private static final Holder> ENUM_COMMAND_ARGUMENT_TYPE = COMMAND_ARGUMENT_TYPES.register("enum", () -> ArgumentTypeInfos.registerByClass(EnumArgument.class, new EnumArgument.Info())); private static final DeferredHolder, SingletonArgumentInfo> MODID_COMMAND_ARGUMENT_TYPE = COMMAND_ARGUMENT_TYPES.register("modid", () -> ArgumentTypeInfos.registerByClass(ModIdArgument.class, SingletonArgumentInfo.contextFree(ModIdArgument::modIdArgument))); @@ -184,7 +185,7 @@ public class NeoForgeMod { public static final Holder BLOCK_REACH = ATTRIBUTES.register("block_reach", () -> new RangedAttribute("neoforge.block_reach", 4.5D, 0.0D, 1024.0D).setSyncable(true)); /** - * Attack Range represents the distance at which a player may attack an entity. The default is 3 blocks. Players in creative mode have an additional 3 blocks of entity reach. + * Attack Range represents the distance at which a player may attack an entity. The default is 3 blocks. Players in creative mode have an additional 2 blocks of entity reach. * The default of 3.0 is technically considered a bug by Mojang - see MC-172289 and MC-92484. However, updating this value would allow for longer-range attacks on vanilla servers, which makes some people mad. * * @see IPlayerExtension#getEntityReach() @@ -507,6 +508,7 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist) { DualStackUtils.initialise(); modEventBus.addListener(CapabilityHooks::registerVanillaProviders); + modEventBus.addListener(CauldronFluidContent::registerCapabilities); // These 3 listeners use the default priority for now, can be re-evaluated later. NeoForge.EVENT_BUS.addListener(CapabilityHooks::invalidateCapsOnChunkLoad); NeoForge.EVENT_BUS.addListener(CapabilityHooks::invalidateCapsOnChunkUnload); @@ -533,9 +535,8 @@ public void gatherData(GatherDataEvent event) { gen.addProvider(true, new PackMetadataGenerator(packOutput) .add(PackMetadataSection.TYPE, new PackMetadataSection( Component.translatable("pack.neoforge.description"), - DetectedVersion.BUILT_IN.getPackVersion(PackType.CLIENT_RESOURCES), - Optional.empty(), - Optional.of(new PackMetadataSection.NeoForgeData(Optional.of(Arrays.stream(PackType.values()).collect(Collectors.toMap(Function.identity(), DetectedVersion.BUILT_IN::getPackVersion)))))))); + DetectedVersion.BUILT_IN.getPackVersion(PackType.SERVER_DATA), + Optional.of(new InclusiveRange<>(0, Integer.MAX_VALUE))))); NeoForgeBlockTagsProvider blockTags = new NeoForgeBlockTagsProvider(packOutput, lookupProvider, existingFileHelper); gen.addProvider(event.includeServer(), blockTags); gen.addProvider(event.includeServer(), new NeoForgeItemTagsProvider(packOutput, lookupProvider, blockTags.contentsGetter(), existingFileHelper)); diff --git a/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java b/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java index c6a9848a9d7..fb1ea65bb63 100644 --- a/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java +++ b/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java @@ -5,14 +5,26 @@ package net.neoforged.neoforge.common; -import com.google.common.collect.*; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import com.google.common.graph.ElementOrder; import com.google.common.graph.GraphBuilder; import com.google.common.graph.MutableGraph; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import it.unimi.dsi.fastutil.booleans.BooleanConsumer; -import java.io.*; -import java.util.*; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; diff --git a/src/main/java/net/neoforged/neoforge/common/ToolActions.java b/src/main/java/net/neoforged/neoforge/common/ToolActions.java index 6c7a93c72f7..1a8d47b2a4b 100644 --- a/src/main/java/net/neoforged/neoforge/common/ToolActions.java +++ b/src/main/java/net/neoforged/neoforge/common/ToolActions.java @@ -95,14 +95,26 @@ public class ToolActions { /** * A tool action corresponding to the 'block' action of shields. + * Items should expose this tool action in order to enable damage blocking when the item is being "used". */ public static final ToolAction SHIELD_BLOCK = ToolAction.get("shield_block"); /** - * This action corresponds to right-clicking the fishing rod. + * This action corresponds to right-clicking the fishing rod to reel it in after earlier casting. + * Needed for modded fishing rods so that the FishingHook entity can properly function. */ public static final ToolAction FISHING_ROD_CAST = ToolAction.get("fishing_rod_cast"); + /** + * Exposed by trident-like items to allow querying tool behaviours for items that can be thrown like Tridents. + */ + public static final ToolAction TRIDENT_THROW = ToolAction.get("trident_throw"); + + /** + * Exposed by brushes to allow querying tool behaviours for items that can brush Suspicious Blocks. + */ + public static final ToolAction BRUSH_BRUSH = ToolAction.get("brush_brush"); + // Default actions supported by each tool type public static final Set DEFAULT_AXE_ACTIONS = of(AXE_DIG, AXE_STRIP, AXE_SCRAPE, AXE_WAX_OFF); public static final Set DEFAULT_HOE_ACTIONS = of(HOE_DIG, HOE_TILL); @@ -112,6 +124,8 @@ public class ToolActions { public static final Set DEFAULT_SHEARS_ACTIONS = of(SHEARS_DIG, SHEARS_HARVEST, SHEARS_CARVE, SHEARS_DISARM); public static final Set DEFAULT_SHIELD_ACTIONS = of(SHIELD_BLOCK); public static final Set DEFAULT_FISHING_ROD_ACTIONS = of(FISHING_ROD_CAST); + public static final Set DEFAULT_TRIDENT_ACTIONS = of(TRIDENT_THROW); + public static final Set DEFAULT_BRUSH_ACTIONS = of(BRUSH_BRUSH); private static Set of(ToolAction... actions) { return Stream.of(actions).collect(Collectors.toCollection(Sets::newIdentityHashSet)); diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/ConditionalOps.java b/src/main/java/net/neoforged/neoforge/common/conditions/ConditionalOps.java index 6151bf90222..f34a52e410e 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/ConditionalOps.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/ConditionalOps.java @@ -6,10 +6,14 @@ package net.neoforged.neoforge.common.conditions; import com.mojang.datafixers.util.Pair; -import com.mojang.serialization.*; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Decoder; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.Encoder; +import com.mojang.serialization.MapCodec; import java.util.List; import java.util.Optional; -import java.util.function.ObjIntConsumer; import net.minecraft.resources.RegistryOps; import net.minecraft.util.ExtraCodecs; import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; @@ -73,7 +77,6 @@ public static Codec> createConditionalCodec(final Codec owner * Creates a conditional codec. * *

The conditional codec is generally not suitable for use as a dispatch target because it is never a {@link MapCodec.MapCodecCodec}. - * If you need to dispatch on a conditional codec, consider using {@link NeoForgeExtraCodecs#dispatchUnsafe}. */ public static Codec> createConditionalCodec(final Codec ownerCodec, String conditionalsKey) { return createConditionalCodecWithConditions(ownerCodec, conditionalsKey).xmap(r -> r.map(WithConditions::carrier), r -> r.map(i -> new WithConditions<>(List.of(), i))); @@ -88,23 +91,6 @@ public static Codec> decodeListWithElementConditions(final Codec NeoForgeExtraCodecs.listWithOptionalElements(createConditionalCodec(ownerCodec))); } - /** - * Creates a codec that can decode a list of elements, and will check for conditions on element. - * Additionally, a callback will be invoked with each deserialized element and its index in the list. - * - *

The index is computed before filtering out the elements with non-matching conditions, - * but the callback will only be invoked on the elements with matching conditions. - */ - public static Codec> decodeListWithElementConditionsAndIndexedPeek(final Codec ownerCodec, final ObjIntConsumer consumer) { - return Codec.of( - ownerCodec.listOf(), - NeoForgeExtraCodecs.listWithoutEmpty( - NeoForgeExtraCodecs.decodeOnly( - NeoForgeExtraCodecs.listDecoderWithIndexedPeek( - createConditionalCodec(ownerCodec).listOf(), - (op, i) -> op.ifPresent(o -> consumer.accept(o, i)))))); - } - /** * @see #createConditionalCodecWithConditions(Codec, String) */ @@ -116,7 +102,6 @@ public static Codec>> createConditionalCodecWithC * Creates a conditional codec. * *

The conditional codec is generally not suitable for use as a dispatch target because it is never a {@link MapCodec.MapCodecCodec}. - * If you need to dispatch on a conditional codec, consider using {@link NeoForgeExtraCodecs#dispatchUnsafe}. */ public static Codec>> createConditionalCodecWithConditions(final Codec ownerCodec, String conditionalsKey) { return Codec.of( diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/FalseCondition.java b/src/main/java/net/neoforged/neoforge/common/conditions/FalseCondition.java index c02af1c4e2f..8e9993539c6 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/FalseCondition.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/FalseCondition.java @@ -6,12 +6,13 @@ package net.neoforged.neoforge.common.conditions; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; public final class FalseCondition implements ICondition { public static final FalseCondition INSTANCE = new FalseCondition(); - public static final Codec CODEC = Codec.unit(INSTANCE).stable(); + public static final Codec CODEC = MapCodec.unit(INSTANCE).stable().codec(); private FalseCondition() {} diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java b/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java index 6417f5acb9d..9a0432a52e5 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java @@ -11,7 +11,12 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import net.minecraft.Util; import net.minecraft.core.Holder; @@ -22,15 +27,11 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.util.Unit; -import net.neoforged.neoforge.common.util.NeoForgeExtraCodecs; import net.neoforged.neoforge.registries.NeoForgeRegistries; public interface ICondition { - // Use dispatchUnsafe to always write the condition value inline. - Codec CODEC = NeoForgeExtraCodecs.dispatchUnsafe( - NeoForgeRegistries.CONDITION_SERIALIZERS.byNameCodec(), - ICondition::codec, - Function.identity()); + Codec CODEC = NeoForgeRegistries.CONDITION_SERIALIZERS.byNameCodec() + .dispatch(ICondition::codec, Function.identity()); Codec> LIST_CODEC = CODEC.listOf(); static Optional getConditionally(Codec codec, DynamicOps ops, V element) { diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/TrueCondition.java b/src/main/java/net/neoforged/neoforge/common/conditions/TrueCondition.java index 7ab719b4f7a..3a3fe700527 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/TrueCondition.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/TrueCondition.java @@ -6,12 +6,13 @@ package net.neoforged.neoforge.common.conditions; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; public final class TrueCondition implements ICondition { public static final TrueCondition INSTANCE = new TrueCondition(); - public static Codec CODEC = Codec.unit(INSTANCE).stable(); + public static Codec CODEC = MapCodec.unit(INSTANCE).stable().codec(); private TrueCondition() {} diff --git a/src/main/java/net/neoforged/neoforge/common/crafting/CraftingHelper.java b/src/main/java/net/neoforged/neoforge/common/crafting/CraftingHelper.java index dbcec078575..4e55844bac2 100644 --- a/src/main/java/net/neoforged/neoforge/common/crafting/CraftingHelper.java +++ b/src/main/java/net/neoforged/neoforge/common/crafting/CraftingHelper.java @@ -5,7 +5,8 @@ package net.neoforged.neoforge.common.crafting; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; @@ -117,12 +118,8 @@ public static Codec makeIngredientCodec(boolean allowEmpty, Codec makeIngredientCodec0(boolean allowEmpty, Codec vanillaCodec) { // Dispatch codec for custom ingredient types: - Codec dispatchCodec = - // Use dispatchUnsafe to always inline the dispatched type parameters into the root ingredient object, next to the "type" - NeoForgeExtraCodecs.dispatchUnsafe( - NeoForgeRegistries.INGREDIENT_TYPES.byNameCodec(), - Ingredient::getType, - ingredientType -> ingredientType.codec(allowEmpty)); + Codec dispatchCodec = NeoForgeRegistries.INGREDIENT_TYPES.byNameCodec() + .dispatch(Ingredient::getType, ingredientType -> ingredientType.codec(allowEmpty)); // Either codec to combine with the vanilla ingredient codec: Codec> eitherCodec = ExtraCodecs.either( dispatchCodec, diff --git a/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java b/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java index 0128cb7c976..c9ae532ae93 100644 --- a/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java +++ b/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java @@ -8,7 +8,11 @@ import com.google.common.collect.Lists; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.neoforged.neoforge.common.NeoForgeMod; diff --git a/src/main/java/net/neoforged/neoforge/common/data/ExistingFileHelper.java b/src/main/java/net/neoforged/neoforge/common/data/ExistingFileHelper.java index ead9ebb6961..0f1f4c4bf5d 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/ExistingFileHelper.java +++ b/src/main/java/net/neoforged/neoforge/common/data/ExistingFileHelper.java @@ -117,9 +117,10 @@ public ExistingFileHelper(Collection existingPacks, final Set exis for (String existingMod : existingMods) { IModFileInfo modFileInfo = ModList.get().getModFileById(existingMod); if (modFileInfo != null) { - PackResources pack = ResourcePackLoader.createPackForMod(modFileInfo); - candidateClientResources.add(pack); - candidateServerResources.add(pack); + // Only opens primary packs - overlays are not currently considered for datagen + final String name = "mod:" + existingMod; + candidateClientResources.add(ResourcePackLoader.createPackForMod(modFileInfo).openPrimary(name)); + candidateServerResources.add(ResourcePackLoader.createPackForMod(modFileInfo).openPrimary(name)); } } diff --git a/src/main/java/net/neoforged/neoforge/common/data/JsonCodecProvider.java b/src/main/java/net/neoforged/neoforge/common/data/JsonCodecProvider.java index 54bec169975..5a0bd43c1f1 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/JsonCodecProvider.java +++ b/src/main/java/net/neoforged/neoforge/common/data/JsonCodecProvider.java @@ -13,7 +13,9 @@ import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; import java.nio.file.Path; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import net.minecraft.core.HolderLookup; diff --git a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLootTableProvider.java b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLootTableProvider.java index c395bd8c7e1..48aaacc2609 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLootTableProvider.java +++ b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLootTableProvider.java @@ -38,7 +38,12 @@ import net.minecraft.world.level.storage.loot.entries.SequentialEntry; import net.minecraft.world.level.storage.loot.entries.TagEntry; import net.minecraft.world.level.storage.loot.functions.LootItemFunction; -import net.minecraft.world.level.storage.loot.predicates.*; +import net.minecraft.world.level.storage.loot.predicates.AllOfCondition; +import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; +import net.minecraft.world.level.storage.loot.predicates.CompositeLootItemCondition; +import net.minecraft.world.level.storage.loot.predicates.InvertedLootItemCondition; +import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; +import net.minecraft.world.level.storage.loot.predicates.MatchTool; import net.neoforged.fml.util.ObfuscationReflectionHelper; import net.neoforged.neoforge.common.ToolActions; import net.neoforged.neoforge.common.loot.CanToolPerformAction; diff --git a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeRecipeProvider.java b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeRecipeProvider.java index d783be9ee02..b4970563f13 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeRecipeProvider.java +++ b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeRecipeProvider.java @@ -5,7 +5,12 @@ package net.neoforged.neoforge.common.data.internal; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import net.minecraft.advancements.Advancement; import net.minecraft.advancements.AdvancementHolder; diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java index 225cfac6c87..ac373ef061a 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java @@ -24,7 +24,11 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.FishingHook; import net.minecraft.world.entity.projectile.WitherSkull; -import net.minecraft.world.item.*; +import net.minecraft.world.item.AxeItem; +import net.minecraft.world.item.HoneycombItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.ShovelItem; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.BlockGetter; @@ -33,7 +37,24 @@ import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.SignalGetter; -import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.BeaconBeamBlock; +import net.minecraft.world.level.block.BedBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.FarmBlock; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.FireBlock; +import net.minecraft.world.level.block.HalfTransparentBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.LadderBlock; +import net.minecraft.world.level.block.LeavesBlock; +import net.minecraft.world.level.block.ObserverBlock; +import net.minecraft.world.level.block.RedStoneWireBlock; +import net.minecraft.world.level.block.RepeaterBlock; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.TrapDoorBlock; +import net.minecraft.world.level.block.WeatheringCopper; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockGetterExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockGetterExtension.java index 69d16f510be..28f507685d9 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockGetterExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockGetterExtension.java @@ -10,7 +10,8 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.*; +import net.minecraft.world.level.chunk.ImposterProtoChunk; +import net.minecraft.world.level.chunk.LevelChunk; import net.neoforged.neoforge.client.model.data.ModelDataManager; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java index 7446a3fe7bb..8d9d2a10841 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java @@ -298,7 +298,7 @@ default boolean isEyeInFluidType(FluidType type) { * @return {@code true} if the entity can start swimming, {@code false} otherwise */ default boolean canStartSwimming() { - return !this.getEyeInFluidType().isAir() && this.canSwimInFluidType(this.getEyeInFluidType()); + return !this.getEyeInFluidType().isAir() && this.canSwimInFluidType(this.getEyeInFluidType()) && this.canSwimInFluidType(this.self().level().getFluidState(this.self().blockPosition()).getFluidType()); } /** diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java index acef481f82b..c4b961b4db0 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java @@ -28,7 +28,12 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.food.FoodProperties; -import net.minecraft.world.item.*; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.ArmorMaterials; +import net.minecraft.world.item.AxeItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.item.enchantment.Enchantment; diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IMobEffectExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IMobEffectExtension.java index 8f377387e4f..2bb6895fdb8 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IMobEffectExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IMobEffectExtension.java @@ -5,37 +5,34 @@ package net.neoforged.neoforge.common.extensions; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; +import net.minecraft.world.effect.MobEffects; +import net.neoforged.neoforge.common.EffectCure; +import net.neoforged.neoforge.common.EffectCures; public interface IMobEffectExtension { private MobEffect self() { return (MobEffect) this; } - /** - * Get a fresh list of items that can cure this Potion. - * All new PotionEffects created from this Potion will call this to initialize the default curative items - * - * @see MobEffectInstance#getCurativeItems() - * @return A list of items that can cure this Potion + /*** + * Fill the given set with the {@link EffectCure}s this effect should be curable with by default */ - default List getCurativeItems() { - ArrayList ret = new ArrayList(); - ret.add(new ItemStack(Items.MILK_BUCKET)); - return ret; + default void fillEffectCures(Set cures, MobEffectInstance effectInstance) { + cures.addAll(EffectCures.DEFAULT_CURES); + if (self() == MobEffects.POISON) { + cures.add(EffectCures.HONEY); + } } /** - * Used for determining {@code PotionEffect} sort order in GUIs. - * Defaults to the {@code PotionEffect}'s liquid color. + * Used for determining {@link MobEffect} sort order in GUIs. + * Defaults to the {@link MobEffect}'s liquid color. * - * @param effectInstance the {@code PotionEffect} instance containing the potion - * @return a value used to sort {@code PotionEffect}s in GUIs + * @param effectInstance the {@link MobEffectInstance} containing this {@link MobEffect} + * @return a value used to sort {@link MobEffect}s in GUIs */ default int getSortOrder(MobEffectInstance effectInstance) { return self().getColor(); diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IMobEffectInstanceExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IMobEffectInstanceExtension.java deleted file mode 100644 index e12e1b98254..00000000000 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IMobEffectInstanceExtension.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.common.extensions; - -import java.util.List; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.world.effect.MobEffect; -import net.minecraft.world.item.ItemStack; - -public interface IMobEffectInstanceExtension { - - /*** - * Returns a list of curative items for the potion effect - * By default, this list is initialized using {@link MobEffect#getCurativeItems()} - * - * @return The list (ItemStack) of curative items for the potion effect - */ - List getCurativeItems(); - - /*** - * Checks the given ItemStack to see if it is in the list of curative items for the potion effect - * - * @param stack The ItemStack being checked against the list of curative items for this PotionEffect - * @return true if the given ItemStack is in the list of curative items for this PotionEffect, false otherwise - */ - default boolean isCurativeItem(ItemStack stack) { - return this.getCurativeItems().stream().anyMatch(e -> ItemStack.isSameItem(e, stack)); - } - - /*** - * Sets the list of curative items for this potion effect, overwriting any already present - * - * @param curativeItems The list of ItemStacks being set to the potion effect - */ - void setCurativeItems(List curativeItems); - - /*** - * Adds the given stack to the list of curative items for this PotionEffect - * - * @param stack The ItemStack being added to the curative item list - */ - default void addCurativeItem(ItemStack stack) { - if (!this.isCurativeItem(stack)) - this.getCurativeItems().add(stack); - } - - default void writeCurativeItems(CompoundTag nbt) { - ListTag list = new ListTag(); - getCurativeItems().forEach(s -> list.add(s.save(new CompoundTag()))); - nbt.put("CurativeItems", list); - } -} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IPackResourcesExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IPackResourcesExtension.java index 76658ae7c7a..6064ace2a21 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IPackResourcesExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IPackResourcesExtension.java @@ -5,12 +5,6 @@ package net.neoforged.neoforge.common.extensions; -import java.util.Collection; -import net.minecraft.server.packs.PackResources; -import net.minecraft.server.packs.resources.ResourceManager; -import net.neoforged.neoforge.resource.DelegatingPackResources; -import org.jetbrains.annotations.Nullable; - public interface IPackResourcesExtension { /** * {@return {@code true} if the pack should be hidden from any user interfaces} @@ -18,18 +12,4 @@ public interface IPackResourcesExtension { default boolean isHidden() { return false; } - - /** - * Gets a collection of {@code PackResource} instances nested inside this pack. - * Used to merge several packs into one entry in the resource pack selection UI without - * losing the ability for each pack to return a resource in - * {@link ResourceManager#getResourceStack(ResourceLocation)} - * - * @return Collection of nested {@code PackResource}, or null if this pack has no children - * @see DelegatingPackResources - */ - @Nullable - default Collection getChildren() { - return null; - } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java index 4dee1e3cb84..a4a6722ee14 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java @@ -20,13 +20,13 @@ private Player self() { } /** - * The entity reach is increased by 3 for creative players, unless it is currently zero, which disables attacks and entity interactions. + * The entity reach is increased by 2 for creative players, unless it is currently zero, which disables attacks and entity interactions. * * @return The entity reach of this player. */ default double getEntityReach() { double range = self().getAttributeValue(NeoForgeMod.ENTITY_REACH.value()); - return range == 0 ? 0 : range + (self().isCreative() ? 3 : 0); + return range == 0 ? 0 : range + (self().isCreative() ? 2 : 0); } /** diff --git a/src/main/java/net/neoforged/neoforge/common/util/ConcatenatedListView.java b/src/main/java/net/neoforged/neoforge/common/util/ConcatenatedListView.java index dc55512a5db..99518ad55e4 100644 --- a/src/main/java/net/neoforged/neoforge/common/util/ConcatenatedListView.java +++ b/src/main/java/net/neoforged/neoforge/common/util/ConcatenatedListView.java @@ -7,7 +7,14 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Spliterator; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/net/neoforged/neoforge/common/util/NeoForgeExtraCodecs.java b/src/main/java/net/neoforged/neoforge/common/util/NeoForgeExtraCodecs.java index 65edbfc50c8..da8908509ca 100644 --- a/src/main/java/net/neoforged/neoforge/common/util/NeoForgeExtraCodecs.java +++ b/src/main/java/net/neoforged/neoforge/common/util/NeoForgeExtraCodecs.java @@ -13,15 +13,11 @@ import com.mojang.serialization.DataResult; import com.mojang.serialization.Decoder; import com.mojang.serialization.DynamicOps; -import com.mojang.serialization.Encoder; import com.mojang.serialization.MapCodec; -import com.mojang.serialization.MapCodec.MapCodecCodec; -import com.mojang.serialization.codecs.KeyDispatchCodec; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.function.ObjIntConsumer; import net.minecraft.advancements.Advancement; import net.minecraft.util.ExtraCodecs; import net.minecraft.world.item.crafting.Recipe; @@ -82,39 +78,6 @@ public static Codec> setOf(final Codec codec) { return Codec.list(codec).xmap(ImmutableSet::copyOf, ImmutableList::copyOf); } - /** - * Version of {@link Codec#dispatch(Function, Function)} that always writes the dispatched codec inline, - * i.e. at the same nesting level as the {@code "type": ...}. - *

- * Note: the codec produced by {@code .dispatch()} inlines the dispatched codec ONLY if it is a {@link MapCodecCodec}. - * This function always inlines. - */ - public static Codec dispatchUnsafe(final Codec keyCodec, final Function type, final Function> codec) { - return dispatchUnsafe(keyCodec, "type", type, codec); - } - - /** - * Version of {@link Codec#dispatch(String, Function, Function)} that always writes the dispatched codec inline, - * i.e. at the same nesting level as the {@code "type": ...}. - *

- * Note: the codec produced by {@code .dispatch()} inlines the dispatched codec ONLY if it is a {@link MapCodecCodec}. - * This function always inlines. - */ - public static Codec dispatchUnsafe(final Codec keyCodec, final String typeKey, final Function type, final Function> codec) { - return partialDispatchUnsafe(keyCodec, typeKey, type.andThen(DataResult::success), codec.andThen(DataResult::success)); - } - - /** - * Version of {@link Codec#partialDispatch(String, Function, Function)} that always writes the dispatched codec inline, - * i.e. at the same nesting level as the {@code "type": ...}. - *

- * Note: the codec produced by {@code .dispatch()} inlines the dispatched codec ONLY if it is a {@link MapCodecCodec}. - * This function always inlines. - */ - public static Codec partialDispatchUnsafe(final Codec keyCodec, final String typeKey, final Function> type, final Function>> codec) { - return KeyDispatchCodec.unsafe(typeKey, keyCodec, type, codec, v -> type.apply(v).>flatMap(k -> codec.apply(k).map(Function.identity())).map(e -> ((Encoder) e))).codec(); - } - /** * Creates a codec from a decoder. * The returned codec can only decode, and will throw on any attempt to encode. @@ -142,19 +105,6 @@ public static Codec> listWithoutEmpty(Codec>> codec list -> list.stream().map(Optional::of).toList()); } - /** - * Creates a decoder invoking a callback for each element and the corresponding index in a list. - */ - public static Decoder> listDecoderWithIndexedPeek(final Decoder> decoder, ObjIntConsumer consumer) { - return decoder.map( - list -> { - for (int i = 0; i < list.size(); i++) { - consumer.accept(list.get(i), i); - } - return list; - }); - } - /** * Codec with two alternatives. *

diff --git a/src/main/java/net/neoforged/neoforge/event/EventHooks.java b/src/main/java/net/neoforged/neoforge/event/EventHooks.java index 9bac542d3cb..063f4b3eb92 100644 --- a/src/main/java/net/neoforged/neoforge/event/EventHooks.java +++ b/src/main/java/net/neoforged/neoforge/event/EventHooks.java @@ -8,7 +8,9 @@ import com.mojang.authlib.GameProfile; import com.mojang.brigadier.CommandDispatcher; import java.io.File; -import java.util.*; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import net.minecraft.advancements.AdvancementHolder; @@ -34,6 +36,7 @@ import net.minecraft.server.players.PlayerList; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundSource; +import net.minecraft.stats.Stat; import net.minecraft.util.RandomSource; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.Container; @@ -41,6 +44,8 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityType; @@ -84,6 +89,7 @@ import net.minecraft.world.phys.Vec3; import net.neoforged.bus.api.Event.Result; import net.neoforged.fml.LogicalSide; +import net.neoforged.neoforge.common.EffectCure; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.ToolAction; import net.neoforged.neoforge.common.util.BlockSnapshot; @@ -104,6 +110,7 @@ import net.neoforged.neoforge.event.entity.living.LivingExperienceDropEvent; import net.neoforged.neoforge.event.entity.living.LivingHealEvent; import net.neoforged.neoforge.event.entity.living.LivingPackSizeEvent; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; import net.neoforged.neoforge.event.entity.living.MobSpawnEvent; import net.neoforged.neoforge.event.entity.living.MobSpawnEvent.AllowDespawn; import net.neoforged.neoforge.event.entity.living.MobSpawnEvent.PositionCheck; @@ -840,6 +847,12 @@ public static WeightedRandomList getPotentialSpawn return WeightedRandomList.create(event.getSpawnerDataList()); } + public static StatAwardEvent onStatAward(Player player, Stat stat, int value) { + StatAwardEvent event = new StatAwardEvent(player, stat, value); + NeoForge.EVENT_BUS.post(event); + return event; + } + @ApiStatus.Internal public static void onAdvancementEarnedEvent(Player player, AdvancementHolder earned) { NeoForge.EVENT_BUS.post(new AdvancementEarnEvent(player, earned)); @@ -849,4 +862,12 @@ public static void onAdvancementEarnedEvent(Player player, AdvancementHolder ear public static void onAdvancementProgressedEvent(Player player, AdvancementHolder progressed, AdvancementProgress advancementProgress, String criterion, ProgressType progressType) { NeoForge.EVENT_BUS.post(new AdvancementProgressEvent(player, progressed, advancementProgress, criterion, progressType)); } + + public static boolean onEffectRemoved(LivingEntity entity, MobEffect effect, @Nullable EffectCure cure) { + return NeoForge.EVENT_BUS.post(new MobEffectEvent.Remove(entity, effect, cure)).isCanceled(); + } + + public static boolean onEffectRemoved(LivingEntity entity, MobEffectInstance effectInstance, @Nullable EffectCure cure) { + return NeoForge.EVENT_BUS.post(new MobEffectEvent.Remove(entity, effectInstance, cure)).isCanceled(); + } } diff --git a/src/main/java/net/neoforged/neoforge/event/LootTableLoadEvent.java b/src/main/java/net/neoforged/neoforge/event/LootTableLoadEvent.java index b8d311ce210..6c1cff856a8 100644 --- a/src/main/java/net/neoforged/neoforge/event/LootTableLoadEvent.java +++ b/src/main/java/net/neoforged/neoforge/event/LootTableLoadEvent.java @@ -5,6 +5,7 @@ package net.neoforged.neoforge.event; +import java.util.Objects; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.storage.loot.LootTable; import net.neoforged.bus.api.Event; @@ -14,7 +15,8 @@ /** * Fired when a {@link LootTable} is loaded from JSON. - * Loot tables loaded from world save datapacks will not fire this event as they are considered user configuration files. + * Can be used to modify the loot table, cancel loading it, or outright replace it. + * This event is currently fired for all loot tables coming from vanilla, mods, and user datapacks. * This event is fired whenever server resources are loaded or reloaded. * *

This event is {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}. @@ -41,6 +43,7 @@ public LootTable getTable() { } public void setTable(LootTable table) { + Objects.requireNonNull(table); this.table = table; } } diff --git a/src/main/java/net/neoforged/neoforge/event/StatAwardEvent.java b/src/main/java/net/neoforged/neoforge/event/StatAwardEvent.java new file mode 100644 index 00000000000..e61616de4a9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/event/StatAwardEvent.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.event; + +import net.minecraft.stats.Stat; +import net.minecraft.world.entity.player.Player; +import net.neoforged.bus.api.ICancellableEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Fired when a {@link Player} is awarded a {@link Stat}. This event is fired in {@link Player#awardStat(Stat, int)} + *

+ * This event is {@link ICancellableEvent cancelable}. + *

+ * This event does not have a result. + *

+ * This event is fired on the {@link NeoForge#EVENT_BUS}. + */ +public class StatAwardEvent extends PlayerEvent implements ICancellableEvent { + private Stat stat; + private int value; + + @ApiStatus.Internal + public StatAwardEvent(Player player, Stat stat, int value) { + super(player); + this.stat = stat; + this.value = value; + } + + /** {@return the {@link Stat} being awarded} */ + public Stat getStat() { + return stat; + } + + /** Replaces the {@link Stat} to be awarded */ + public void setStat(Stat stat) { + this.stat = stat; + } + + /** {@return the current value to be awarded to the {@link Stat}} */ + public int getValue() { + return value; + } + + /** Replaces the value to be awarded. */ + public void setValue(int value) { + this.value = value; + } +} diff --git a/src/main/java/net/neoforged/neoforge/event/entity/living/MobEffectEvent.java b/src/main/java/net/neoforged/neoforge/event/entity/living/MobEffectEvent.java index 6dad6c07042..973587a61c6 100644 --- a/src/main/java/net/neoforged/neoforge/event/entity/living/MobEffectEvent.java +++ b/src/main/java/net/neoforged/neoforge/event/entity/living/MobEffectEvent.java @@ -10,7 +10,9 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.neoforged.bus.api.ICancellableEvent; +import net.neoforged.neoforge.common.EffectCure; import net.neoforged.neoforge.common.NeoForge; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,7 +25,7 @@ public abstract class MobEffectEvent extends LivingEvent { @Nullable protected final MobEffectInstance effectInstance; - public MobEffectEvent(LivingEntity living, MobEffectInstance effectInstance) { + protected MobEffectEvent(LivingEntity living, MobEffectInstance effectInstance) { super(living); this.effectInstance = effectInstance; } @@ -40,24 +42,38 @@ public MobEffectInstance getEffectInstance() { */ public static class Remove extends MobEffectEvent implements ICancellableEvent { private final MobEffect effect; + @Nullable + private final EffectCure cure; - public Remove(LivingEntity living, MobEffect effect) { + @ApiStatus.Internal + public Remove(LivingEntity living, MobEffect effect, @Nullable EffectCure cure) { super(living, living.getEffect(effect)); this.effect = effect; + this.cure = cure; } - public Remove(LivingEntity living, MobEffectInstance effectInstance) { + @ApiStatus.Internal + public Remove(LivingEntity living, MobEffectInstance effectInstance, @Nullable EffectCure cure) { super(living, effectInstance); this.effect = effectInstance.getEffect(); + this.cure = cure; } /** - * @return the {@link MobEffectEvent} which is being removed from the entity + * @return the {@link MobEffect} which is being removed from the entity */ public MobEffect getEffect() { return this.effect; } + /** + * {@return the {@link EffectCure} the effect is being cured by. Null if the effect is not removed due to being cured} + */ + @Nullable + public EffectCure getCure() { + return cure; + } + /** * @return the {@link MobEffectInstance}. In the remove event, this can be null if the entity does not have a {@link MobEffect} of the right type active. */ @@ -79,6 +95,7 @@ public MobEffectInstance getEffectInstance() { */ @HasResult public static class Applicable extends MobEffectEvent { + @ApiStatus.Internal public Applicable(LivingEntity living, @NotNull MobEffectInstance effectInstance) { super(living, effectInstance); } @@ -100,6 +117,7 @@ public static class Added extends MobEffectEvent { private final MobEffectInstance oldEffectInstance; private final Entity source; + @ApiStatus.Internal public Added(LivingEntity living, MobEffectInstance oldEffectInstance, MobEffectInstance newEffectInstance, Entity source) { super(living, newEffectInstance); this.oldEffectInstance = oldEffectInstance; @@ -138,6 +156,7 @@ public Entity getEffectSource() { * This event does not have a result. */ public static class Expired extends MobEffectEvent implements ICancellableEvent { + @ApiStatus.Internal public Expired(LivingEntity living, MobEffectInstance effectInstance) { super(living, effectInstance); } diff --git a/src/main/java/net/neoforged/neoforge/fluids/CauldronFluidContent.java b/src/main/java/net/neoforged/neoforge/fluids/CauldronFluidContent.java new file mode 100644 index 00000000000..a2f0ea44fe8 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/CauldronFluidContent.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.fluids; + +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Map; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LayeredCauldronBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; +import net.neoforged.fml.ModLoader; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; +import net.neoforged.neoforge.fluids.capability.wrappers.CauldronWrapper; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +/** + * Fluid content information for cauldrons. + * + *

Empty, water and lava cauldrons are registered by default, + * and additional cauldrons must be registered with {@link RegisterCauldronFluidContentEvent}. + * Contents can be queried with {@link #getForBlock} and {@link #getForFluid}. + * + *

The {@code CauldronFluidContent} itself defines: + *

+ */ +public final class CauldronFluidContent { + /** + * Block of the cauldron. + */ + public final Block block; + /** + * Fluid stored inside the cauldron. + */ + public final Fluid fluid; + /** + * Amount of {@code #fluid} in millibuckets in the entire full cauldron. + */ + public final int totalAmount; + /** + * Maximum level for {@link #levelProperty}. {@code 1} if {@code levelProperty} is null, otherwise a number {@code >= 1}. + * The minimum level is always 1. + */ + public final int maxLevel; + /** + * Property storing the level of the cauldron. If it's {@code null}, only one level is possible. + */ + @Nullable + public final IntegerProperty levelProperty; + + /** + * Return the current level of the cauldron given its block state, or 0 if it's an empty cauldron. + */ + public int currentLevel(BlockState state) { + if (fluid == Fluids.EMPTY) { + return 0; + } else if (levelProperty == null) { + return 1; + } else { + return state.getValue(levelProperty); + } + } + + private CauldronFluidContent(Block block, Fluid fluid, int totalAmount, int maxLevel, @Nullable IntegerProperty levelProperty) { + this.block = block; + this.fluid = fluid; + this.totalAmount = totalAmount; + this.maxLevel = maxLevel; + this.levelProperty = levelProperty; + } + + private static final Map BLOCK_TO_CAULDRON = new IdentityHashMap<>(); + private static final Map FLUID_TO_CAULDRON = new IdentityHashMap<>(); + + /** + * Get the cauldron fluid content for a cauldron block, or {@code null} if none was registered (yet). + */ + @Nullable + public static CauldronFluidContent getForBlock(Block block) { + return BLOCK_TO_CAULDRON.get(block); + } + + /** + * Get the cauldron fluid content for a fluid, or {@code null} if no cauldron was registered for that fluid (yet). + */ + @Nullable + public static CauldronFluidContent getForFluid(Fluid fluid) { + return FLUID_TO_CAULDRON.get(fluid); + } + + @ApiStatus.Internal + public static void init() { + var registerEvent = new RegisterCauldronFluidContentEvent(); + // Vanilla registrations + registerEvent.register(Blocks.CAULDRON, Fluids.EMPTY, FluidType.BUCKET_VOLUME, null); + registerEvent.register(Blocks.WATER_CAULDRON, Fluids.WATER, FluidType.BUCKET_VOLUME, LayeredCauldronBlock.LEVEL); + registerEvent.register(Blocks.LAVA_CAULDRON, Fluids.LAVA, FluidType.BUCKET_VOLUME, null); + // Modded registrations + ModLoader.get().postEvent(registerEvent); + } + + /** + * Do not try to call, use the {@link RegisterCauldronFluidContentEvent} event instead. + */ + static void register(Block block, Fluid fluid, int totalAmount, @Nullable IntegerProperty levelProperty) { + if (BLOCK_TO_CAULDRON.get(block) != null) { + throw new IllegalArgumentException("Duplicate cauldron registration for block %s.".formatted(block)); + } + if (FLUID_TO_CAULDRON.get(fluid) != null) { + throw new IllegalArgumentException("Duplicate cauldron registration for fluid %s.".formatted(fluid)); + } + if (totalAmount <= 0) { + throw new IllegalArgumentException("Cauldron total amount %d should be positive.".formatted(totalAmount)); + } + + CauldronFluidContent data; + + if (levelProperty == null) { + data = new CauldronFluidContent(block, fluid, totalAmount, 1, null); + } else { + Collection levels = levelProperty.getPossibleValues(); + if (levels.isEmpty()) { + throw new IllegalArgumentException("Cauldron should have at least one possible level."); + } + + int minLevel = Integer.MAX_VALUE; + int maxLevel = 0; + + for (int level : levels) { + minLevel = Math.min(minLevel, level); + maxLevel = Math.max(maxLevel, level); + } + + if (minLevel != 1) { + throw new IllegalStateException("Minimum level should be 1, and maximum level should be >= 1."); + } + + data = new CauldronFluidContent(block, fluid, totalAmount, maxLevel, levelProperty); + } + + BLOCK_TO_CAULDRON.put(block, data); + FLUID_TO_CAULDRON.put(fluid, data); + } + + @ApiStatus.Internal + public static void registerCapabilities(RegisterCapabilitiesEvent event) { + if (BLOCK_TO_CAULDRON.isEmpty()) { + throw new IllegalStateException("CauldronFluidContent.init() should have been called before the capability event!"); + } + + for (Block block : BLOCK_TO_CAULDRON.keySet()) { + event.registerBlock( + Capabilities.FluidHandler.BLOCK, + (level, pos, state, be, context) -> new CauldronWrapper(level, pos), + block); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/RegisterCauldronFluidContentEvent.java b/src/main/java/net/neoforged/neoforge/fluids/RegisterCauldronFluidContentEvent.java new file mode 100644 index 00000000000..57c5bb38456 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/RegisterCauldronFluidContentEvent.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.fluids; + +import java.util.Objects; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.AbstractCauldronBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.Fluid; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import net.neoforged.neoforge.capabilities.Capabilities; +import org.jetbrains.annotations.Nullable; + +/** + * Event to register {@link CauldronFluidContent} for modded cauldrons. + * + *

Registering cauldrons is done by calling {@link CauldronFluidContent#register} + * and allows all cauldrons registered in this way to interoperate with each other + * when accessed via the {@link Capabilities.FluidHandler#BLOCK} capability. + */ +public class RegisterCauldronFluidContentEvent extends Event implements IModBusEvent { + RegisterCauldronFluidContentEvent() {} + + /** + * Register a new cauldron, allowing it to be filled and emptied through the standard capability. + * In both cases, return the content of the cauldron, either the existing one, or the newly registered one. + * + *

If the block is not a subclass of {@link AbstractCauldronBlock}, + * {@link BlockBehaviour#onPlace(BlockState, Level, BlockPos, BlockState, boolean)} + * and {@link BlockBehaviour#onRemove(BlockState, Level, BlockPos, BlockState, boolean)} + * must be overridden to invalidate capabilities when the block changes! + * See how NeoForge patches {@link AbstractCauldronBlock} for reference. + * + * @param block the block of the cauldron + * @param fluid the fluid stored in this cauldron + * @param totalAmount how much fluid can fit in the cauldron at maximum capacity, in {@linkplain FluidStack millibuckets} + * @param levelProperty the property used by the cauldron to store its levels, or {@code null} if the cauldron only has one level + */ + public void register(Block block, Fluid fluid, int totalAmount, @Nullable IntegerProperty levelProperty) { + Objects.requireNonNull(block, "Block may not be null"); + Objects.requireNonNull(fluid, "Fluid may not be null"); + + CauldronFluidContent.register(block, fluid, totalAmount, levelProperty); + } +} diff --git a/src/main/java/net/neoforged/neoforge/fluids/capability/wrappers/CauldronWrapper.java b/src/main/java/net/neoforged/neoforge/fluids/capability/wrappers/CauldronWrapper.java new file mode 100644 index 00000000000..d5df96a3f41 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/fluids/capability/wrappers/CauldronWrapper.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.fluids.capability.wrappers; + +import com.google.common.math.IntMath; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.fluids.CauldronFluidContent; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class CauldronWrapper implements IFluidHandler { + private final Level level; + private final BlockPos pos; + + public CauldronWrapper(Level level, BlockPos pos) { + this.level = level; + this.pos = pos; + } + + @Override + public int getTanks() { + return 1; + } + + private CauldronFluidContent getContent(BlockState state) { + CauldronFluidContent content = CauldronFluidContent.getForBlock(state.getBlock()); + if (content == null) { + throw new IllegalStateException("Unexpected error: no cauldron at location " + pos); + } + return content; + } + + @Override + public FluidStack getFluidInTank(int tank) { + BlockState state = level.getBlockState(pos); + CauldronFluidContent contents = getContent(state); + return new FluidStack(contents.fluid, contents.totalAmount * contents.currentLevel(state) / contents.maxLevel); + } + + @Override + public int getTankCapacity(int tank) { + BlockState state = level.getBlockState(pos); + CauldronFluidContent contents = getContent(state); + return contents.totalAmount; + } + + @Override + public boolean isFluidValid(int tank, FluidStack stack) { + return CauldronFluidContent.getForFluid(stack.getFluid()) != null; + } + + // Called by fill and drain to update the block state. + private void updateLevel(CauldronFluidContent newContent, int level, FluidAction action) { + if (action.execute()) { + BlockState newState = newContent.block.defaultBlockState(); + + if (newContent.levelProperty != null) { + newState = newState.setValue(newContent.levelProperty, level); + } + + this.level.setBlockAndUpdate(pos, newState); + } + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) { + return 0; + } + + CauldronFluidContent insertContent = CauldronFluidContent.getForFluid(resource.getFluid()); + if (insertContent == null) { + return 0; + } + + BlockState state = level.getBlockState(pos); + CauldronFluidContent currentContent = getContent(state); + if (currentContent.fluid != Fluids.EMPTY && currentContent.fluid != resource.getFluid()) { + // Fluid mismatch + return 0; + } + + // We can only insert increments based on the GCD between the number of levels and the total amount. + int d = IntMath.gcd(insertContent.maxLevel, insertContent.totalAmount); + int amountIncrements = insertContent.totalAmount / d; + int levelIncrements = insertContent.maxLevel / d; + + int currentLevel = currentContent.currentLevel(state); + int insertedIncrements = Math.min(resource.getAmount() / amountIncrements, (insertContent.maxLevel - currentLevel) / levelIncrements); + if (insertedIncrements > 0) { + updateLevel(insertContent, currentLevel + insertedIncrements * levelIncrements, action); + } + + return insertedIncrements * amountIncrements; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) { + return FluidStack.EMPTY; + } + + BlockState state = level.getBlockState(pos); + if (getContent(state).fluid == resource.getFluid() && !resource.hasTag()) { + return drain(state, resource.getAmount(), action); + } else { + return FluidStack.EMPTY; + } + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + if (maxDrain <= 0) { + return FluidStack.EMPTY; + } + + return drain(level.getBlockState(pos), maxDrain, action); + } + + private FluidStack drain(BlockState state, int maxDrain, FluidAction action) { + CauldronFluidContent content = getContent(state); + + // We can only extract increments based on the GCD between the number of levels and the total amount. + int d = IntMath.gcd(content.maxLevel, content.totalAmount); + int amountIncrements = content.totalAmount / d; + int levelIncrements = content.maxLevel / d; + + int currentLevel = content.currentLevel(state); + int extractedIncrements = Math.min(maxDrain / amountIncrements, currentLevel / levelIncrements); + if (extractedIncrements > 0) { + int newLevel = currentLevel - extractedIncrements * levelIncrements; + if (newLevel == 0) { + // Fully extract -> back to empty cauldron + if (action.execute()) { + level.setBlockAndUpdate(pos, Blocks.CAULDRON.defaultBlockState()); + } + } else { + // Otherwise just decrease levels + updateLevel(content, newLevel, action); + } + } + + return new FluidStack(content.fluid, extractedIncrements * amountIncrements); + } +} diff --git a/src/main/java/net/neoforged/neoforge/internal/RegistrationEvents.java b/src/main/java/net/neoforged/neoforge/internal/RegistrationEvents.java index 5d1a3eba367..6273fb11ac6 100644 --- a/src/main/java/net/neoforged/neoforge/internal/RegistrationEvents.java +++ b/src/main/java/net/neoforged/neoforge/internal/RegistrationEvents.java @@ -7,10 +7,12 @@ import net.neoforged.neoforge.capabilities.CapabilityHooks; import net.neoforged.neoforge.common.world.chunk.ForcedChunkManager; +import net.neoforged.neoforge.fluids.CauldronFluidContent; class RegistrationEvents { public static void init() { - CapabilityHooks.init(); + CauldronFluidContent.init(); // must be before capability event + CapabilityHooks.init(); // must be after cauldron event ForcedChunkManager.init(); } } diff --git a/src/main/java/net/neoforged/neoforge/logging/CrashReportExtender.java b/src/main/java/net/neoforged/neoforge/logging/CrashReportExtender.java index 59a7a8da212..f0425eb2170 100644 --- a/src/main/java/net/neoforged/neoforge/logging/CrashReportExtender.java +++ b/src/main/java/net/neoforged/neoforge/logging/CrashReportExtender.java @@ -8,7 +8,9 @@ import cpw.mods.modlauncher.log.TransformingThrowablePatternConverter; import java.io.File; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Date; +import java.util.Objects; +import java.util.Optional; import joptsimple.internal.Strings; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; diff --git a/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketSplitter.java b/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketSplitter.java index 450ce788f4b..d452a18d75b 100644 --- a/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketSplitter.java +++ b/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketSplitter.java @@ -10,12 +10,20 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; -import net.minecraft.network.*; +import net.minecraft.network.CompressionDecoder; +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.PacketListener; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.*; +import net.neoforged.neoforge.network.ConnectionData; +import net.neoforged.neoforge.network.NetworkEvent; +import net.neoforged.neoforge.network.NetworkHooks; +import net.neoforged.neoforge.network.NetworkRegistry; +import net.neoforged.neoforge.network.PlayNetworkDirection; import net.neoforged.neoforge.network.custom.payload.SimplePayload; import net.neoforged.neoforge.network.event.EventNetworkChannel; import org.apache.logging.log4j.LogManager; diff --git a/src/main/java/net/neoforged/neoforge/network/simple/SimpleChannel.java b/src/main/java/net/neoforged/neoforge/network/simple/SimpleChannel.java index 90aa694c4f9..274bf752db1 100644 --- a/src/main/java/net/neoforged/neoforge/network/simple/SimpleChannel.java +++ b/src/main/java/net/neoforged/neoforge/network/simple/SimpleChannel.java @@ -7,8 +7,13 @@ import io.netty.buffer.Unpooled; import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.function.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.IntSupplier; import net.minecraft.client.Minecraft; import net.minecraft.network.Connection; import net.minecraft.network.FriendlyByteBuf; @@ -18,7 +23,13 @@ import net.neoforged.neoforge.network.NetworkInstance; import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.PlayNetworkDirection; -import net.neoforged.neoforge.network.simple.MessageFunctions.*; +import net.neoforged.neoforge.network.simple.MessageFunctions.LoginIndexGetter; +import net.neoforged.neoforge.network.simple.MessageFunctions.LoginIndexSetter; +import net.neoforged.neoforge.network.simple.MessageFunctions.LoginPacket; +import net.neoforged.neoforge.network.simple.MessageFunctions.LoginPacketGenerator; +import net.neoforged.neoforge.network.simple.MessageFunctions.MessageConsumer; +import net.neoforged.neoforge.network.simple.MessageFunctions.MessageDecoder; +import net.neoforged.neoforge.network.simple.MessageFunctions.MessageEncoder; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class SimpleChannel { diff --git a/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java b/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java index a929b442254..cfc0fcd1023 100644 --- a/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java +++ b/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java @@ -156,8 +156,8 @@ public static DeferredRegister.Blocks createBlocks(String modid) { private final ResourceKey> registryKey; private final String namespace; - private final Map, Supplier> entries = new LinkedHashMap<>(); - private final Set> entriesView = Collections.unmodifiableSet(entries.keySet()); + private final Map, Supplier> entries = new LinkedHashMap<>(); + private final Set> entriesView = Collections.unmodifiableSet(entries.keySet()); private final Map aliases = new HashMap<>(); @Nullable diff --git a/src/main/java/net/neoforged/neoforge/resource/DelegatingPackResources.java b/src/main/java/net/neoforged/neoforge/resource/DelegatingPackResources.java deleted file mode 100644 index 4d63da4b993..00000000000 --- a/src/main/java/net/neoforged/neoforge/resource/DelegatingPackResources.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.resource; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.AbstractPackResources; -import net.minecraft.server.packs.PackResources; -import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.metadata.MetadataSectionSerializer; -import net.minecraft.server.packs.metadata.pack.PackMetadataSection; -import net.minecraft.server.packs.resources.IoSupplier; -import org.jetbrains.annotations.Nullable; - -public class DelegatingPackResources extends AbstractPackResources { - private final PackMetadataSection packMeta; - private final List delegates; - private final Map> namespacesAssets; - private final Map> namespacesData; - - public DelegatingPackResources(String packId, boolean isBuiltin, PackMetadataSection packMeta, List packs) { - super(packId, isBuiltin); - this.packMeta = packMeta; - this.delegates = ImmutableList.copyOf(packs); - this.namespacesAssets = this.buildNamespaceMap(PackType.CLIENT_RESOURCES, delegates); - this.namespacesData = this.buildNamespaceMap(PackType.SERVER_DATA, delegates); - } - - private Map> buildNamespaceMap(PackType type, List packList) { - Map> map = new HashMap<>(); - for (PackResources pack : packList) { - for (String namespace : pack.getNamespaces(type)) { - map.computeIfAbsent(namespace, k -> new ArrayList<>()).add(pack); - } - } - map.replaceAll((k, list) -> ImmutableList.copyOf(list)); - return ImmutableMap.copyOf(map); - } - - @SuppressWarnings("unchecked") - @Nullable - @Override - public T getMetadataSection(MetadataSectionSerializer deserializer) throws IOException { - return deserializer.getMetadataSectionName().equals("pack") ? (T) this.packMeta : null; - } - - @Override - public void listResources(PackType type, String resourceNamespace, String paths, ResourceOutput resourceOutput) { - for (PackResources delegate : this.delegates) { - delegate.listResources(type, resourceNamespace, paths, resourceOutput); - } - } - - @Override - public Set getNamespaces(PackType type) { - return type == PackType.CLIENT_RESOURCES ? namespacesAssets.keySet() : namespacesData.keySet(); - } - - @Override - public void close() { - for (PackResources pack : delegates) { - pack.close(); - } - } - - @Nullable - @Override - public IoSupplier getRootResource(String... paths) { - // Root resources do not make sense here - return null; - } - - @Nullable - @Override - public IoSupplier getResource(PackType type, ResourceLocation location) { - for (PackResources pack : getCandidatePacks(type, location)) { - IoSupplier ioSupplier = pack.getResource(type, location); - if (ioSupplier != null) - return ioSupplier; - } - - return null; - } - - @Nullable - public Collection getChildren() { - return delegates; - } - - private List getCandidatePacks(PackType type, ResourceLocation location) { - Map> map = type == PackType.CLIENT_RESOURCES ? namespacesAssets : namespacesData; - List packsWithNamespace = map.get(location.getNamespace()); - return packsWithNamespace == null ? Collections.emptyList() : packsWithNamespace; - } -} diff --git a/src/main/java/net/neoforged/neoforge/resource/EmptyPackResources.java b/src/main/java/net/neoforged/neoforge/resource/EmptyPackResources.java new file mode 100644 index 00000000000..4e140c0c2ec --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/resource/EmptyPackResources.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.resource; + +import java.io.InputStream; +import java.util.Collections; +import java.util.Set; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.AbstractPackResources; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.metadata.MetadataSectionSerializer; +import net.minecraft.server.packs.metadata.pack.PackMetadataSection; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.resources.IoSupplier; +import org.jetbrains.annotations.Nullable; + +public class EmptyPackResources extends AbstractPackResources { + private final PackMetadataSection packMeta; + + public EmptyPackResources(String packId, boolean isBuiltin, PackMetadataSection packMeta) { + super(packId, isBuiltin); + this.packMeta = packMeta; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getMetadataSection(MetadataSectionSerializer deserializer) { + return deserializer.getMetadataSectionName().equals("pack") ? (T) this.packMeta : null; + } + + @Override + public void close() {} + + @Override + public void listResources(PackType type, String resourceNamespace, String paths, ResourceOutput resourceOutput) {} + + @Override + public Set getNamespaces(PackType type) { + return Collections.emptySet(); + } + + @Nullable + @Override + public IoSupplier getRootResource(String... paths) { + // Root resources do not make sense here + return null; + } + + @Nullable + @Override + public IoSupplier getResource(PackType type, ResourceLocation location) { + return null; + } + + public static class EmptyResourcesSupplier implements Pack.ResourcesSupplier { + private final PackMetadataSection packMeta; + private final boolean isBuiltin; + + public EmptyResourcesSupplier(PackMetadataSection packMeta, boolean isBuiltin) { + this.packMeta = packMeta; + this.isBuiltin = isBuiltin; + } + + @Override + public PackResources openPrimary(String id) { + return new EmptyPackResources(id, isBuiltin, packMeta); + } + + @Override + public PackResources openFull(String id, Pack.Info info) { + return openPrimary(id); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java b/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java index a7c19956ea9..5aa447d3082 100644 --- a/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java +++ b/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java @@ -15,47 +15,123 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; -import net.minecraft.server.packs.PackResources; +import net.minecraft.SharedConstants; +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.PathPackResources; +import net.minecraft.server.packs.metadata.pack.PackMetadataSection; +import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.repository.PackSource; import net.minecraft.server.packs.repository.RepositorySource; +import net.neoforged.fml.Logging; import net.neoforged.fml.ModList; +import net.neoforged.fml.ModLoader; +import net.neoforged.fml.ModLoadingStage; +import net.neoforged.fml.ModLoadingWarning; import net.neoforged.neoforgespi.language.IModFileInfo; +import net.neoforged.neoforgespi.language.IModInfo; import net.neoforged.neoforgespi.locating.IModFile; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; public class ResourcePackLoader { - private static Map modResourcePacks; + public static final String MOD_DATA_ID = "mod_data"; + public static final String MOD_RESOURCES_ID = "mod_resources"; + private static Map modResourcePacks; + private static final Logger LOGGER = LogManager.getLogger(); - public static Optional getPackFor(String modId) { + public static Optional getPackFor(String modId) { return Optional.ofNullable(ModList.get().getModFileById(modId)).map(IModFileInfo::getFile).map(mf -> modResourcePacks.get(mf)); } - public static void loadResourcePacks(PackRepository resourcePacks, Function, ? extends RepositorySource> packFinder) { - modResourcePacks = ModList.get().getModFiles().stream() - .filter(mf -> mf.requiredLanguageLoaders().stream().noneMatch(ls -> ls.languageName().equals("minecraft"))) - .map(mf -> Pair.of(mf, createPackForMod(mf))) - .collect(Collectors.toMap(p -> p.getFirst().getFile(), Pair::getSecond, (u, v) -> { - throw new IllegalStateException(String.format(Locale.ENGLISH, "Duplicate key %s", u)); - }, LinkedHashMap::new)); + public static void loadResourcePacks(PackRepository resourcePacks, Function, RepositorySource> packFinder) { + findResourcePacks(); resourcePacks.addPackFinder(packFinder.apply(modResourcePacks)); } + private synchronized static void findResourcePacks() { + if (modResourcePacks == null) { + modResourcePacks = ModList.get().getModFiles().stream() + .filter(mf -> mf.requiredLanguageLoaders().stream().noneMatch(ls -> ls.languageName().equals("minecraft"))) + .map(mf -> Pair.of(mf, createPackForMod(mf))) + .collect(Collectors.toMap(p -> p.getFirst().getFile(), Pair::getSecond, (u, v) -> { + throw new IllegalStateException(String.format(Locale.ENGLISH, "Duplicate key %s", u)); + }, LinkedHashMap::new)); + } + } + + public static RepositorySource buildPackFinder(Map modResourcePacks, PackType packType) { + return packAcceptor -> packFinder(modResourcePacks, packAcceptor, packType); + } + + private static void packFinder(Map modResourcePacks, Consumer packAcceptor, PackType packType) { + var hiddenPacks = new ArrayList(); + for (Map.Entry e : modResourcePacks.entrySet()) { + IModInfo mod = e.getKey().getModInfos().get(0); + if ("minecraft".equals(mod.getModId())) continue; // skip the minecraft "mod" + final String name = "mod:" + mod.getModId(); + final String packName = mod.getOwningFile().getFile().getFileName(); + Pack modPack = Pack.readMetaAndCreate( + name, + Component.literal(packName.isEmpty() ? "[unnamed]" : packName), + false, + e.getValue(), + packType, + Pack.Position.BOTTOM, + PackSource.DEFAULT); + if (modPack == null) { + // Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong + ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey())); + continue; + } + LOGGER.debug(Logging.CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath()); + if ((packType == PackType.CLIENT_RESOURCES && mod.getOwningFile().showAsResourcePack()) || (packType == PackType.SERVER_DATA && mod.getOwningFile().showAsDataPack())) { + packAcceptor.accept(modPack); + } else { + hiddenPacks.add(modPack.hidden()); + } + } + + packAcceptor.accept(makePack(packType, hiddenPacks)); + } + + private static Pack makePack(PackType packType, ArrayList hiddenPacks) { + final String id = packType == PackType.CLIENT_RESOURCES ? MOD_RESOURCES_ID : MOD_DATA_ID; + final String name = packType == PackType.CLIENT_RESOURCES ? "Mod Resources" : "Mod Data"; + final String descriptionKey = packType == PackType.CLIENT_RESOURCES ? "fml.resources.modresources" : "fml.resources.moddata"; + return Pack.readMetaAndCreate(id, Component.literal(name), true, + new EmptyPackResources.EmptyResourcesSupplier(new PackMetadataSection(Component.translatable(descriptionKey, hiddenPacks.size()), + SharedConstants.getCurrentVersion().getPackVersion(packType)), false), + packType, Pack.Position.BOTTOM, PackSource.DEFAULT).withChildren(hiddenPacks); + } + @NotNull - public static PackResources createPackForMod(IModFileInfo mf) { - return new PathPackResources(mf.getFile().getFileName(), mf.getFile().getSecureJar().getRootPath(), true); + public static Pack.ResourcesSupplier createPackForMod(IModFileInfo mf) { + return new PathPackResources.PathResourcesSupplier(mf.getFile().getSecureJar().getRootPath(), true); } - public static List getPackNames() { - return ModList.get().applyForEachModFile(mf -> "mod:" + mf.getModInfos().get(0).getModId()).filter(n -> !n.equals("mod:minecraft")).collect(Collectors.toList()); + public static List getDataPackNames() { + List ids = new ArrayList<>(ModList.get().getModFiles().stream().filter(IModFileInfo::showAsDataPack) + .map(IModFileInfo::getFile) + .map(mf -> "mod:" + mf.getModInfos().get(0).getModId()).filter(n -> !n.equals("mod:minecraft")) + .toList()); + ids.add(MOD_DATA_ID); + return ids; } - public static Comparator> getSorter() { + public static Comparator> getSorter(PackType packType) { List order = new ArrayList<>(); order.add("vanilla"); - order.add("mod_resources"); + if (packType == PackType.CLIENT_RESOURCES) { + order.add(MOD_RESOURCES_ID); + } else { + order.add(MOD_DATA_ID); + } ModList.get().getModFiles().stream() .filter(mf -> mf.requiredLanguageLoaders().stream().noneMatch(ls -> ls.languageName().equals("minecraft"))) diff --git a/src/main/java/net/neoforged/neoforge/server/LanguageHook.java b/src/main/java/net/neoforged/neoforge/server/LanguageHook.java index e1b6288552d..31bd4272ae7 100644 --- a/src/main/java/net/neoforged/neoforge/server/LanguageHook.java +++ b/src/main/java/net/neoforged/neoforge/server/LanguageHook.java @@ -12,7 +12,12 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.regex.Pattern; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; diff --git a/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java b/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java index 0ae45dd23da..ee435b50720 100644 --- a/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java +++ b/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java @@ -6,15 +6,12 @@ package net.neoforged.neoforge.server; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import net.minecraft.core.Holder; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; @@ -26,20 +23,11 @@ import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.packs.PackResources; -import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.repository.BuiltInPackSource; -import net.minecraft.server.packs.repository.Pack; -import net.minecraft.server.packs.repository.PackSource; -import net.minecraft.server.packs.repository.RepositorySource; import net.minecraft.world.level.storage.LevelResource; -import net.neoforged.fml.Logging; -import net.neoforged.fml.ModLoader; -import net.neoforged.fml.ModLoadingStage; -import net.neoforged.fml.ModLoadingWarning; import net.neoforged.fml.config.ConfigTracker; import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.util.LogicalSidedProvider; import net.neoforged.neoforge.common.world.BiomeModifier; @@ -58,13 +46,10 @@ import net.neoforged.neoforge.registries.NeoForgeRegistries.Keys; import net.neoforged.neoforge.registries.RegistryManager; import net.neoforged.neoforge.server.permission.PermissionAPI; -import net.neoforged.neoforgespi.language.IModInfo; -import net.neoforged.neoforgespi.locating.IModFile; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; -import org.jetbrains.annotations.ApiStatus; public class ServerLifecycleHooks { private static final Logger LOGGER = LogManager.getLogger(); @@ -82,6 +67,18 @@ private static Path getServerConfigPath(final MinecraftServer server) { throw new RuntimeException(e); } } + final Path explanation = serverConfig.resolve("readme.txt"); + if (!Files.exists(explanation)) { + try { + Files.writeString(explanation, """ + Any server configs put in this folder will override the corresponding server config from /config/. + If the config being transferred is in a subfolder of the base config folder make sure to include that folder here in the path to the file you are overwriting. + For example if you are overwriting a config with the path /config/ExampleMod/config-server.toml, you would need to put it in serverconfig/ExampleMod/config-server.toml + """, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } return serverConfig; } @@ -89,7 +86,7 @@ public static void handleServerAboutToStart(final MinecraftServer server) { currentServer = server; // on the dedi server we need to force the stuff to setup properly LogicalSidedProvider.setServer(() -> server); - ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.SERVER, getServerConfigPath(server)); + ConfigTracker.INSTANCE.loadConfigs(ModConfig.Type.SERVER, FMLPaths.CONFIGDIR.get(), getServerConfigPath(server)); runModifiers(server); NeoForge.EVENT_BUS.post(new ServerAboutToStartEvent(server)); } @@ -130,7 +127,7 @@ public static void handleServerStopped(final MinecraftServer server) { latch.countDown(); exitLatch = null; } - ConfigTracker.INSTANCE.unloadConfigs(ModConfig.Type.SERVER, getServerConfigPath(server)); + ConfigTracker.INSTANCE.unloadConfigs(ModConfig.Type.SERVER); } public static MinecraftServer getCurrentServer() { @@ -185,27 +182,6 @@ public static void handleExit(int retVal) { System.exit(retVal); } - @ApiStatus.Internal - public static RepositorySource buildPackFinder(Map modResourcePacks) { - return packAcceptor -> serverPackFinder(modResourcePacks, packAcceptor); - } - - private static void serverPackFinder(Map modResourcePacks, Consumer packAcceptor) { - for (Entry e : modResourcePacks.entrySet()) { - IModInfo mod = e.getKey().getModInfos().get(0); - if (Objects.equals(mod.getModId(), "minecraft")) continue; // skip the minecraft "mod" - final String name = "mod:" + mod.getModId(); - final Pack modPack = Pack.readMetaAndCreate(name, Component.literal(e.getValue().packId()), false, BuiltInPackSource.fixedResources(e.getValue()), PackType.SERVER_DATA, Pack.Position.BOTTOM, PackSource.DEFAULT); - if (modPack == null) { - // Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong - ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey())); - continue; - } - LOGGER.debug(Logging.CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath()); - packAcceptor.accept(modPack); - } - } - private static void runModifiers(final MinecraftServer server) { final RegistryAccess registries = server.registryAccess(); diff --git a/src/main/java/net/neoforged/neoforge/server/command/ChunkGenWorker.java b/src/main/java/net/neoforged/neoforge/server/command/ChunkGenWorker.java deleted file mode 100644 index aee9adba935..00000000000 --- a/src/main/java/net/neoforged/neoforge/server/command/ChunkGenWorker.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.server.command; - -import java.util.ArrayDeque; -import java.util.Queue; -import net.minecraft.commands.CommandSourceStack; -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkStatus; -import net.neoforged.neoforge.common.WorldWorkerManager; - -public class ChunkGenWorker implements WorldWorkerManager.IWorker { - private final CommandSourceStack listener; - protected final BlockPos start; - protected final int total; - private final ServerLevel dim; - private final Queue queue; - private final int notificationFrequency; - private int lastNotification = 0; - private long lastNotifcationTime = 0; - private int genned = 0; - private Boolean keepingLoaded; - - public ChunkGenWorker(CommandSourceStack listener, BlockPos start, int total, ServerLevel dim, int interval) { - this.listener = listener; - this.start = start; - this.total = total; - this.dim = dim; - this.queue = buildQueue(); - this.notificationFrequency = interval != -1 ? interval : Math.max(total / 20, 100); //Every 5% or every 100, whichever is more. - this.lastNotifcationTime = System.currentTimeMillis(); //We also notify at least once every 60 seconds, to show we haven't froze. - } - - protected Queue buildQueue() { - Queue ret = new ArrayDeque(); - ret.add(start); - - //This *should* spiral outwards, starting on right side, down, left, up, right, but hey we'll see! - int radius = 1; - while (ret.size() < total) { - for (int q = -radius + 1; q <= radius && ret.size() < total; q++) - ret.add(start.offset(radius, 0, q)); - - for (int q = radius - 1; q >= -radius && ret.size() < total; q--) - ret.add(start.offset(q, 0, radius)); - - for (int q = radius - 1; q >= -radius && ret.size() < total; q--) - ret.add(start.offset(-radius, 0, q)); - - for (int q = -radius + 1; q <= radius && ret.size() < total; q++) - ret.add(start.offset(q, 0, -radius)); - - radius++; - } - return ret; - } - - public MutableComponent getStartMessage(CommandSourceStack sender) { - return Component.translatable("commands.neoforge.gen.start", total, start.getX(), start.getZ(), dim); - } - - @Override - public boolean hasWork() { - return queue.size() > 0; - } - - @Override - public boolean doWork() { - /* TODO: Check how many things are pending save, and slow down world gen if to many - AnvilChunkLoader loader = dim.getChunkProvider().chunkLoader instanceof AnvilChunkLoader ? (AnvilChunkLoader)world.getChunkProvider().chunkLoader : null; - if (loader != null && loader.getPendingSaveCount() > 100) - { - - if (lastNotifcationTime < System.currentTimeMillis() - 10*1000) - { - listener.sendFeedback(new TranslationTextComponent("commands.neoforge.gen.progress", total - queue.size(), total), true); - lastNotifcationTime = System.currentTimeMillis(); - } - return false; - } - */ - - BlockPos next = queue.poll(); - - if (next != null) { - // While we work we don't want to cause world load spam so pause unloading the world. - /* TODO: Readd if/when we introduce world unloading, or get Mojang to do it. - if (keepingLoaded == null) - keepingLoaded = DimensionManager.keepLoaded(dim, true); - */ - - if (++lastNotification >= notificationFrequency || lastNotifcationTime < System.currentTimeMillis() - 60 * 1000) { - listener.sendSuccess(() -> Component.translatable("commands.neoforge.gen.progress", total - queue.size(), total), true); - lastNotification = 0; - lastNotifcationTime = System.currentTimeMillis(); - } - - int x = next.getX(); - int z = next.getZ(); - - if (!dim.hasChunk(x, z)) { //Chunk is unloaded - ChunkAccess chunk = dim.getChunk(x, z, ChunkStatus.EMPTY, true); - if (!chunk.getStatus().isOrAfter(ChunkStatus.FULL)) { - chunk = dim.getChunk(x, z, ChunkStatus.FULL); - genned++; //There isn't a way to check if the chunk is actually created just if it was loaded - } - } - } - - if (queue.size() == 0) { - listener.sendSuccess(() -> Component.translatable("commands.neoforge.gen.complete", genned, total, dim.dimension().location()), true); - /* TODO: Readd if/when we introduce world unloading, or get Mojang to do it. - if (keepingLoaded != null && !keepingLoaded) - DimensionManager.keepLoaded(dim, false); - */ - return false; - } - return true; - } -} diff --git a/src/main/java/net/neoforged/neoforge/server/command/GenerateCommand.java b/src/main/java/net/neoforged/neoforge/server/command/GenerateCommand.java index 23b8fcbce0b..2e5dc3725bd 100644 --- a/src/main/java/net/neoforged/neoforge/server/command/GenerateCommand.java +++ b/src/main/java/net/neoforged/neoforge/server/command/GenerateCommand.java @@ -5,41 +5,149 @@ package net.neoforged.neoforge.server.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; -import net.minecraft.commands.arguments.DimensionArgument; import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.neoforged.neoforge.common.WorldWorkerManager; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.neoforged.neoforge.server.command.generation.GenerationBar; +import net.neoforged.neoforge.server.command.generation.GenerationTask; +/** + * Special thanks to Jasmine and Gegy for allowing us to use their pregenerator mod as a model to use in NeoForge! + * Original code: https://github.com/jaskarth/fabric-chunkpregenerator + */ class GenerateCommand { + private static GenerationTask activeTask; + private static GenerationBar generationBar; + static ArgumentBuilder register() { - return Commands.literal("generate") - .requires(cs -> cs.hasPermission(4)) //permission + LiteralArgumentBuilder builder = Commands.literal("generate").requires(cs -> cs.hasPermission(4)); //permission + + builder.then(Commands.literal("start") .then(Commands.argument("pos", BlockPosArgument.blockPos()) - .then(Commands.argument("count", IntegerArgumentType.integer(1)) - .then(Commands.argument("dim", DimensionArgument.dimension()) - .then(Commands.argument("interval", IntegerArgumentType.integer()) - .executes(ctx -> execute(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "count"), DimensionArgument.getDimension(ctx, "dim"), getInt(ctx, "interval")))) - .executes(ctx -> execute(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "count"), DimensionArgument.getDimension(ctx, "dim"), -1))) - .executes(ctx -> execute(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "count"), ctx.getSource().getLevel(), -1)))); + .then(Commands.argument("chunkRadius", IntegerArgumentType.integer(1, 1250)) // 20000 block radius limit + .then(Commands.argument("progressBar", BoolArgumentType.bool()) + .executes(ctx -> executeGeneration(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "chunkRadius"), getBool(ctx, "progressBar")))) + .executes(ctx -> executeGeneration(ctx.getSource(), BlockPosArgument.getSpawnablePos(ctx, "pos"), getInt(ctx, "chunkRadius"), true))))); + + builder.then(Commands.literal("stop") + .executes(ctx -> stopGeneration(ctx.getSource()))); + + builder.then(Commands.literal("status") + .executes(ctx -> getGenerationStatus(ctx.getSource()))); + + builder.then(Commands.literal("help") + .executes(ctx -> getGenerationHelp(ctx.getSource()))); + + return builder; } private static int getInt(CommandContext ctx, String name) { return IntegerArgumentType.getInteger(ctx, name); } - private static int execute(CommandSourceStack source, BlockPos pos, int count, ServerLevel dim, int interval) { - BlockPos chunkpos = new BlockPos(pos.getX() >> 4, 0, pos.getZ() >> 4); + private static boolean getBool(CommandContext ctx, String name) { + return BoolArgumentType.getBool(ctx, name); + } + + private static int executeGeneration(CommandSourceStack source, BlockPos pos, int chunkRadius, boolean progressBar) { + if (activeTask != null) { + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.already_running"), true); + return Command.SINGLE_SUCCESS; + } + + ChunkPos origin = new ChunkPos(pos); + + activeTask = new GenerationTask(source.getLevel(), origin.x, origin.z, chunkRadius); + int diameter = chunkRadius * 2 + 1; + + if (progressBar) { + generationBar = new GenerationBar(); + + if (source.getEntity() instanceof ServerPlayer) { + generationBar.addPlayer(source.getPlayer()); + } + } + + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.started", + activeTask.getTotalCount(), diameter, diameter, diameter * 16, diameter * 16), true); + + activeTask.run(createPregenListener(source)); + + return Command.SINGLE_SUCCESS; + } + + private static int stopGeneration(CommandSourceStack source) { + if (activeTask != null) { + activeTask.stop(); + + int count = activeTask.getOkCount() + activeTask.getErrorCount() + activeTask.getSkippedCount(); + int total = activeTask.getTotalCount(); + + double percent = (double) count / total * 100.0; + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.stopped", count, total, percent), true); + + generationBar.close(); + generationBar = null; + activeTask = null; + } else { + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.not_running"), false); + } + + return Command.SINGLE_SUCCESS; + } + + private static int getGenerationStatus(CommandSourceStack source) { + if (activeTask != null) { + int count = activeTask.getOkCount() + activeTask.getErrorCount() + activeTask.getSkippedCount(); + int total = activeTask.getTotalCount(); + + double percent = (double) count / total * 100.0; + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.status", count, total, percent), true); + } else { + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.not_running"), false); + } + + return Command.SINGLE_SUCCESS; + } + + private static int getGenerationHelp(CommandSourceStack source) { + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.help_line"), false); + return Command.SINGLE_SUCCESS; + } + + private static GenerationTask.Listener createPregenListener(CommandSourceStack source) { + return new GenerationTask.Listener() { + @Override + public void update(int ok, int error, int skipped, int total) { + if (generationBar != null) { + generationBar.update(ok, error, skipped, total); + } + } + + @Override + public void complete(int error) { + source.sendSuccess(() -> Component.translatable("commands.neoforge.chunkgen.success"), true); - ChunkGenWorker worker = new ChunkGenWorker(source, chunkpos, count, dim, interval); - source.sendSuccess(() -> worker.getStartMessage(source), true); - WorldWorkerManager.addWorker(worker); + if (error > 0) { + source.sendFailure(Component.translatable("commands.neoforge.chunkgen.error")); + } - return 0; + if (generationBar != null) { + generationBar.close(); + generationBar = null; + } + activeTask = null; + } + }; } } diff --git a/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java b/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java index 06fd55f35a1..2dd297cc4c1 100644 --- a/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java +++ b/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java @@ -31,7 +31,11 @@ import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; -import net.minecraft.network.chat.*; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentUtils; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; diff --git a/src/main/java/net/neoforged/neoforge/server/command/generation/CoarseOnionIterator.java b/src/main/java/net/neoforged/neoforge/server/command/generation/CoarseOnionIterator.java new file mode 100644 index 00000000000..57c7585cd04 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/server/command/generation/CoarseOnionIterator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.server.command.generation; + +import com.google.common.collect.AbstractIterator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import net.minecraft.world.level.ChunkPos; + +/** + * Special thanks to Jasmine and Gegy for allowing us to use their pregenerator mod as a model to use in NeoForge! + * Original code: https://github.com/jaskarth/fabric-chunkpregenerator + */ +public class CoarseOnionIterator extends AbstractIterator { + private final int radius; + private final int cellSize; + + private final OnionIterator cells; + private CellIterator cell; + + public CoarseOnionIterator(int radius, int cellSize) { + this.radius = radius; + this.cellSize = cellSize; + + this.cells = new OnionIterator((radius + cellSize - 1) / cellSize); + } + + @Override + protected ChunkPos computeNext() { + OnionIterator cells = this.cells; + CellIterator cell = this.cell; + while (cell == null || !cell.hasNext()) { + if (!cells.hasNext()) { + return this.endOfData(); + } + + ChunkPos cellPos = cells.next(); + this.cell = cell = this.createCellIterator(cellPos); + } + + return cell.next(); + } + + private CellIterator createCellIterator(ChunkPos pos) { + int size = this.cellSize; + int radius = this.radius; + + int x0 = pos.x * size; + int z0 = pos.z * size; + int x1 = x0 + size - 1; + int z1 = z0 + size - 1; + return new CellIterator( + Math.max(x0, -radius), Math.max(z0, -radius), + Math.min(x1, radius), Math.min(z1, radius)); + } + + private static final class CellIterator implements Iterator { + private final int x0; + private final int x1; + private final int z1; + + private int x; + private int z; + + private CellIterator(int x0, int z0, int x1, int z1) { + this.x = x0; + this.z = z0; + this.x0 = x0; + this.x1 = x1; + this.z1 = z1; + } + + @Override + public boolean hasNext() { + return this.z <= this.z1; + } + + @Override + public ChunkPos next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + + ChunkPos pos = new ChunkPos(this.x, this.z); + if (this.x++ >= this.x1) { + this.x = this.x0; + this.z++; + } + + return pos; + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/server/command/generation/GenerationBar.java b/src/main/java/net/neoforged/neoforge/server/command/generation/GenerationBar.java new file mode 100644 index 00000000000..73c7a221565 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/server/command/generation/GenerationBar.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.server.command.generation; + +import java.text.DecimalFormat; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.server.level.ServerBossEvent; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.BossEvent; + +/** + * Special thanks to Jasmine and Gegy for allowing us to use their pregenerator mod as a model to use in NeoForge! + * Original code: https://github.com/jaskarth/fabric-chunkpregenerator + */ +public class GenerationBar implements AutoCloseable { + private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#.00"); + + private final ServerBossEvent bar; + + public GenerationBar() { + this.bar = new ServerBossEvent(Component.translatable("commands.neoforge.chunkgen.progress_bar_title"), BossEvent.BossBarColor.YELLOW, BossEvent.BossBarOverlay.PROGRESS); + this.bar.setPlayBossMusic(false); + this.bar.setCreateWorldFog(false); + this.bar.setDarkenScreen(false); + } + + public void update(int ok, int error, int skipped, int total) { + int count = ok + error + skipped; + + float percent = (float) count / total; + + MutableComponent title = Component.translatable("commands.neoforge.chunkgen.progress_bar_progress", total) + .append(Component.translatable(PERCENT_FORMAT.format(percent * 100.0F) + "%") + .setStyle(Style.EMPTY.withColor(ChatFormatting.GOLD))); + + if (error > 0) { + title = title.append(Component.translatable("commands.neoforge.chunkgen.progress_bar_errors") + .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + } + + this.bar.setName(title); + this.bar.setProgress(percent); + } + + public void addPlayer(ServerPlayer player) { + this.bar.addPlayer(player); + } + + @Override + public void close() { + this.bar.setVisible(false); + this.bar.removeAllPlayers(); + } +} diff --git a/src/main/java/net/neoforged/neoforge/server/command/generation/GenerationTask.java b/src/main/java/net/neoforged/neoforge/server/command/generation/GenerationTask.java new file mode 100644 index 00000000000..733cf5cd883 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/server/command/generation/GenerationTask.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.server.command.generation; + +import com.mojang.datafixers.util.Either; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.visitors.CollectFields; +import net.minecraft.nbt.visitors.FieldSelector; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Special thanks to Jasmine and Gegy for allowing us to use their pregenerator mod as a model to use in NeoForge! + * Original code: https://github.com/jaskarth/fabric-chunkpregenerator + */ +public class GenerationTask { + private static final Logger LOGGER = LogManager.getLogger(); + private static final int BATCH_SIZE = 32; + private static final int QUEUE_THRESHOLD = 8; + private static final int COARSE_CELL_SIZE = 4; + + private final MinecraftServer server; + private final ServerChunkCache chunkSource; + + private final Iterator iterator; + private final int x; + private final int z; + private final int radius; + + private final int totalCount; + + private final Object queueLock = new Object(); + + private final AtomicInteger queuedCount = new AtomicInteger(); + private final AtomicInteger okCount = new AtomicInteger(); + private final AtomicInteger errorCount = new AtomicInteger(); + private final AtomicInteger skippedCount = new AtomicInteger(); + + private volatile Listener listener; + private volatile boolean stopped; + + public static final TicketType NEOFORGE_GENERATE_FORCED = TicketType.create("neoforge_generate_forced", Comparator.comparingLong(ChunkPos::toLong)); + + public GenerationTask(ServerLevel serverLevel, int x, int z, int radius) { + this.server = serverLevel.getServer(); + this.chunkSource = serverLevel.getChunkSource(); + + this.iterator = new CoarseOnionIterator(radius, COARSE_CELL_SIZE); + this.x = x; + this.z = z; + this.radius = radius; + + int diameter = radius * 2 + 1; + this.totalCount = diameter * diameter; + } + + public int getOkCount() { + return this.okCount.get(); + } + + public int getErrorCount() { + return this.errorCount.get(); + } + + public int getSkippedCount() { + return this.skippedCount.get(); + } + + public int getTotalCount() { + return this.totalCount; + } + + public void run(Listener listener) { + if (this.listener != null) { + throw new IllegalStateException("already running!"); + } + + this.listener = listener; + + this.server.submit(() -> CompletableFuture.runAsync(this::tryEnqueueTasks)); + } + + public void stop() { + synchronized (this.queueLock) { + this.stopped = true; + this.listener = null; + } + } + + private void tryEnqueueTasks() { + synchronized (this.queueLock) { + if (this.stopped) { + return; + } + + int enqueueCount = BATCH_SIZE - this.queuedCount.get(); + if (enqueueCount <= 0) { + return; + } + + LongList chunks = this.collectChunks(enqueueCount); + if (chunks.isEmpty()) { + this.listener.complete(this.errorCount.get()); + this.stopped = true; + return; + } + + this.queuedCount.getAndAdd(chunks.size()); + this.server.submit(() -> this.enqueueChunks(chunks)); + } + } + + private void enqueueChunks(LongList chunks) { + for (int i = 0; i < chunks.size(); i++) { + long chunk = chunks.getLong(i); + this.acquireChunk(chunk); + } + + // tick the chunk manager to force the ChunkHolders to be created + this.chunkSource.tick(() -> false, true); + + ChunkMap chunkMap = this.chunkSource.chunkMap; + + for (int i = 0; i < chunks.size(); i++) { + long chunkLongPos = chunks.getLong(i); + + ChunkHolder holder = chunkMap.getVisibleChunkIfPresent(chunkLongPos); + if (holder == null) { + LOGGER.warn("Added ticket for chunk but it was not added! ({}; {})", ChunkPos.getX(chunkLongPos), ChunkPos.getZ(chunkLongPos)); + this.acceptChunkResult(chunkLongPos, ChunkHolder.UNLOADED_CHUNK); + continue; + } + + holder.getOrScheduleFuture(ChunkStatus.FULL, chunkMap).whenComplete((result, throwable) -> { + if (throwable == null) { + this.acceptChunkResult(chunkLongPos, result); + } else { + LOGGER.warn("Encountered unexpected error while generating chunk", throwable); + this.acceptChunkResult(chunkLongPos, ChunkHolder.UNLOADED_CHUNK); + } + }); + } + } + + private void acceptChunkResult(long chunk, Either result) { + this.server.submit(() -> this.releaseChunk(chunk)); + + if (result.left().isPresent()) { + this.okCount.getAndIncrement(); + } else { + this.errorCount.getAndIncrement(); + } + + this.listener.update(this.okCount.get(), this.errorCount.get(), this.skippedCount.get(), this.totalCount); + + int queuedCount = this.queuedCount.decrementAndGet(); + if (queuedCount <= QUEUE_THRESHOLD) { + this.tryEnqueueTasks(); + } + } + + private LongList collectChunks(int count) { + LongList chunks = new LongArrayList(count); + + Iterator iterator = this.iterator; + int i = 0; + while (i < count && iterator.hasNext()) { + ChunkPos chunkPosInLocalSpace = iterator.next(); + if (Math.abs(chunkPosInLocalSpace.x) <= this.radius && Math.abs(chunkPosInLocalSpace.z) <= this.radius) { + if (isChunkFullyGenerated(chunkPosInLocalSpace)) { + this.skippedCount.incrementAndGet(); + this.listener.update(this.okCount.get(), this.errorCount.get(), this.skippedCount.get(), this.totalCount); + continue; + } + + chunks.add(ChunkPos.asLong(chunkPosInLocalSpace.x + this.x, chunkPosInLocalSpace.z + this.z)); + i++; + } + } + + return chunks; + } + + private void acquireChunk(long chunk) { + ChunkPos pos = new ChunkPos(chunk); + this.chunkSource.addRegionTicket(NEOFORGE_GENERATE_FORCED, pos, 0, pos); + } + + private void releaseChunk(long chunk) { + ChunkPos pos = new ChunkPos(chunk); + this.chunkSource.addRegionTicket(NEOFORGE_GENERATE_FORCED, pos, 0, pos); + } + + private boolean isChunkFullyGenerated(ChunkPos chunkPosInLocalSpace) { + ChunkPos chunkPosInWorldSpace = new ChunkPos(chunkPosInLocalSpace.x + this.x, chunkPosInLocalSpace.z + this.z); + CollectFields collectFields = new CollectFields(new FieldSelector(StringTag.TYPE, "Status")); + this.chunkSource.chunkMap.chunkScanner().scanChunk(chunkPosInWorldSpace, collectFields).join(); + + if (collectFields.getResult() instanceof CompoundTag compoundTag) { + return compoundTag.getString("Status").equals("minecraft:full"); + } + + return false; + } + + public interface Listener { + void update(int ok, int error, int skipped, int total); + + void complete(int error); + } +} diff --git a/src/main/java/net/neoforged/neoforge/server/command/generation/OnionIterator.java b/src/main/java/net/neoforged/neoforge/server/command/generation/OnionIterator.java new file mode 100644 index 00000000000..d416d5291a3 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/server/command/generation/OnionIterator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.server.command.generation; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import net.minecraft.world.level.ChunkPos; + +/** + * Special thanks to Jasmine and Gegy for allowing us to use their pregenerator mod as a model to use in NeoForge! + * Original code: https://github.com/jaskarth/fabric-chunkpregenerator + */ +public class OnionIterator implements Iterator { + private static final byte EAST = 0; + private static final byte SOUTH = 1; + private static final byte WEST = 2; + private static final byte NORTH = 3; + private static final byte STOP = 4; + + private final int radius; + + private int x; + private int z; + + private int distance = 0; + private byte state = EAST; + + public OnionIterator(int radius) { + this.radius = radius; + } + + @Override + public ChunkPos next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + + ChunkPos pos = new ChunkPos(this.x, this.z); + + switch (this.state) { + case EAST: + if (++this.x >= this.distance) { + this.state = SOUTH; + if (this.distance > this.radius) { + this.state = STOP; + } + } + break; + case SOUTH: + if (++this.z >= this.distance) { + this.state = WEST; + } + break; + case WEST: + if (--this.x <= -this.distance) { + this.state = NORTH; + } + break; + case NORTH: + if (--this.z <= -this.distance) { + this.state = EAST; + this.distance++; + } + break; + } + + if (this.distance == 0) { + this.distance++; + } + + return pos; + } + + @Override + public boolean hasNext() { + return this.state != STOP; + } +} diff --git a/src/main/java/net/neoforged/neoforge/server/loading/ServerModLoader.java b/src/main/java/net/neoforged/neoforge/server/loading/ServerModLoader.java index 314ed6a97e7..6e498284cc7 100644 --- a/src/main/java/net/neoforged/neoforge/server/loading/ServerModLoader.java +++ b/src/main/java/net/neoforged/neoforge/server/loading/ServerModLoader.java @@ -7,7 +7,11 @@ import java.io.File; import java.util.List; -import net.neoforged.fml.*; +import net.neoforged.fml.LoadingFailedException; +import net.neoforged.fml.Logging; +import net.neoforged.fml.ModLoader; +import net.neoforged.fml.ModLoadingWarning; +import net.neoforged.fml.ModWorkManager; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.util.LogicalSidedProvider; import net.neoforged.neoforge.logging.CrashReportExtender; diff --git a/src/main/java/net/neoforged/neoforge/server/permission/events/PermissionGatherEvent.java b/src/main/java/net/neoforged/neoforge/server/permission/events/PermissionGatherEvent.java index 0ca3fa21f46..37823e8c470 100644 --- a/src/main/java/net/neoforged/neoforge/server/permission/events/PermissionGatherEvent.java +++ b/src/main/java/net/neoforged/neoforge/server/permission/events/PermissionGatherEvent.java @@ -6,7 +6,12 @@ package net.neoforged.neoforge.server.permission.events; import com.google.common.base.Preconditions; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import net.minecraft.resources.ResourceLocation; import net.neoforged.bus.api.Event; import net.neoforged.neoforge.server.permission.handler.DefaultPermissionHandler; diff --git a/src/main/java/net/neoforged/neoforge/server/permission/handler/DefaultPermissionHandler.java b/src/main/java/net/neoforged/neoforge/server/permission/handler/DefaultPermissionHandler.java index 192c143b7da..ea55fbc330e 100644 --- a/src/main/java/net/neoforged/neoforge/server/permission/handler/DefaultPermissionHandler.java +++ b/src/main/java/net/neoforged/neoforge/server/permission/handler/DefaultPermissionHandler.java @@ -5,7 +5,11 @@ package net.neoforged.neoforge.server.permission.handler; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContext; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index ece5a39c146..efa0d240868 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -97,6 +97,8 @@ protected net.minecraft.client.gui.components.AbstractButton SPRITES protected net.minecraft.client.gui.components.AbstractSelectionList$Entry list # list protected net.minecraft.client.gui.components.AbstractSliderButton getSprite()Lnet/minecraft/resources/ResourceLocation; # getSprite protected net.minecraft.client.gui.components.AbstractSliderButton getHandleSprite()Lnet/minecraft/resources/ResourceLocation; # getHandleSprite +public net.minecraft.client.gui.components.AbstractWidget renderScrollingString(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIII)V +public net.minecraft.client.gui.components.AbstractWidget renderScrollingString(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIIII)V protected net.minecraft.client.gui.components.DebugScreenOverlay renderProfilerChart # renderProfilerChart protected net.minecraft.client.gui.components.DebugScreenOverlay renderNetworkCharts # renderNetworkCharts protected net.minecraft.client.gui.components.DebugScreenOverlay renderFpsCharts # renderFpsCharts @@ -254,6 +256,7 @@ public net.minecraft.resources.ResourceLocation isValidNamespace(Ljava/lang/Stri protected net.minecraft.server.MinecraftServer nextTickTimeNanos # nextTickTimeNanos public net.minecraft.server.MinecraftServer$ReloadableResources public net.minecraft.server.dedicated.DedicatedServer consoleInput # consoleInput +public net.minecraft.server.level.ChunkMap getVisibleChunkIfPresent(J)Lnet/minecraft/server/level/ChunkHolder; public net.minecraft.server.level.ServerChunkCache level # level public net.minecraft.server.level.ServerLevel getEntities()Lnet/minecraft/world/level/entity/LevelEntityGetter; # getEntities public net.minecraft.server.level.ServerPlayer containerCounter # containerCounter diff --git a/src/main/resources/assets/neoforge/lang/en_us.json b/src/main/resources/assets/neoforge/lang/en_us.json index 82f2e254d53..577ebd624a4 100644 --- a/src/main/resources/assets/neoforge/lang/en_us.json +++ b/src/main/resources/assets/neoforge/lang/en_us.json @@ -65,8 +65,16 @@ "fml.modloading.uncaughterror":"An uncaught parallel processing error has occurred.\n\u00a77{2,exc,msg}", "fml.modloading.errorduringevent":"{0,modinfo,name} ({0,modinfo,id}) encountered an error during the {1,lower} event phase\n\u00a77{2,exc,msg}", "fml.modloading.failedtoloadforge": "Failed to load NeoForge", + "fml.modloading.missingdependency": "Mod \u00a7e{4}\u00a7r requires \u00a76{3}\u00a7r \u00a7o{5,vr}\u00a7r\n\u00a77Currently, \u00a76{3}\u00a7r\u00a77 is \u00a7o{6,i18n,fml.messages.artifactversion.ornotinstalled}", "fml.modloading.missingdependency.optional": "Mod \u00a7e{4}\u00a7r only supports \u00a73{3}\u00a7r \u00a7o{5,vr}\u00a7r\n\u00a77Currently, \u00a73{3}\u00a7r\u00a77 is \u00a7o{6}", + "fml.modloading.incompatiblemod": "Mod \u00a7e{4}\u00a7r is §cincompatible§r with \u00a73{3}\u00a7r \u00a7o{5,vr}\u00a7r\n\u00a77Currently, \u00a73{3}\u00a7r\u00a77 is \u00a7o{6}§r\n§7The reason is:§r §o{7,i18ntranslate}§r", + "fml.modloading.discouragedmod": "Mod \u00a7e{3}\u00a7r §ddiscourages§r the use of \u00a73{2}\u00a7r \u00a7o{4,vr}\u00a7r\n\u00a77Currently, \u00a73{2}\u00a7r\u00a77 is \u00a7o{5}§r\n§7The reason is:§r §o{6,i18ntranslate}§r", + "fml.modloading.discouragedmod.proceed": "Proceed at your own risk", + + "fml.modloading.incompatiblemod.noreason": "§eNo reason provided§r", + "fml.modloading.discouragedmod.noreason": "§eNo reason provided§r", + "fml.dependencyloading.conflictingdependencies": "Some mods have requested conflicting versions of: \u00a76{3}\u00a7r. Requested by: \u00a7e{4}\u00a7r.", "fml.dependencyloading.mismatchedcontaineddependencies": "Some mods have agreed upon an acceptable version range for : \u00a76{3}\u00a7r, but no jar was provided which matched the range. Requested by: \u00a7e{4}\u00a7r.", "fml.modloading.cycle": "Detected a mod dependency cycle: {0}", @@ -81,6 +89,7 @@ "fml.modloading.brokenresources": "File {2} failed to load a valid ResourcePackInfo", "fml.modloading.missinglicense": "Missing License Information in file {3}", "fml.resources.modresources": "Resources for {0} mod files", + "fml.resources.moddata": "Data for {0} mod files", "fml.messages.artifactversion.ornotinstalled":"{0,ornull,fml.messages.artifactversion.notinstalled}", "fml.messages.artifactversion":"{0,ornull,fml.messages.artifactversion.none}", @@ -104,11 +113,6 @@ "commands.neoforge.entity.list.none": "No entities found.", "commands.neoforge.entity.list.single.header": "Entity: {0} Total: {1}", "commands.neoforge.entity.list.multiple.header": "Total: {0}", - "commands.neoforge.gen.usage": "Use /neoforge gen [dimension] [interval]", - "commands.neoforge.gen.dim_fail": "Failed to load world for dimension {0}, Task terminated.", - "commands.neoforge.gen.progress": "Generation Progress: {0}/{1}", - "commands.neoforge.gen.complete": "Finished generating {0} new chunks (out of {1}) for dimension {2}.", - "commands.neoforge.gen.start": "Starting to generate {0} chunks in a spiral around {1}, {2} in dimension {3}.", "commands.neoforge.setdim.invalid.entity": "The entity selected ({0}) is not valid.", "commands.neoforge.setdim.invalid.dim": "The dimension ID specified ({0}) is not valid.", "commands.neoforge.setdim.invalid.nochange": "The entity selected ({0}) is already in the dimension specified ({1}).", @@ -137,6 +141,17 @@ "commands.neoforge.tags.containing_tag_count": "Containing tags: %s", "commands.neoforge.tags.element": "%s : %s", "commands.neoforge.tags.page_info": "%s ", + "commands.neoforge.chunkgen.progress_bar_title": "Generating chunks...", + "commands.neoforge.chunkgen.progress_bar_progress": "Generating {0} chunks - ", + "commands.neoforge.chunkgen.progress_bar_errors": "({0} errors!)", + "commands.neoforge.chunkgen.already_running": "Generation already running. Please execute '/neoforge generate stop' first and then you can start a new generation.", + "commands.neoforge.chunkgen.started": "Generating {0} chunks, in an area of {1}x{2} chunks ({3}x{4} blocks).", + "commands.neoforge.chunkgen.success": "Generation Done!", + "commands.neoforge.chunkgen.error": "Generation experienced {0} errors! Check the log for more information.", + "commands.neoforge.chunkgen.stopped": "Generation stopped! {0} out of {1} chunks generated. ({2}%)", + "commands.neoforge.chunkgen.status": "Generation status! {0} out of {1} chunks generated. ({2}%)", + "commands.neoforge.chunkgen.not_running": "No pregeneration currently running. Run `/neoforge generate help` to see commands for starting generation.", + "commands.neoforge.chunkgen.help_line": "§2/neoforge generate start [progressBar] §r§f- Generates a square centered on the given position that is chunkRadius * 2 on each side.\n§2/neoforge generate stop §r§f- Stops the current generation and displays progress that it had completed.\n§2/neoforge generate status §r- Displays the progress completed for the currently running generation.\n§2/neoforge generate help §r- Displays this message.\nGeneral tips: If running from a server console, you can run generate in different dimensions by using /execute in neoforge generate...", "commands.config.getwithtype": "Config for %s of type %s found at %s", "commands.config.noconfig": "Config for %s of type %s not found", diff --git a/testframework/build.gradle b/testframework/build.gradle index 77a836be65d..0caa8fd3f6d 100644 --- a/testframework/build.gradle +++ b/testframework/build.gradle @@ -36,5 +36,14 @@ spotless { toggleOffOn() eclipse().configFile rootProject.file('codeformat/formatter-config.xml') importOrder() + + // courtesy of diffplug/spotless#240 + // https://github.com/diffplug/spotless/issues/240#issuecomment-385206606 + custom 'noWildcardImports', { String fileContents -> + if (fileContents.contains('*;\n')) { + throw new GradleException('No wildcard imports are allowed!') + } + } + bumpThisNumberIfACustomStepChanges(1) } -} \ No newline at end of file +} diff --git a/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java b/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java index ca7aeb1eb70..61a02e0838c 100644 --- a/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java +++ b/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java @@ -25,8 +25,10 @@ import net.minecraft.server.network.CommonListenerCookie; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; +import net.minecraft.world.effect.MobEffect; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; @@ -200,4 +202,12 @@ public void assertContainerContains(int x, int y, int z, Item item) { public void pulseRedstone(int x, int y, int z, long delay) { pulseRedstone(new BlockPos(x, y, z), delay); } + + public void assertMobEffectPresent(E entity, MobEffect effect, String testName) { + assertEntityProperty(entity, e -> e.hasEffect(effect), testName); + } + + public void assertMobEffectAbsent(E entity, MobEffect effect, String testName) { + assertEntityProperty(entity, e -> !e.hasEffect(effect), testName); + } } diff --git a/tests/build.gradle b/tests/build.gradle index f9c6dcec1ed..3ae4763568e 100644 --- a/tests/build.gradle +++ b/tests/build.gradle @@ -113,5 +113,14 @@ spotless { toggleOffOn() eclipse().configFile rootProject.file('codeformat/formatter-config.xml') importOrder() + + // courtesy of diffplug/spotless#240 + // https://github.com/diffplug/spotless/issues/240#issuecomment-385206606 + custom 'noWildcardImports', { String fileContents -> + if (fileContents.contains('*;\n')) { + throw new GradleException('No wildcard imports are allowed!') + } + } + bumpThisNumberIfACustomStepChanges(1) } } diff --git a/tests/src/generated/resources/data/neoforge/loot_tables/test_loot_table.json b/tests/src/generated/resources/data/neoforge/loot_tables/test_loot_table.json new file mode 100644 index 00000000000..177dba9b574 --- /dev/null +++ b/tests/src/generated/resources/data/neoforge/loot_tables/test_loot_table.json @@ -0,0 +1,26 @@ +{ + "pools": [ + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "name": "minecraft:diamond" + } + ], + "name": "custom_name", + "rolls": 1.0 + }, + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "name": "minecraft:gold_nugget" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "neoforge:test_loot_table" +} \ No newline at end of file diff --git a/tests/src/generated/resources/pack.mcmeta b/tests/src/generated/resources/pack.mcmeta index 94ca988b2b5..c5aae1065af 100644 --- a/tests/src/generated/resources/pack.mcmeta +++ b/tests/src/generated/resources/pack.mcmeta @@ -1,12 +1,21 @@ { + "overlays": { + "entries": [ + { + "directory": "pack_overlays_test", + "formats": [ + 0, + 2147483647 + ] + } + ] + }, "pack": { "description": "NeoForge tests resource pack", - "neoforge": { - "versions": { - "client_resources": 22, - "server_data": 26 - } - }, - "pack_format": 22 + "pack_format": 22, + "supported_formats": [ + 0, + 2147483647 + ] } } \ No newline at end of file diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/capabilities/VanillaHandlersTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/capabilities/VanillaHandlersTests.java new file mode 100644 index 00000000000..9d3aeb124b7 --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/capabilities/VanillaHandlersTests.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug.capabilities; + +import static net.neoforged.neoforge.fluids.capability.IFluidHandler.FluidAction.EXECUTE; +import static net.neoforged.neoforge.fluids.capability.IFluidHandler.FluidAction.SIMULATE; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LayeredCauldronBlock; +import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.capabilities.BlockCapabilityCache; +import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.gametest.ExtendedGameTestHelper; +import org.apache.commons.lang3.mutable.MutableInt; + +@ForEachTest(groups = "capabilities.vanillahandlers") +public class VanillaHandlersTests { + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests that composter capabilities get invalidated correctly") + public static void testComposterInvalidation(ExtendedGameTestHelper helper) { + var composterPos = new BlockPos(1, 1, 1); + + MutableInt invalidationCount = new MutableInt(); + var capCache = BlockCapabilityCache.create( + Capabilities.ItemHandler.BLOCK, + helper.getLevel(), + helper.absolutePos(composterPos), + Direction.UP, + () -> true, + invalidationCount::increment); + + if (capCache.getCapability() != null) + helper.fail("Expected no capability", composterPos); + if (capCache.getCapability() != null) // check again just in case + helper.fail("Expected no capability", composterPos); + if (invalidationCount.getValue() != 0) + helper.fail("Should not have been invalidated yet", composterPos); + + // The cache should only be invalidated once until it is queried again + helper.setBlock(composterPos, Blocks.COMPOSTER.defaultBlockState()); + if (invalidationCount.getValue() != 1) + helper.fail("Should have invalidated once"); + + helper.setBlock(composterPos, Blocks.AIR.defaultBlockState()); + if (invalidationCount.getValue() != 1) // capability not re-queried, so no invalidation + helper.fail("Should have invalidated once"); + + helper.setBlock(composterPos, Blocks.COMPOSTER.defaultBlockState()); + if (invalidationCount.getValue() != 1) // capability not re-queried, so no invalidation + helper.fail("Should have invalidated once"); + + // Should be ok to query now + if (capCache.getCapability() == null) + helper.fail("Expected capability", composterPos); + if (invalidationCount.getValue() != 1) + helper.fail("Should have invalidated once"); + + // Should be notified of disappearance if the composter is removed + helper.setBlock(composterPos, Blocks.AIR.defaultBlockState()); + + if (invalidationCount.getValue() != 2) + helper.fail("Should have invalidated a second time"); + if (capCache.getCapability() != null) + helper.fail("Expected no capability", composterPos); + + helper.succeed(); + } + + @GameTest + @EmptyTemplate + @TestHolder(description = "Test cauldron interactions via the fluid handler capability") + public static void testCauldronCapability(ExtendedGameTestHelper helper) { + var cauldronPos = new BlockPos(1, 1, 1); + + MutableInt invalidationCount = new MutableInt(); + var capCache = BlockCapabilityCache.create( + Capabilities.FluidHandler.BLOCK, + helper.getLevel(), + helper.absolutePos(cauldronPos), + Direction.UP, + () -> true, + invalidationCount::increment); + + // Capability should be absent + helper.assertTrue(capCache.getCapability() == null, "Expected no capability"); + + // Should invalidate once when setting the block + helper.setBlock(cauldronPos, Blocks.CAULDRON); + var wrapper = capCache.getCapability(); + helper.assertTrue(wrapper != null, "Expected fluid handler"); + helper.assertTrue(invalidationCount.intValue() == 1, "Expected 1 invalidation only"); + + helper.assertTrue(wrapper.getTanks() == 1, "Got %d tanks".formatted(wrapper.getTanks())); + + // Simulate filling with water + var fillResult = wrapper.fill(new FluidStack(Fluids.WATER, 2000), SIMULATE); + helper.assertTrue(fillResult == 1000, "Filled " + fillResult); + helper.assertBlockPresent(Blocks.CAULDRON, cauldronPos); + // Can't fill with less than 1000 though... + helper.assertTrue(wrapper.fill(new FluidStack(Fluids.WATER, 999), SIMULATE) == 0, "Expected 0 fill result"); + + // Action! + fillResult = wrapper.fill(new FluidStack(Fluids.WATER, 2000), EXECUTE); + helper.assertTrue(fillResult == 1000, "Filled " + fillResult); + helper.assertBlockState(cauldronPos, state -> state.is(Blocks.WATER_CAULDRON) && state.getValue(LayeredCauldronBlock.LEVEL) == 3, () -> "Expected level 3 cauldron"); + + helper.assertTrue(wrapper.getFluidInTank(0).equals(new FluidStack(Fluids.WATER, 1000)), "Expected 1000 water"); + + // Try to empty as well + helper.assertTrue(wrapper.drain(new FluidStack(Fluids.LAVA, 1000), EXECUTE).isEmpty(), "Cannot drain lava"); + helper.assertTrue(wrapper.drain(new FluidStack(Fluids.WATER, 999), EXECUTE).isEmpty(), "Cannot drain less than 1000 water"); + helper.assertTrue(wrapper.drain(new FluidStack(Fluids.WATER, 1000), EXECUTE).equals(new FluidStack(Fluids.WATER, 1000)), "Expected drain of 1000 water"); + + helper.assertBlockPresent(Blocks.CAULDRON, cauldronPos); + helper.assertTrue(wrapper.getFluidInTank(0).isEmpty(), "Expected empty handler"); + + // Try lava cauldron + helper.setBlock(cauldronPos, Blocks.LAVA_CAULDRON); + helper.assertTrue(wrapper.getFluidInTank(0).equals(new FluidStack(Fluids.LAVA, 1000)), "Expected 1000 lava"); + helper.assertTrue(wrapper.drain(1000, EXECUTE).equals(new FluidStack(Fluids.LAVA, 1000)), "Expected drain of 1000 lava"); + helper.assertBlockPresent(Blocks.CAULDRON, cauldronPos); + + // Try partial water filling + helper.setBlock(cauldronPos, Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 2)); + helper.assertTrue(wrapper.getFluidInTank(0).equals(new FluidStack(Fluids.WATER, 666)), "Expected 666 water"); + helper.assertTrue(wrapper.drain(1000, EXECUTE).isEmpty(), "Expected no water drain from partial cauldron"); + helper.assertTrue(wrapper.fill(new FluidStack(Fluids.WATER, 1000), EXECUTE) == 0, "Expected no water fill to partial cauldron"); + + // None of this should have invalidated the capability + helper.assertTrue(invalidationCount.intValue() == 1, "Expected 1 invalidation only after the whole test"); + // But if we change the block to a non-cauldron, it should invalidate + helper.destroyBlock(cauldronPos); + helper.assertTrue(invalidationCount.intValue() == 2, "Expected a second invalidation after cauldron destruction"); + + helper.succeed(); + } +} diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/capability/CapabilityTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/capability/CapabilityTests.java deleted file mode 100644 index edf17574ac7..00000000000 --- a/tests/src/main/java/net/neoforged/neoforge/debug/capability/CapabilityTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.debug.capability; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.gametest.framework.GameTest; -import net.minecraft.world.level.block.Blocks; -import net.neoforged.neoforge.capabilities.BlockCapabilityCache; -import net.neoforged.neoforge.capabilities.Capabilities; -import net.neoforged.testframework.annotation.ForEachTest; -import net.neoforged.testframework.annotation.TestHolder; -import net.neoforged.testframework.gametest.EmptyTemplate; -import net.neoforged.testframework.gametest.ExtendedGameTestHelper; -import org.apache.commons.lang3.mutable.MutableInt; - -@ForEachTest(groups = "capability") -public class CapabilityTests { - @GameTest - @EmptyTemplate - @TestHolder(description = "Tests if composter invalidation works") - static void composterInvalidationTest(final ExtendedGameTestHelper helper) { - var composterPos = new BlockPos(1, 1, 1); - - MutableInt invalidationCount = new MutableInt(); - var capCache = BlockCapabilityCache.create( - Capabilities.ItemHandler.BLOCK, - helper.getLevel(), - helper.absolutePos(composterPos), - Direction.UP, - () -> true, - invalidationCount::increment); - - if (capCache.getCapability() != null) - helper.fail("Expected no capability", composterPos); - if (capCache.getCapability() != null) // check again just in case - helper.fail("Expected no capability", composterPos); - if (invalidationCount.getValue() != 0) - helper.fail("Should not have been invalidated yet", composterPos); - - // The cache should only be invalidated once until it is queried again - helper.setBlock(composterPos, Blocks.COMPOSTER.defaultBlockState()); - if (invalidationCount.getValue() != 1) - helper.fail("Should have invalidated once"); - - helper.setBlock(composterPos, Blocks.AIR.defaultBlockState()); - if (invalidationCount.getValue() != 1) // capability not re-queried, so no invalidation - helper.fail("Should have invalidated once"); - - helper.setBlock(composterPos, Blocks.COMPOSTER.defaultBlockState()); - if (invalidationCount.getValue() != 1) // capability not re-queried, so no invalidation - helper.fail("Should have invalidated once"); - - // Should be ok to query now - if (capCache.getCapability() == null) - helper.fail("Expected capability", composterPos); - if (invalidationCount.getValue() != 1) - helper.fail("Should have invalidated once"); - - // Should be notified of disappearance if the composter is removed - helper.setBlock(composterPos, Blocks.AIR.defaultBlockState()); - - if (invalidationCount.getValue() != 2) - helper.fail("Should have invalidated a second time"); - if (capCache.getCapability() != null) - helper.fail("Expected no capability", composterPos); - - helper.succeed(); - } -} diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/effect/MobEffectTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/effect/MobEffectTests.java new file mode 100644 index 00000000000..fa8d94eea9a --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/effect/MobEffectTests.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug.effect; + +import java.util.Set; +import net.minecraft.core.registries.Registries; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.animal.Pig; +import net.neoforged.neoforge.common.EffectCure; +import net.neoforged.neoforge.common.EffectCures; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.registration.RegistrationHelper; + +@ForEachTest(groups = MobEffectTests.GROUP) +public class MobEffectTests { + public static final String GROUP = "level.effect"; + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests whether items and effects can properly specify what they cure and what they are cured by respectively") + static void effectCures(final DynamicTest test, final RegistrationHelper reg) { + final var testCure = EffectCure.get("test_cure"); + final var testCureTwo = EffectCure.get("test_cure_two"); + + final var testEffect = reg.registrar(Registries.MOB_EFFECT).register("test_effect", () -> new MobEffect( + MobEffectCategory.HARMFUL, 0xFF0000) { + @Override + public void fillEffectCures(Set cures, MobEffectInstance effectInstance) { + super.fillEffectCures(cures, effectInstance); + cures.remove(EffectCures.MILK); + cures.add(testCureTwo); + } + }); + + test.eventListeners().forge().addListener((MobEffectEvent.Added event) -> { + if (event.getEffectInstance().getEffect() == MobEffects.NIGHT_VISION) { + event.getEffectInstance().getCures().add(testCure); + } + }); + + test.onGameTest(helper -> { + Pig pig = helper.spawnWithNoFreeWill(EntityType.PIG, 1, 1, 1); + + pig.addEffect(new MobEffectInstance(MobEffects.CONFUSION)); + helper.assertMobEffectPresent(pig, MobEffects.CONFUSION, "'confusion was applied'"); + pig.removeEffectsCuredBy(testCure); + helper.assertMobEffectPresent(pig, MobEffects.CONFUSION, "'confusion not removed by test cure'"); + pig.removeEffectsCuredBy(EffectCures.MILK); + helper.assertMobEffectAbsent(pig, MobEffects.CONFUSION, "'confusion removed by milk'"); + + pig.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION)); + helper.assertMobEffectPresent(pig, MobEffects.NIGHT_VISION, "'nightvision was applied'"); + pig.removeEffectsCuredBy(testCure); + helper.assertMobEffectAbsent(pig, MobEffects.NIGHT_VISION, "'nightvision removed by test cure'"); + + pig.addEffect(new MobEffectInstance(testEffect.get())); + helper.assertMobEffectPresent(pig, testEffect.get(), "'test effect was applied'"); + pig.removeEffectsCuredBy(EffectCures.MILK); + helper.assertMobEffectPresent(pig, testEffect.get(), "'test effect not removed by milk'"); + pig.removeEffectsCuredBy(testCureTwo); + helper.assertMobEffectAbsent(pig, testEffect.get(), "'test effect removed by test cure'"); + + MobEffectInstance srcInst = new MobEffectInstance(MobEffects.CONFUSION); + MobEffectInstance destInst = MobEffectInstance.load(srcInst.save(new CompoundTag())); + helper.assertTrue(srcInst.getCures().equals(destInst.getCures()), "'MobEffectInstance serialization roundtrip (standard cures)'"); + + srcInst.getCures().add(testCure); + destInst = MobEffectInstance.load(srcInst.save(new CompoundTag())); + helper.assertTrue(srcInst.getCures().equals(destInst.getCures()), "'MobEffectInstance serialization roundtrip (custom additional cure)'"); + + srcInst.getCures().clear(); + destInst = MobEffectInstance.load(srcInst.save(new CompoundTag())); + helper.assertTrue(srcInst.getCures().equals(destInst.getCures()), "'MobEffectInstance serialization roundtrip (no cures)'"); + + helper.succeed(); + }); + } +} diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/entity/player/PlayerEventTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/entity/player/PlayerEventTests.java index 6b2f21e8247..3ba0789f936 100644 --- a/tests/src/main/java/net/neoforged/neoforge/debug/entity/player/PlayerEventTests.java +++ b/tests/src/main/java/net/neoforged/neoforge/debug/entity/player/PlayerEventTests.java @@ -10,6 +10,8 @@ import net.minecraft.gametest.framework.GameTest; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundInteractPacket; +import net.minecraft.stats.ServerStatsCounter; +import net.minecraft.stats.Stats; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.EntityType; @@ -22,6 +24,8 @@ import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.neoforge.event.StatAwardEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; import net.neoforged.neoforge.event.entity.player.UseItemOnBlockEvent; @@ -29,6 +33,8 @@ import net.neoforged.testframework.annotation.ForEachTest; import net.neoforged.testframework.annotation.TestHolder; import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.gametest.GameTestPlayer; +import net.neoforged.testframework.registration.RegistrationHelper; @ForEachTest(groups = { PlayerTests.GROUP + ".event", "event" }) public class PlayerEventTests { @@ -108,4 +114,32 @@ static void entityInteractEvent(final DynamicTest test) { .thenSucceed(); }); } + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests if the StatsAwardEvent properly modifies stats stored per player") + static void changeStatAward(final DynamicTest test, final RegistrationHelper reg) { + test.eventListeners().forge().addListener(EventPriority.NORMAL, false, StatAwardEvent.class, event -> { + //when damage is dealt, instead record this stat as a bell ring + if (event.getStat().equals(Stats.CUSTOM.get(Stats.DAMAGE_TAKEN))) + event.setStat(Stats.CUSTOM.get(Stats.BELL_RING)); + }); + test.eventListeners().forge().addListener(EventPriority.NORMAL, false, StatAwardEvent.class, event -> { + //when awarded stats for breeding, multiply the value by 10 + if (event.getStat().equals(Stats.CUSTOM.get(Stats.ANIMALS_BRED))) + event.setValue(event.getValue() * 10); + }); + + test.onGameTest(helper -> { + GameTestPlayer player = helper.makeTickingMockServerPlayerInLevel(GameType.SURVIVAL); + //Award a damage stat, which we are listening for in order to change the stat + player.awardStat(Stats.CUSTOM.get(Stats.DAMAGE_TAKEN), 100); + //Award an animal breed stat, which we are listining for in order to multiply the value + player.awardStat(Stats.CUSTOM.get(Stats.ANIMALS_BRED), 1); + ServerStatsCounter stats = player.level().getServer().getPlayerList().getPlayerStats(player); + //if our damage stat is changed to bell ring and our animal breed stat is multiplied by ten, the test passes + if (stats.getValue(Stats.CUSTOM.get(Stats.BELL_RING)) == 100 && stats.getValue(Stats.CUSTOM.get(Stats.ANIMALS_BRED)) == 10) + helper.succeed(); + }); + } } diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/loot/LootPoolTest.java b/tests/src/main/java/net/neoforged/neoforge/debug/loot/LootPoolTest.java new file mode 100644 index 00000000000..32fd7dfd51d --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/loot/LootPoolTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug.loot; + +import java.util.List; +import java.util.Set; +import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.registration.RegistrationHelper; + +@ForEachTest(groups = "loot") +public class LootPoolTest { + private static final ResourceLocation TEST_LOOT_TABLE = new ResourceLocation("neoforge", "test_loot_table"); + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests if loading loot pools with custom names works") + public static void testPoolLoading(DynamicTest test, RegistrationHelper reg) { + reg.addProvider(event -> new LootTableProvider( + event.getGenerator().getPackOutput(), + Set.of(), + List.of( + new LootTableProvider.SubProviderEntry(() -> p_249643_ -> { + p_249643_.accept( + TEST_LOOT_TABLE, + LootTable.lootTable() + .withPool(LootPool.lootPool() + .add(LootItem.lootTableItem(Items.DIAMOND)) + .name("custom_name")) + .withPool(LootPool.lootPool() + .add(LootItem.lootTableItem(Items.GOLD_NUGGET)))); + }, LootContextParamSets.ALL_PARAMS)))); + + test.onGameTest(helper -> { + var testTable = helper.getLevel().getServer().getLootData().getLootTable(TEST_LOOT_TABLE); + + helper.assertTrue(testTable.getPool("custom_name") != null, "Expected custom_name pool"); + helper.assertTrue(testTable.getPool("pool1") != null, "Expected unnamed pool pool1"); + + helper.succeed(); + }); + } +} diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/resources/OverlayTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/resources/OverlayTests.java new file mode 100644 index 00000000000..43c58c44ad8 --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/resources/OverlayTests.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug.resources; + +import net.minecraft.core.registries.Registries; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Blocks; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; + +@ForEachTest(groups = OverlayTests.GROUP) +public class OverlayTests { + public static final String GROUP = "resources"; + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests if pack overlays from mods work") + static void packOverlay(final DynamicTest test) { + var tagKey = TagKey.create(Registries.BLOCK, new ResourceLocation("pack_overlays_test", "must_be_overlayed")); + test.onGameTest(helper -> { + helper.assertTrue(Blocks.DIAMOND_BLOCK.defaultBlockState().is(tagKey), "Overlay was not applied"); + helper.assertFalse(Blocks.COBBLESTONE.defaultBlockState().is(tagKey), "File under overlay was applied"); + helper.succeed(); + }); + } +} diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/DataGeneratorTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/DataGeneratorTest.java index 5423e352a23..1fce40d596b 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/DataGeneratorTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/DataGeneratorTest.java @@ -5,16 +5,34 @@ package net.neoforged.neoforge.oldtest; -import com.google.common.collect.*; -import com.google.gson.*; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.ObjectArrays; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigDecimal; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -28,7 +46,11 @@ import net.minecraft.client.renderer.block.model.ItemTransform; import net.minecraft.client.renderer.block.model.ItemTransforms; import net.minecraft.client.renderer.block.model.Variant; -import net.minecraft.core.*; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderGetter; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.RegistrySetBuilder; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; @@ -36,17 +58,22 @@ import net.minecraft.data.DataGenerator; import net.minecraft.data.PackOutput; import net.minecraft.data.metadata.PackMetadataGenerator; -import net.minecraft.data.recipes.*; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.RecipeProvider; +import net.minecraft.data.recipes.ShapedRecipeBuilder; import net.minecraft.data.worldgen.BootstapContext; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.OverlayMetadataSection; import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.metadata.pack.PackMetadataSection; import net.minecraft.server.packs.resources.Resource; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.BlockTags; import net.minecraft.tags.ItemTags; +import net.minecraft.util.InclusiveRange; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.ItemDisplayContext; @@ -56,7 +83,21 @@ import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.TheEndBiomeSource; -import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.BarrelBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.ButtonBlock; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.FurnaceBlock; +import net.minecraft.world.level.block.IronBarsBlock; +import net.minecraft.world.level.block.PressurePlateBlock; +import net.minecraft.world.level.block.RotatedPillarBlock; +import net.minecraft.world.level.block.SlabBlock; +import net.minecraft.world.level.block.StairBlock; +import net.minecraft.world.level.block.StandingSignBlock; +import net.minecraft.world.level.block.TrapDoorBlock; +import net.minecraft.world.level.block.WallSignBlock; import net.minecraft.world.level.dimension.BuiltinDimensionTypes; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.LevelStem; @@ -65,11 +106,27 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.Mod; import net.neoforged.fml.common.Mod.EventBusSubscriber.Bus; -import net.neoforged.neoforge.client.model.generators.*; +import net.neoforged.neoforge.client.model.generators.BlockStateProvider; +import net.neoforged.neoforge.client.model.generators.ConfiguredModel; +import net.neoforged.neoforge.client.model.generators.ItemModelProvider; +import net.neoforged.neoforge.client.model.generators.ModelBuilder; +import net.neoforged.neoforge.client.model.generators.ModelFile; import net.neoforged.neoforge.client.model.generators.ModelFile.UncheckedModelFile; +import net.neoforged.neoforge.client.model.generators.MultiPartBlockStateBuilder; +import net.neoforged.neoforge.client.model.generators.VariantBlockStateBuilder; import net.neoforged.neoforge.common.conditions.IConditionBuilder; -import net.neoforged.neoforge.common.crafting.*; -import net.neoforged.neoforge.common.data.*; +import net.neoforged.neoforge.common.crafting.CompoundIngredient; +import net.neoforged.neoforge.common.crafting.DifferenceIngredient; +import net.neoforged.neoforge.common.crafting.IntersectionIngredient; +import net.neoforged.neoforge.common.crafting.NBTIngredient; +import net.neoforged.neoforge.common.data.AdvancementProvider; +import net.neoforged.neoforge.common.data.BlockTagsProvider; +import net.neoforged.neoforge.common.data.DatapackBuiltinEntriesProvider; +import net.neoforged.neoforge.common.data.ExistingFileHelper; +import net.neoforged.neoforge.common.data.LanguageProvider; +import net.neoforged.neoforge.common.data.ParticleDescriptionProvider; +import net.neoforged.neoforge.common.data.SoundDefinition; +import net.neoforged.neoforge.common.data.SoundDefinitionsProvider; import net.neoforged.neoforge.data.event.GatherDataEvent; import org.apache.commons.lang3.tuple.Triple; import org.apache.logging.log4j.LogManager; @@ -103,10 +160,12 @@ public static void gatherData(GatherDataEvent event) { CompletableFuture lookupProvider = event.getLookupProvider(); gen.addProvider(true, new PackMetadataGenerator(packOutput) + .add(OverlayMetadataSection.TYPE, new OverlayMetadataSection(List.of( + new OverlayMetadataSection.OverlayEntry(new InclusiveRange<>(0, Integer.MAX_VALUE), "pack_overlays_test")))) .add(PackMetadataSection.TYPE, new PackMetadataSection( Component.literal("NeoForge tests resource pack"), DetectedVersion.BUILT_IN.getPackVersion(PackType.CLIENT_RESOURCES), - Arrays.stream(PackType.values()).collect(Collectors.toMap(Function.identity(), DetectedVersion.BUILT_IN::getPackVersion))))); + Optional.of(new InclusiveRange<>(0, Integer.MAX_VALUE))))); gen.addProvider(event.includeClient(), new Lang(packOutput)); // Let blockstate provider see generated item models by passing its existing file helper ItemModelProvider itemModels = new ItemModels(packOutput, event.getExistingFileHelper()); diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java index 5560bb38575..34021439cd0 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java @@ -12,7 +12,10 @@ import net.minecraft.core.Direction; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.*; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Rarity; +import net.minecraft.world.item.StandingAndWallBlockItem; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.SkullBlock; import net.minecraft.world.level.block.WallSkullBlock; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/block/SlipperinessTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/block/SlipperinessTest.java index ceab2c4fd91..87e51c2cba3 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/block/SlipperinessTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/block/SlipperinessTest.java @@ -22,7 +22,7 @@ import net.neoforged.neoforge.registries.RegisterEvent; @Mod(SlipperinessTest.MOD_ID) -@EventBusSubscriber +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) public class SlipperinessTest { static final String MOD_ID = "slipperiness_test"; static final String BLOCK_ID = "test_block"; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomArmorModelTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomArmorModelTest.java index 5a989e90af9..bf26ab4433d 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomArmorModelTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/CustomArmorModelTest.java @@ -18,8 +18,13 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.*; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.ArmorMaterial; +import net.minecraft.world.item.ArmorMaterials; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; import net.minecraft.world.item.Item.Properties; +import net.minecraft.world.item.ItemStack; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CompositeModelTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CompositeModelTest.java index 750103586df..53d44e56d34 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CompositeModelTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CompositeModelTest.java @@ -8,7 +8,10 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.Block; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CustomItemDisplayContextTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CustomItemDisplayContextTest.java index be94d011b74..7e08e7649ef 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CustomItemDisplayContextTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/CustomItemDisplayContextTest.java @@ -26,7 +26,12 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/NewModelLoaderTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/NewModelLoaderTest.java index 5a30428ecd5..e0e2301fd3b 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/NewModelLoaderTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/model/NewModelLoaderTest.java @@ -19,7 +19,11 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.Block; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/CustomParticleTypeTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/CustomParticleTypeTest.java index 820878402df..e531a8cf2b3 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/CustomParticleTypeTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/CustomParticleTypeTest.java @@ -9,7 +9,8 @@ import com.mojang.blaze3d.vertex.Tesselator; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.particle.*; +import net.minecraft.client.particle.ParticleRenderType; +import net.minecraft.client.particle.TerrainParticle; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.block.Blocks; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/EntityRendererEventsTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/EntityRendererEventsTest.java index 5db6ac5e83b..fbe6e1eb507 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/EntityRendererEventsTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/client/rendering/EntityRendererEventsTest.java @@ -12,7 +12,11 @@ import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.PartPose; -import net.minecraft.client.model.geom.builders.*; +import net.minecraft.client.model.geom.builders.CubeDeformation; +import net.minecraft.client.model.geom.builders.CubeListBuilder; +import net.minecraft.client.model.geom.builders.LayerDefinition; +import net.minecraft.client.model.geom.builders.MeshDefinition; +import net.minecraft.client.model.geom.builders.PartDefinition; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.entity.LivingEntityRenderer; @@ -20,7 +24,11 @@ import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.*; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.MobCategory; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.monster.Monster; import net.minecraft.world.item.ItemStack; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/entity/player/ItemUseAnimationTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/entity/player/ItemUseAnimationTest.java index 81e997a58d6..286c2193dea 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/entity/player/ItemUseAnimationTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/entity/player/ItemUseAnimationTest.java @@ -13,7 +13,10 @@ import net.minecraft.world.entity.HumanoidArm; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.food.FoodProperties; -import net.minecraft.world.item.*; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.UseAnim; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/fluid/NewFluidTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/fluid/NewFluidTest.java index 263ada4f5de..2c790c55f29 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/fluid/NewFluidTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/fluid/NewFluidTest.java @@ -10,7 +10,12 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.BucketItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Block; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomElytraTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomElytraTest.java index 08fd0b07756..4962c058c5c 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomElytraTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomElytraTest.java @@ -15,8 +15,11 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.*; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; import net.minecraft.world.item.Item.Properties; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.DispenserBlock; import net.minecraft.world.level.gameevent.GameEvent; import net.neoforged.api.distmarker.Dist; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomShieldTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomShieldTest.java index a52a884ffc1..0da6637ed89 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomShieldTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/CustomShieldTest.java @@ -8,7 +8,10 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.*; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.UseAnim; import net.minecraft.world.level.Level; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/EnderMaskTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/EnderMaskTest.java index 9f4afb36a1b..576605612b8 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/EnderMaskTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/EnderMaskTest.java @@ -7,7 +7,11 @@ import net.minecraft.world.entity.monster.EnderMan; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.*; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.ArmorMaterials; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/RangedMobsUseModdedWeaponsTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/RangedMobsUseModdedWeaponsTest.java index 733cf4e907c..b2e4aae2c86 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/RangedMobsUseModdedWeaponsTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/RangedMobsUseModdedWeaponsTest.java @@ -7,7 +7,11 @@ import net.minecraft.client.renderer.item.ItemProperties; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BowItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.CrossbowItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/SnowBootsTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/SnowBootsTest.java index 9502a303915..31a0b62451c 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/SnowBootsTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/SnowBootsTest.java @@ -6,7 +6,11 @@ package net.neoforged.neoforge.oldtest.item; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.*; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.ArmorMaterials; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/TagBasedToolTypesTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/TagBasedToolTypesTest.java index 5750d306c06..21da5d4eb73 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/item/TagBasedToolTypesTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/item/TagBasedToolTypesTest.java @@ -18,7 +18,14 @@ import net.minecraft.tags.BlockTags; import net.minecraft.tags.TagKey; import net.minecraft.world.flag.FeatureFlags; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.DiggerItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.Tier; +import net.minecraft.world.item.Tiers; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockBehaviour; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/GameTestTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/GameTestTest.java index 46391cbb484..b91466ae5ce 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/GameTestTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/GameTestTest.java @@ -13,7 +13,9 @@ import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.gametest.framework.TestFunction; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.*; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Items; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestMenu.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestMenu.java index d46129b6570..1c44ca6d26d 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestMenu.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestMenu.java @@ -14,7 +14,13 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.StackedContents; -import net.minecraft.world.inventory.*; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.CraftingMenu; +import net.minecraft.world.inventory.RecipeBookMenu; +import net.minecraft.world.inventory.RecipeBookType; +import net.minecraft.world.inventory.RecipeCraftingHolder; +import net.minecraft.world.inventory.ResultContainer; +import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeHolder; diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestRecipe.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestRecipe.java index 42a4c67d838..1fdcf35273f 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestRecipe.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookTestRecipe.java @@ -9,7 +9,11 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.codecs.RecordCodecBuilder; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/tests/src/main/java/net/neoforged/neoforge/unittest/TextTableTest.java b/tests/src/main/java/net/neoforged/neoforge/unittest/TextTableTest.java index d4f1871976b..e1dfa88041b 100644 --- a/tests/src/main/java/net/neoforged/neoforge/unittest/TextTableTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/unittest/TextTableTest.java @@ -6,7 +6,9 @@ package net.neoforged.neoforge.unittest; import static net.neoforged.neoforge.common.util.TextTable.column; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import com.google.common.collect.Lists; import net.neoforged.neoforge.common.util.TextTable; diff --git a/tests/src/main/resources/data/pack_overlays_test/tags/blocks/must_be_overlayed.json b/tests/src/main/resources/data/pack_overlays_test/tags/blocks/must_be_overlayed.json new file mode 100644 index 00000000000..08651152739 --- /dev/null +++ b/tests/src/main/resources/data/pack_overlays_test/tags/blocks/must_be_overlayed.json @@ -0,0 +1,5 @@ +{ + "values": [ + "minecraft:cobblestone" + ] +} diff --git a/tests/src/main/resources/pack_overlays_test/data/pack_overlays_test/tags/blocks/must_be_overlayed.json b/tests/src/main/resources/pack_overlays_test/data/pack_overlays_test/tags/blocks/must_be_overlayed.json new file mode 100644 index 00000000000..ad41f7f735d --- /dev/null +++ b/tests/src/main/resources/pack_overlays_test/data/pack_overlays_test/tags/blocks/must_be_overlayed.json @@ -0,0 +1,5 @@ +{ + "values": [ + "minecraft:diamond_block" + ] +}