Compare commits

...
29 Commits
Author SHA1 Message Date
Matt c974df523e Update MDK to use FG7 (#10923) 2026-11-08 09:39:33 -08:00
LexManos d13dd327bd Fix legacy object holders not being fired in the registry specific codepath
Better optimize the scanning for object holders by unrolling streams.
2026-10-13 09:21:04 -07:00
BonsUnleashedandLexManos 216cc50162 Store object holder handlers per registry name (#10899)
Co-authored-by: LexManos <[email protected]>
2026-10-12 18:46:47 -07:00
BonsUnleashed 0ec923d730 Group the status ping channels by mod once instead of filtering per mod (#10893) 2026-10-02 13:19:02 -07:00
BonsUnleashed 8418bad66d Skip the MessageFormat parse in ForgeI18n for strings with no format syntax (#10894) 2026-10-02 13:18:40 -07:00
LexManos ae3c937073 Explicitly make heap copy of ClientboundCustomPayloadPacket's Data. Closes #10861 2026-10-02 13:07:53 -07:00
Owen 3c3f496015 Fix BlockElements not properly saving forge_data when writing to json (#10877) 2026-08-19 10:05:11 -07:00
Meme Man 22d0c94c82 Update equality check for RegistryObject to include RegistryObject#key. (#10854) 2026-07-21 11:25:36 -07:00
Matt 77785cb181 Toml parsing errors are now visible in the latest log (#10846) 2026-07-09 13:44:19 -07:00
Matt 9130c27d05 Add support for multiple access transformer configs. Backport (#10760) (#10786) 2026-04-10 08:05:36 -07:00
LexManos 1b26f0c967 [no ci] Bump ForgeGradle, disable clean project. Closes #10802 2026-04-01 09:49:22 -07:00
embeddedt 723799cdb8 Fix HandshakeHandler potentially skipping last packet (#10787) 2026-03-19 22:38:11 -07:00
Paint_Ninja 64caebe63c Skip processing vanilla classes in RuntimeDistCleaner when running on a client
Backport of #10682 to 1.20.1. Also bumped ASM to 9.9.1 for Java 26 support
2026-03-17 21:21:26 +00:00
embeddedt 8dffec4dbc Bump EventBus to 6.2.32 (#10744)
Brings all the improvements made to EventBus 6 over the past 2+ years to Forge 1.20.1. Highlights:
- Lower memory usage, faster event posting and listener registration (EventBus#98, #85, #84, #70, #65, #64, #62, #60)
- EventBus#fire(), Event#cancel() and Event.Result convenience methods (EventBus#57, #94, e2a19cb)
- New monitor phase for read-only event listeners that want to run last (EventBus c510e2c)
- Fixed memory leak after unregistering listeners in some cases (EventBus#65, #39)
- Factors in bus phase tracking and event class modifiers (final/sealed) to enable further optimisations (EventBus#98, #85, #70)
2026-01-20 21:29:49 +00:00
LexManos d6b2bf7ca3 Re-trigger build due to Github Actions Issue 2026-01-16 18:28:04 -08:00
MattandLexManos e7629d5ae4 Optimize memory allocation for checking entity fluid collisions. Backport of #10745
Co-authored-by: LexManos <[email protected]>
2026-01-16 13:13:06 -08:00
LexManos 7187d8d97f Migrate to Github Actions 2025-12-02 15:17:12 -08:00
LexManos f20ab8e6ce Fix loading ImmediateWindowHandler services from the wrong Module Layer. Closes #10719
Fix errors not logging to log files when early loading screen errors.
Fix generics compile issue in ConctendatedListView.
2025-11-23 20:37:18 -08:00
Yuri 1837d8c36c Allow loading ImmediateWindowProvider from mods dir (#10718) 2025-11-21 09:57:56 -08:00
Jonathing 132704e5f2 Work around mixins targeting IForgePlayer#canReach (#10680) 2025-10-09 15:50:41 -07:00
embeddedt e9ce8a6cc6 Bump ASM to 9.8 for Java 25 runtime support (#10664) 2025-09-19 18:57:32 -07:00
Jonathing 350bfa51e1 Optimize fix for MC-176559 (#10649) 2025-09-01 20:22:28 -07:00
Jonathing ddb5f6c749 Ignore item equality when checking shouldCauseBlockBreakReset, fixes #10645 (#10646) 2025-08-26 12:29:48 -07:00
Joseph T. McQuigg d00f5c4013 Allow command suggestions/autocomplete to search all modded paths at once without namespace provided (#10595) 2025-08-03 09:55:59 -07:00
Jenny Mei f4e8c6b67e Fix ServerChatEvent thread not using correct class loader. (#10591) 2025-08-01 13:25:26 -07:00
Jonathing 6c4f5e2865 Fix ignitedByLava making blocks permanently flammable (#10570) 2025-07-16 09:18:45 -07:00
LexManos d1071fd0f8 Add back magic number to make mixin happy. 2025-06-28 22:30:12 -07:00
LexManos 064e2fed48 Fix container screens closing when opened with an extended REACH attribute. Fixes #10560
Also fixes cases where creative mode effected reach incorrectly.
2025-06-11 14:23:16 -07:00
JonathingandLexManos f36781652c Deprecate EntityRenderersEvent.AddLayers functions that hard case to LivingEntityRenderers as the backing maps do not guarantee that type. (#10513)
Co-authored-by: LexManos <[email protected]>
2025-05-22 00:47:40 -07:00
49 changed files with 954 additions and 745 deletions

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
name: Publish
on:
push:
branches: [ '1.20.1' ]
permissions:
contents: read
jobs:
build:
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@main
with:
java: 17
gradle_setup: './gradlew setup'
gradle_tasks: 'build publish'
artifact_name: 'forge'
secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
SIGN_KEY_ALIAS: ${{ secrets.SIGN_KEY_ALIAS }}
SIGN_KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
SIGN_KEYSTORE_PASSWORD: ${{ secrets.SIGN_KEYSTORE_PASSWORD }}
SIGN_KEYSTORE_DATA: ${{ secrets.SIGN_KEYSTORE_DATA }}
-104
View File
@@ -1,104 +0,0 @@
<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<name>MinecraftForge_Forge Config DSL Script</name>
<groupId>MinecraftForge_Forge</groupId>
<artifactId>MinecraftForge_Forge_dsl</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<repositories>
<repository>
<id>jetbrains-all</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>teamcity-server</id>
<url>https://teamcity.minecraftforge.net/app/dsl-plugins-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>JetBrains</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>${basedir}</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration/>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>teamcity-configs-maven-plugin</artifactId>
<version>${teamcity.dsl.version}</version>
<configuration>
<format>kotlin</format>
<dstDir>target/generated-configs</dstDir>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin</artifactId>
<version>${teamcity.dsl.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-plugins</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-script-runtime</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
-208
View File
@@ -1,208 +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)
buildType(PullRequests)
buildType(PullRequestChecks)
buildType(PullRequestCompatibility)
params {
text("docker_jdk_version", "21", 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", "8.8", 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", "MinecraftForge", 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.minecraftforge", 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 = "MinecraftForge/MinecraftForge"
repositoryURL = "https://github.com/MinecraftForge/MinecraftForge"
}
}
}
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/(*)
-: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)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequests : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequests")
name = "Pull Requests"
description = "Builds pull requests for the project"
params {
checkbox("should_execute_build", "true", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
text(
"gradle_build_task",
"assemble",
label = "Gradle build task to execute during build",
description = "Determines the build task that is executed to build the project.",
display = ParameterDisplay.HIDDEN,
allowEmpty = false
)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestChecks : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestChecks")
name = "Pull Requests (Checks)"
description = "Checks pull requests for the project"
steps {
gradle {
name = "Check"
id = "RUNNER_10_Check"
tasks = "checkAll"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestCompatibility : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
name = "Pull Requests (Compatibility)"
description = "Validates binary compatibility for pull requests made to the project"
steps {
gradle {
name = "Validate"
id = "RUNNER_10_Compatibility"
tasks = "checkJarCompatibility"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
+21 -29
View File
@@ -29,13 +29,14 @@ plugins {
id 'eclipse'
id 'idea'
id 'de.undercouch.download' version '5.4.0'
id 'net.minecraftforge.gradle.patcher' version '[6.0.11,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0.11,6.2)' apply false
id 'net.minecraftforge.gradle.patcher' version '[6.0.52,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0.52,6.2)' apply false
}
Util.init() //Init all our extension methods!
ext {
HAS_CLEAN = findProject(':clean') != null
JAR_SIGNER = null
if (project.hasProperty('keystore')) {
JAR_SIGNER = [
@@ -55,11 +56,11 @@ ext {
MERGETOOL_VERSION = '1.1.5'
ACCESSTRANSFORMERS_VERSION = '8.0.4'
COREMODS_VERSION = '5.2.4'
EVENTBUS_VERSION = '6.0.5'
EVENTBUS_VERSION = '6.2.33'
MODLAUNCHER_VERSION = '10.0.9'
SECUREJARHANDLER_VERSION = '2.1.10'
BOOTSTRAPLAUNCHER_VERSION = '1.1.2'
ASM_VERSION = '9.7.1'
ASM_VERSION = '9.9.1'
INSTALLER_VERSION = '2.2.+'
MIXIN_VERSION = '0.8.5'
TERMINALCONSOLEAPPENDER_VERSION = '1.2.0'
@@ -227,7 +228,7 @@ project(':mcp') {
}
}
if (!System.env.TEAMCITY_VERSION) {
if (HAS_CLEAN) {
project(':clean') {
evaluationDependsOn(':mcp')
apply plugin: 'net.minecraftforge.gradle.patcher'
@@ -890,7 +891,7 @@ project(':fmlonly') {
}
project(':forge') {
evaluationDependsOn(System.env.TEAMCITY_VERSION ? ':mcp' : ':clean')
evaluationDependsOn(HAS_CLEAN ? ':clean' : ':mcp')
apply plugin: 'java-library'
apply plugin: 'net.minecraftforge.gradle.patcher'
apply plugin: 'org.cadixdev.licenser'
@@ -970,18 +971,14 @@ project(':forge') {
patcher {
excs.from file("$rootDir/src/main/resources/forge.exc")
parent = project(System.env.TEAMCITY_VERSION ? ':mcp' : ':clean')
parent = project(HAS_CLEAN ? ':clean' : ':mcp')
patches = file("$rootDir/patches/minecraft")
patchedSrc = file('src/main/java')
srgPatches = true
accessTransformers.from file("$rootDir/src/main/resources/META-INF/accesstransformer.cfg")
sideAnnotationStrippers.from file("$rootDir/src/main/resources/forge.sas")
// the clean project provides these. if we're not using it, we need to provide them ourselves
if (System.env.TEAMCITY_VERSION) {
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
mcVersion = MC_VERSION
}
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
mcVersion = MC_VERSION
runs {
forge_client {
@@ -1635,6 +1632,12 @@ project(':forge') {
}
}
final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) {
gradleVersion = "9.8.2"
scriptFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradlew")
jarFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradle/wrapper/gradle-wrapper.jar")
}
task makeMdk(type: Zip) {
archiveBaseName = project.name
archiveClassifier = 'mdk'
@@ -1644,8 +1647,9 @@ project(':forge') {
from rootProject.file('gradlew')
from rootProject.file('gradlew.bat')
from extraTxts
from(rootProject.file('gradle/')){
into('gradle/')
into('gradle/wrapper/') {
from mdkGradleWrapper.map(Wrapper.&getJarFile)
from mdkGradleWrapper.map(Wrapper.&getPropertiesFile)
}
from(rootProject.file('mdk/')){
rootProject.file('mdk/gitignore.txt').eachLine{
@@ -1803,7 +1807,7 @@ project(':forge') {
//evaluationDependsOnChildren()
task setup() { //These must be strings so that we can do lazy resolution. Else we need evaluationDependsOnChildren above
if (!System.env.TEAMCITY_VERSION)
if (HAS_CLEAN)
dependsOn ':clean:extractMapped'
if (symlinkValid)
dependsOn ':fmlonly:extractMapped'
@@ -1818,21 +1822,9 @@ task doChecks {
if (symlinkValid)
project.gradle.startParameter.excludedTaskNames.add(':fmlonly:genPatches')
if (!System.env.TEAMCITY_VERSION)
if (HAS_CLEAN)
project.gradle.startParameter.excludedTaskNames.add(':clean:genPatches')
//Also output FMLOnly when building a version.
if (System.env.TEAMCITY_VERSION) {
//Only setup the CI environment if and only if the environment variables are set.
tasks.configureTeamCity {
doLast {
println "##teamcity[buildNumber '${project(':forge').version}']"
println "##teamcity[setParameter name='env.PUBLISHED_JAVA_ARTIFACT_VERSION' value='${project(':forge').version}']"
println "##teamcity[setParameter name='env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION' value='${project(':fmlonly').version}']"
}
}
}
idea {
module {
// IntelliJ IDEA does not do this by itself anymore...
+3 -3
View File
@@ -4,9 +4,9 @@ repositories {
}
dependencies {
implementation 'org.ow2.asm:asm:9.5'
implementation 'org.ow2.asm:asm-tree:9.5'
implementation 'net.minecraftforge:srgutils:0.5.+'
implementation 'org.ow2.asm:asm:9.9.1'
implementation 'org.ow2.asm:asm-tree:9.9.1'
implementation 'net.minecraftforge:srgutils:0.6.5'
implementation 'commons-io:commons-io:2.12.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
@@ -202,7 +202,7 @@ public class FMLLoader
public static void addAccessTransformer(Path atPath, ModFile modName)
{
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
LOGGER.debug(SCAN, "Adding Access Transformer in {} in {}", atPath, modName.getFilePath());
accessTransformer.offerResource(atPath, modName.getFileName());
}
@@ -5,6 +5,8 @@
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.apache.logging.log4j.LogManager;
@@ -23,28 +25,44 @@ public class ImmediateWindowHandler {
private static ProgressMeter earlyProgress;
public static void load(final String launchTarget, final String[] arguments) {
final var serviceLayer = Launcher.INSTANCE.findLayerManager()
.flatMap(manager -> manager.getLayer(IModuleLayerManager.Layer.SERVICE))
.orElse(null);
final var providerName = FMLConfig.getConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER);
if (!List.of("forgeclient", "forgeclientuserdev", "forgeclientdev").contains(launchTarget)) {
provider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider not loading because launch target is {}", launchTarget);
} else if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_CONTROL)) {
provider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider not loading because splash screen is disabled");
} else if (providerName == null) {
LOGGER.info("ImmediateWindowProvider not loading because splash screen provider is null");
} else if (serviceLayer == null) {
LOGGER.error("Failed to find service layer for ImmediateWindowProvider");
} else {
final var providername = FMLConfig.getConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER);
LOGGER.info("Loading ImmediateWindowProvider {}", providername);
final var maybeProvider = ServiceLoader.load(ImmediateWindowProvider.class)
.stream()
.map(ServiceLoader.Provider::get)
.filter(p -> Objects.equals(p.name(), providername))
.findFirst();
provider = maybeProvider.or(() -> {
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
return Optional.of(new DummyProvider());
}).orElseThrow();
LOGGER.info("Loading ImmediateWindowProvider {}", providerName);
for (var itr = ServiceLoader.load(serviceLayer, ImmediateWindowProvider.class).iterator(); itr.hasNext(); ) {
try {
var srvc = itr.next();
if (providerName.equals(srvc.name())) {
provider = new Wrapper(srvc);
break;
}
} catch (ServiceConfigurationError e) {
LOGGER.error("Failed to initalize ImmediateWindowProvider Service", e);
}
}
if (provider == null)
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providerName);
}
// Only update config if the provider isn't the dummy provider
if (!Objects.equals(provider.name(), "dummyprovider"))
if (provider != null)
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER, provider.name());
else
provider = new DummyProvider();
FMLLoader.progressWindowTick = provider.initialize(arguments);
earlyProgress = StartupNotificationManager.addProgressBar("EARLY", 0);
earlyProgress.label("Bootstrapping Minecraft");
@@ -78,6 +96,7 @@ public class ImmediateWindowHandler {
public static String getGLVersion() {
return provider.getGLVersion();
}
public static void updateProgress(final String message) {
earlyProgress.label(message);
}
@@ -156,4 +175,94 @@ public class ImmediateWindowHandler {
// NOOP
}
}
// We create a wrapper because we want to log errors to the log files, which not all paths of ModLauncher do. Little dirty, but works for now.
private static class Wrapper implements ImmediateWindowProvider {
private final ImmediateWindowProvider delegate;
private Wrapper(ImmediateWindowProvider delegate) {
this.delegate = delegate;
}
public String name() {
try {
return delegate.name();
} catch (Throwable t) {
LOGGER.error("Failed to call provider.name", t);
throw t;
}
}
public Runnable initialize(String[] arguments) {
try {
return delegate.initialize(arguments);
} catch (Throwable t) {
LOGGER.error("Failed to call provider.initialize", t);
throw t;
}
}
public void updateFramebufferSize(IntConsumer width, IntConsumer height) {
try {
delegate.updateFramebufferSize(width, height);
} catch (Throwable t) {
LOGGER.error("Failed to call provider.updateFramebufferSize", t);
throw t;
}
}
public long setupMinecraftWindow(IntSupplier width, IntSupplier height, Supplier<String> title, LongSupplier monitor) {
try {
return delegate.setupMinecraftWindow(width, height, title, monitor);
} catch (Throwable t) {
LOGGER.error("Failed to call provider.setupMinecraftWindow", t);
throw t;
}
}
public boolean positionWindow(Optional<Object> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
try {
return delegate.positionWindow(monitor, widthSetter, heightSetter, xSetter, ySetter);
} catch (Throwable t) {
LOGGER.error("Failed to call provider.positionWindow", t);
throw t;
}
}
public <T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade) {
try {
return delegate.loadingOverlay(mc, ri, ex, fade);
} catch (Throwable t) {
LOGGER.error("Failed to call provider.loadingOverlay", t);
throw t;
}
}
public void updateModuleReads(ModuleLayer layer) {
try {
delegate.updateModuleReads(layer);
} catch (Throwable t) {
LOGGER.error("Failed to call provider.updateModuleReads", t);
throw t;
}
}
public void periodicTick() {
try {
delegate.periodicTick();
} catch (Throwable t) {
LOGGER.error("Failed to call provider.periodicTick", t);
throw t;
}
}
public String getGLVersion() {
try {
return delegate.getGLVersion();
} catch (Throwable t) {
LOGGER.error("Failed to call provider.getGLVersion", t);
throw t;
}
}
}
}
@@ -5,12 +5,14 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.slf4j.Logger;
import java.net.URL;
import java.nio.file.Files;
@@ -30,6 +32,7 @@ import java.util.stream.Collectors;
*/
public class LoadingModList
{
private static final Logger LOGGER = LogUtils.getLogger();
private static LoadingModList INSTANCE;
private final List<ModFileInfo> modFiles;
private final List<ModInfo> sortedList;
@@ -78,9 +81,22 @@ public class LoadingModList
public void addAccessTransformers()
{
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(mod -> mod.getAccessTransformer().ifPresent(path -> FMLLoader.addAccessTransformer(path, mod)));
var errors = new ArrayList<EarlyLoadingException.ExceptionData>();
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
for (Path at : mod.getAccessTransformers()) {
if (!Files.exists(at)) {
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
errors.add(new EarlyLoadingException.ExceptionData(message));
LOGGER.error(message);
} else {
FMLLoader.addAccessTransformer(at, mod);
}
}
}
if (!errors.isEmpty()) {
preLoadErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
}
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
@@ -28,7 +28,8 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
private static final Set<String> SERVICES = Set.of(
"cpw.mods.modlauncher.api.ITransformationService",
"net.minecraftforge.forgespi.locating.IModLocator",
"net.minecraftforge.forgespi.locating.IDependencyLocator"
"net.minecraftforge.forgespi.locating.IDependencyLocator",
"net.minecraftforge.fml.loading.ImmediateWindowProvider"
);
@Override
@@ -34,8 +34,7 @@ import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class RuntimeDistCleaner implements ILaunchPluginService
{
public final class RuntimeDistCleaner implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
private static String DIST;
@@ -61,7 +60,7 @@ public class RuntimeDistCleaner implements ILaunchPluginService
{
unpack(classNode.visibleAnnotations).stream()
.filter(ann->Objects.equals(ann.desc, ONLYIN))
.filter(ann->ann.values.indexOf("_interface") != -1)
.filter(ann-> ann.values.contains("_interface"))
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
.forEach(intf -> {
@@ -151,7 +150,7 @@ public class RuntimeDistCleaner implements ILaunchPluginService
{
return unpack(anns).stream().
filter(ann->Objects.equals(ann.desc, ONLYIN)).
filter(ann->ann.values.indexOf("_interface") == -1).
filter(ann-> !ann.values.contains("_interface")).
anyMatch(ann -> !Objects.equals(((String[])ann.values.get(ann.values.indexOf("value")+1))[1], side));
}
@@ -177,6 +176,10 @@ public class RuntimeDistCleaner implements ILaunchPluginService
if (internalName.startsWith("net/minecraftforge/"))
return NAY;
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
if (FMLEnvironment.dist.isClient() && (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/")))
return NAY;
return YAY;
}
@@ -28,6 +28,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Collections;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@@ -60,6 +62,7 @@ public class ModFile implements IModFile {
private CompletableFuture<ModFileScanData> futureScanResult;
private List<CoreModFile> coreMods;
private Path accessTransformer;
private List<Path> accessTransformers = Collections.emptyList();
static final Attributes.Name TYPE = new Attributes.Name("FMLModType");
private SecureJar.Status securityStatus;
@@ -104,7 +107,11 @@ public class ModFile implements IModFile {
}
public Optional<Path> getAccessTransformer() {
return Optional.ofNullable(Files.exists(accessTransformer) ? accessTransformer : null);
return Optional.ofNullable(accessTransformer);
}
public List<Path> getAccessTransformers() {
return accessTransformers;
}
public boolean identifyMods() {
@@ -113,7 +120,28 @@ public class ModFile implements IModFile {
LOGGER.debug(LogMarkers.LOADING,"Loading mod file {} with languages {}", this.getFilePath(), this.modFileInfo.requiredLanguageLoaders());
this.coreMods = ModFileParser.getCoreMods(this);
this.coreMods.forEach(mi-> LOGGER.debug(LogMarkers.LOADING,"Found coremod {}", mi.getPath()));
this.accessTransformer = findResource("META-INF", "accesstransformer.cfg");
List<String> cfg;
// Note: Some mods may have invalid tomls copied from other projects.
// Unfortunately we must protect against those landmines.
try {
cfg = this.modFileInfo.getConfig().<List<String>>getConfigElement("accessTransformers").orElse(null);
} catch (Exception e) {
cfg = null;
LOGGER.warn("{} contains an invalid 'accessTransformers' TOML entry. Should be e.g. accessTransformers = [\"META-INF/accesstransformer.cfg\", \"META-INF/extra_at.cfg\"] or accessTransformers = [] for no ATs. Falling back to default.", this.getFileName());
}
if (cfg == null) {
var path = findResource("META-INF", "accesstransformer.cfg");
if (Files.exists(path)) {
this.accessTransformer = path;
this.accessTransformers = List.of(path);
}
} else if (!cfg.isEmpty()) {
var paths = new ArrayList<Path>(cfg.size());
for (var path : cfg) {
paths.add(getSecureJar().getPath(path.replace('\\', '/')));
}
this.accessTransformers = List.copyOf(paths);
}
return true;
}
@@ -6,6 +6,7 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.mojang.logging.LogUtils;
@@ -13,6 +14,7 @@ import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import org.slf4j.Logger;
import java.io.IOException;
@@ -22,7 +24,6 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ModFileParser {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -41,10 +42,15 @@ public class ModFileParser {
}
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
try {
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
} catch (ParsingException e) {
LOGGER.error("Mod candidate {} contains a corrupt or misconfigured toml.", modFile.getFileName());
throw new ModFileLoadingException("Mod candidate " + modFile.getFileName() + " contains a corrupt or misconfigured toml.");
}
}
protected static List<CoreModFile> getCoreMods(final ModFile modFile) {
+1
View File
@@ -1,4 +1,5 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
+93 -132
View File
@@ -1,156 +1,87 @@
// Don't see a feature you're wanting to use?
// See our MDKExamples repository for minimal examples for features like JarInJar
// https://github.com/MinecraftForge/MDKExamples
plugins {
id 'eclipse'
id 'idea'
id 'java'
id 'maven-publish'
id 'net.minecraftforge.gradle' version '[6.0,6.2)'
id 'net.minecraftforge.gradle' version '[7.0,8)'
id 'net.minecraftforge.renamer' version '1.1.8+' // Required on all versions which use SRG maps at runtime.
}
version = mod_version
group = mod_group_id
base {
archivesName = mod_id
}
version = '1.0'
group = 'com.example'
base.archivesName = 'examplemod'
// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17.
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}"
println "Java: ${providers.systemProperty('java.version').get()}, " +
"JVM: ${providers.systemProperty('java.vm.version').get()} (${providers.systemProperty('java.vendor').get()}), " +
"Arch: ${providers.systemProperty('os.arch').get()}"
minecraft {
// The mappings can be changed at any time and must be in the following format.
// Channel: Version:
// official MCVersion Official field/method names from Mojang mapping files
// parchment YYYY.MM.DD-MCVersion Open community-sourced parameter names and javadocs layered on top of official
//
// You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.
// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
//
// Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge
// Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started
//
// Use non-default mappings at your own risk. They may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
mappings channel: mapping_channel, version: mapping_version
// Declare which mappings to use
mappings channel: mapping_channel, version: minecraft_version
// When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game.
// In most cases, it is not necessary to enable.
// enableEclipsePrepareRuns = true
// enableIdeaPrepareRuns = true
// Access Transformer files may now be automatically detected, or explicitly declared.
// Setting the below flag will cause ForgeGradle to look for META-INF/accesstransformer.cfg
// Alternatively, provide it a file(<path>) or files(<paths>) for explicitly declared files. If you use any
// explicitly declared access transformers, all transformers must be also declared in the mods.toml
// accessTransformer = true
// This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game.
// It is REQUIRED to be set to true for this template to function.
// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html
copyIdeResources = true
// When true, this property will add the folder name of all declared run configurations to generated IDE run configurations.
// The folder name can be set on a run configuration using the "folderName" property.
// By default, the folder name of a run configuration is the name of the Gradle project containing it.
// generateRunFolders = true
// This property enables access transformers for use in development.
// They will be applied to the Minecraft artifact.
// The access transformer file can be anywhere in the project.
// However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge.
// This default location is a best practice to automatically put the file in the right place in the final jar.
// See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information.
// accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
// applies to all the run configs below
configureEach {
workingDirectory project.file('run')
workingDir.convention layout.projectDirectory.dir('run')
// Recommended logging data for a userdev environment
// The markers can be added/remove as needed separated by commas.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
//systemProperty 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
"${mod_id}" {
source sourceSets.main
}
}
//args "-mixin.config=${mod_id}.mixins.json"
}
client {
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
property 'forge.enabledGameTestNamespaces', mod_id
register('client') {
systemProperty 'forge.enabledGameTestNamespaces', mod_id
}
server {
property 'forge.enabledGameTestNamespaces', mod_id
register('server') {
systemProperty 'forge.enabledGameTestNamespaces', mod_id
args '--nogui'
}
// This run config launches GameTestServer and runs all registered gametests, then exits.
// By default, the server will crash when no gametests are provided.
// The gametest system is also enabled by default for other run configs under the /test command.
gameTestServer {
property 'forge.enabledGameTestNamespaces', mod_id
register('gameTestServer') {
systemProperty 'forge.enabledGameTestNamespaces', mod_id
}
data {
// example of overriding the workingDirectory set in configureEach above
workingDirectory project.file('run-data')
register('data') {
workingDir = layout.projectDirectory.dir('run-data')
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
args '--mod', mod_id, '--all', '--output', layout.projectDirectory.dir('src/generated/resources'), '--existing', layout.projectDirectory.dir('src/main/resources')
}
}
}
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
repositories {
// Put repositories for dependencies here
// ForgeGradle automatically adds the Forge maven and Maven Central for you
// If you have mod jar dependencies in ./libs, you can declare them as a repository like so.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:flat_dir_resolver
// flatDir {
// dir 'libs'
// }
minecraft.mavenizer(it)
maven fg.forgeMaven
maven fg.minecraftLibsMaven
// Needed for JEI
// maven { url = 'https://maven.blamejared.com/' }
}
dependencies {
// Specify the version of Minecraft to use.
// Any artifact can be supplied so long as it has a "userdev" classifier artifact and is a compatible patcher artifact.
// The "userdev" classifier will be requested and setup by ForgeGradle.
// If the group id is "net.minecraft" and the artifact id is one of ["client", "server", "joined"],
// then special handling is done to allow a setup of a vanilla dependency without the use of an external repository.
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
// Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}")
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}")
// runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}")
// Example mod dependency using a mod jar from ./libs with a flat dir repository
// This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar
// The group id is ignored when searching -- in this case, it is "blank"
// implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}")
// For more info:
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
// Minecraft/Forge itself
implementation minecraft.dependency("net.minecraftforge:forge:$minecraft_version-$forge_version")
// A 3rd party mod, which needs to be deobfuscated from SRG names to the mappings we're using
// implementation renamer.dependency('mezz.jei:jei-1.20.1-forge:15.20.0.129')
// annotationProcessor 'org.spongepowered:mixin:0.8.7:processor'
}
// This block of code expands all declared replace properties in the specified resource targets.
// A missing property will result in an error. Properties are expanded using ${} Groovy notation.
// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments.
// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html
tasks.named('processResources', ProcessResources).configure {
tasks.named('processResources', ProcessResources) {
var replaceProperties = [
minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range,
forge_version: forge_version, forge_version_range: forge_version_range,
@@ -158,15 +89,15 @@ tasks.named('processResources', ProcessResources).configure {
mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version,
mod_authors: mod_authors, mod_description: mod_description,
]
inputs.properties replaceProperties
inputs.property('replaceProperties', replaceProperties)
filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) {
expand replaceProperties + [project: project]
expand replaceProperties
}
}
// Example for how to get properties into the manifest for reading at runtime.
tasks.named('jar', Jar).configure {
tasks.named('jar', Jar) {
manifest {
attributes([
'Specification-Title' : mod_id,
@@ -174,34 +105,64 @@ tasks.named('jar', Jar).configure {
'Specification-Version' : '1', // We are version 1 of ourselves
'Implementation-Title' : project.name,
'Implementation-Version' : project.jar.archiveVersion,
'Implementation-Vendor' : mod_authors,
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
'Implementation-Vendor' : mod_authors
])
//attributes['MixinConfigs'] = "${mod_id}.mixins.json"
}
// This is the preferred method to reobfuscate your jar file
finalizedBy 'reobfJar'
// This artifact will not be renamed. While this will not launch in production
// the artifact may be helpful for other developers if your mod is used as a library.
// By assigning this archive classifier, jar file name will end in '-unobfuscated'
// e.g. 'examplemod-1.0-unobfuscated.jar'
archiveClassifier = 'unobfuscated'
}
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing:
// tasks.named('publish').configure {
// dependsOn 'reobfJar'
// }
// Set the default mappings file to our default minecraft dependency
// This must be done after `minecraft.dependency('coords')` has been done so that gradle knows what dependency we want
// This mapping will be used by default for all renamer tasks, including deobfuscating dependencies
// You can still override each specific task, this is just to save repeating yourself in your buildscript.
renamer.mappings.from minecraft.dependency.toSrgFile
// If you are choosing to use mixins, you must give Renamer the additional information for it to handle the refmaps.
//renamer.enableMixinRefmaps {
// config "${mod_id}.mixins.json"
// refMap "${mod_id}.refmap.json"
//}
// Creates a task named 'renameJar'
renamer.classes(tasks.named('jar', Jar)) {
// This artifact will be remapped according to the provided mappings file
// Setting the classifier to '' will attach no extra details to the file name.
// e.g. 'examplemod-1.0.jar'
archiveClassifier = ''
// Unless set globally like above, You need to point to the mappings you wish to apply, typically this is
// the Mapped names to SRG for older versions.
// ForgeGradle/Mavenizer generate these files for the dependencies you declare. So you can use the helper.
// Or you can specify the file or dependency if you host them yourself.
// map.from minecraft.dependency.toSrgFile
// The mixin AP does not include all annotated entries, instead containing some in the augmented mappings.
// So, if you are using mixins or have dependencies which use them, uncomment the below line to tell renamer
// that the generated mappings are available. See the MDKExample for more details.
// mappings renamer.mixin.generatedMappings
}
// Example configuration to allow publishing using the maven-publish plugin
// We recommend publishing all of your artifacts to publicly accessible maven so that others can easily consume them as dependencies
publishing {
publications {
register('mavenJava', MavenPublication) {
artifact jar
}
}
repositories {
maven {
url "file://${project.projectDir}/mcmodsrepo"
}
// Publishing to a local folder makes it easy to see exactly what you would publish to a real server
maven { url = layout.projectDirectory.dir('repo').asFile.toURI() }
}
publications.register('mavenJava', MavenPublication) {
from components.java // Publish the normal jar
artifact renameJar // Publish the renamed jar in addition
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
// IntelliJ no longer downloads javadocs and sources by default, this tells Gradle to force IntelliJ to do it.
idea.module { downloadJavadoc = downloadSources = true }
+16 -13
View File
@@ -1,25 +1,29 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.configuration-cache=true
org.gradle.configuration-cache.parallel=true
org.gradle.configuration-cache.problems=warn
net.minecraftforge.gradle.merge-source-sets=true
## Environment Properties
# The Minecraft version must agree with the Forge version to get a valid artifact
minecraft_version=@MC_VERSION@
minecraft_version=1.20.1
# The Minecraft version range can use any release version of Minecraft as bounds.
# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly
# as they do not follow standard versioning conventions.
minecraft_version_range=[@MC_VERSION@,@MC_NEXT_VERSION@)
minecraft_version_range=[1.20.1, 1.20.2)
# The Forge version must agree with the Minecraft version to get a valid artifact
forge_version=@FORGE_VERSION@
# The Forge version range can use any version of Forge as bounds or match the loader version range
forge_version_range=[@FORGE_SPEC_VERSION@,)
forge_version_range=[47,)
# The loader version range can only use the major version of Forge/FML as bounds
loader_version_range=[@FORGE_SPEC_VERSION@,)
loader_version_range=[47,)
# The mapping channel to use for mappings.
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
# The default set of supported mapping channels are ["official", "parchment"].
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
#
# | Channel | Version | |
@@ -31,12 +35,11 @@ loader_version_range=[@FORGE_SPEC_VERSION@,)
# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
#
# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge.
# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started
mapping_channel=@MAPPING_CHANNEL@
# Unlike in FG6, Parchment no longer requires the Librarian plugin to be set up.
mapping_channel=official
# The mapping version to query from the mapping channel.
# This must match the format required by the mapping channel.
mapping_version=@MAPPING_VERSION@
mapping_version=1.20.1
## Mod Properties
+3 -11
View File
@@ -1,13 +1,5 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven {
name = 'MinecraftForge'
url = 'https://maven.minecraftforge.net/'
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = 'examplemod'
@@ -41,6 +41,12 @@ authors="${mod_authors}" #optional
# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself.
#displayTest="MATCH_VERSION" # if nothing is specified, MATCH_VERSION is the default when clientSideOnly=false, otherwise IGNORE_ALL_VERSION when clientSideOnly=true (#optional)
# Access Transformers
# Optional, if not specified Forge will attempt to find the default META-INF/accesstransformer.cfg and silently continue if not found. May default to an empty list in a future MC.
# If you specify AT path strings in this list, Forge will attempt to load each of them and throw errors for any that aren't found.
# Specifying an empty list when your mod has no ATs is recommended to opt-out of the default for slightly better loading performance.
#accessTransformers = []
# The description text for the mod (multi line!) (#mandatory)
description='''${mod_description}'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
@@ -88,12 +88,15 @@
}
public void m_105287_() {
@@ -262,7 +_,7 @@
@@ -262,7 +_,10 @@
private boolean m_105281_(BlockPos p_105282_) {
ItemStack itemstack = this.f_105189_.f_91074_.m_21205_();
- return p_105282_.equals(this.f_105191_) && ItemStack.m_150942_(itemstack, this.f_105192_);
+ return p_105282_.equals(this.f_105191_) && ItemStack.m_150942_(itemstack, this.f_105192_) && !f_105192_.shouldCauseBlockBreakReset(itemstack);
+ // FORGE: Remove this method call, but keep the bytecode intact for hacks that target it
+ if (Boolean.FALSE)
+ ItemStack.m_150942_(itemstack, this.f_105192_);
+ return p_105282_.equals(this.f_105191_) && !f_105192_.shouldCauseBlockBreakReset(itemstack);
}
private void m_105297_() {
@@ -0,0 +1,11 @@
--- a/net/minecraft/commands/SharedSuggestionProvider.java
+++ b/net/minecraft/commands/SharedSuggestionProvider.java
@@ -82,7 +_,7 @@
if (m_82949_(p_82946_, s)) {
p_82948_.accept(t);
}
- } else if (m_82949_(p_82946_, resourcelocation.m_135827_()) || resourcelocation.m_135827_().equals("minecraft") && m_82949_(p_82946_, resourcelocation.m_135815_())) {
+ } else if (m_82949_(p_82946_, resourcelocation.m_135827_()) || m_82949_(p_82946_, resourcelocation.m_135815_())) { // Forge: Removed "minecraft" namespace requirement for path searching to allow modded path suggestions to show up
p_82948_.accept(t);
}
}
@@ -44,12 +44,20 @@
}
public ResourceLocation m_132042_() {
@@ -61,4 +_,8 @@
@@ -60,5 +_,16 @@
public FriendlyByteBuf m_132045_() {
return new FriendlyByteBuf(this.f_132030_.copy());
}
+ }
+
+ @Override public int getIndex() { return Integer.MAX_VALUE; }
+ @Override public ResourceLocation getName() { return m_132042_(); }
+ @Override public FriendlyByteBuf getInternalData() { return m_132045_(); }
+ @Override public FriendlyByteBuf getInternalData() {
+ // Create a heap copy to pass off to other consumers because we could be reading from a pooled buffer that never gets released.
+ // Using a heap buffer means that it will be released when the GC determines its free.
+ // See: https://github.com/MinecraftForge/MinecraftForge/issues/10861
+ var heapCopy = io.netty.buffer.Unpooled.buffer(this.f_132030_.readableBytes());
+ heapCopy.writeBytes(this.f_132030_.slice()); // We use a slice to not mess with the reader index, just in case someone else wants to touch the buffer later
+ return new FriendlyByteBuf(heapCopy);
}
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -124,7 +_,11 @@
@@ -124,7 +_,14 @@
}
public void m_214168_(BlockPos p_215120_, ServerboundPlayerActionPacket.Action p_215121_, Direction p_215122_, int p_215123_, int p_215124_) {
@@ -9,7 +9,10 @@
+ if (event.isCanceled() || (!this.m_9295_() && event.getResult() == net.minecraftforge.eventbus.api.Event.Result.DENY)) {
+ return;
+ }
+ if (!this.f_9245_.canReach(p_215120_, 1.5)) { // Vanilla check is eye-to-center distance < 6, so padding is 6 - 4.5 = 1.5
+
+ boolean canReachUnmodded = this.testPlayerCanReach(p_215120_);
+ boolean canReachModded = this.f_9245_.canReach(p_215120_, 1.5); // Vanilla uses ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE which is 6*6, default Block reach is 4.5, so add 1.5 padding
+ if (canReachUnmodded == canReachModded ? !this.f_9245_.canReachRaw(p_215120_, 1.5) : !canReachModded) {
this.m_215125_(p_215120_, false, p_215124_, "too far");
} else if (p_215120_.m_123342_() >= p_215123_) {
this.f_9245_.f_8906_.m_9829_(new ClientboundBlockUpdatePacket(p_215120_, this.f_9244_.m_8055_(p_215120_)));
@@ -21,6 +24,18 @@
blockstate.m_60686_(this.f_9244_, p_215120_, this.f_9245_);
f = blockstate.m_60625_(this.f_9245_, this.f_9245_.m_9236_(), p_215120_);
}
@@ -209,6 +_,11 @@
}
}
+ private boolean testPlayerCanReach(BlockPos pos) {
+ // don't mixin into this
+ return this.f_9245_.canReach(pos, 1.5);
+ }
+
public void m_215116_(BlockPos p_215117_, int p_215118_, String p_215119_) {
if (this.m_9280_(p_215117_)) {
this.m_215125_(p_215117_, true, p_215118_, p_215119_);
@@ -221,7 +_,8 @@
public boolean m_9280_(BlockPos p_9281_) {
@@ -26,12 +26,14 @@
this.f_9743_.m_5810_();
}
@@ -1035,7 +_,7 @@
@@ -1035,7 +_,9 @@
Vec3 vec3 = blockhitresult.m_82450_();
BlockPos blockpos = blockhitresult.m_82425_();
Vec3 vec31 = Vec3.m_82512_(blockpos);
- if (!(this.f_9743_.m_146892_().m_82557_(vec31) > f_215198_)) {
+ if (this.f_9743_.canReach(blockpos, 1.5)) { // Vanilla uses eye-to-center distance < 6, which implies a padding of 1.5
+ boolean canReachUnmodded = this.testPlayerCanReach(blockpos);
+ boolean canReachModded = this.f_9743_.canReach(blockpos, 1.5); // Vanilla uses eye-to-center distance < ServerGamePacketListenerImpl which is 6*6, default block reach is 4.5, so add 1.5 padding
+ if (canReachUnmodded == canReachModded ? this.f_9743_.canReachRaw(blockpos, 1.5) : canReachModded) {
Vec3 vec32 = vec3.m_82546_(vec31);
double d0 = 1.0000001D;
if (Math.abs(vec32.m_7096_()) < 1.0000001D && Math.abs(vec32.m_7098_()) < 1.0000001D && Math.abs(vec32.m_7094_()) < 1.0000001D) {
@@ -44,6 +46,17 @@
InteractionResult interactionresult = this.f_9743_.f_8941_.m_7179_(this.f_9743_, serverlevel, itemstack, interactionhand, blockhitresult);
if (direction == Direction.UP && !interactionresult.m_19077_() && blockpos.m_123342_() >= i - 1 && m_9790_(this.f_9743_, itemstack)) {
Component component = Component.m_237110_("build.tooHigh", i - 1).m_130940_(ChatFormatting.RED);
@@ -1066,6 +_,10 @@
}
}
+ private boolean testPlayerCanReach(BlockPos pos) {
+ return this.f_9743_.canReach(pos, 1.5);
+ }
+
public void m_5760_(ServerboundUseItemPacket p_9932_) {
PacketUtils.m_131359_(p_9932_, this, this.f_9743_.m_284548_());
this.m_215201_(p_9932_.m_238013_());
@@ -1187,10 +_,13 @@
}
@@ -65,7 +78,7 @@
AABB aabb = entity.m_20191_();
- if (aabb.m_272282_(this.f_9743_.m_146892_()) < f_215198_) {
+ if (this.f_9743_.canReach(entity, 3)) { // Vanilla padding is 3.0 (distSq < 6.0 * 6.0)
+ if (this.f_9743_.canReachRaw(entity, 3.0)) { // Vanilla uses MAX_INTERACTION_DISTANCE which is 6*6 default entity reach is 3.0 so add 3.0 padding
p_9866_.m_179617_(new ServerboundInteractPacket.Handler() {
private void m_143678_(InteractionHand p_143679_, ServerGamePacketListenerImpl.EntityInteraction p_143680_) {
ItemStack itemstack = ServerGamePacketListenerImpl.this.f_9743_.m_21120_(p_143679_);
@@ -0,0 +1,18 @@
--- a/net/minecraft/world/Container.java
+++ b/net/minecraft/world/Container.java
@@ -78,10 +_,14 @@
}
static boolean m_272074_(BlockEntity p_273154_, Player p_273222_) {
- return m_271806_(p_273154_, p_273222_, 8);
+ return stillValidBlockEntity(p_273154_, p_273222_, p_273222_.m_21133_(net.minecraftforge.common.ForgeMod.BLOCK_REACH.get()) + 3.5); // Vanilla uses 8, default reach is 4.5 so add 3.5 padding
}
static boolean m_271806_(BlockEntity p_272877_, Player p_272670_, int p_273411_) {
+ return stillValidBlockEntity(p_272877_, p_272670_, (double)p_273411_);
+ }
+
+ static boolean stillValidBlockEntity(BlockEntity p_272877_, Player p_272670_, double p_273411_) {
Level level = p_272877_.m_58904_();
BlockPos blockpos = p_272877_.m_58899_();
if (level == null) {
@@ -422,7 +422,7 @@
});
}
@@ -2980,9 +_,22 @@
@@ -2980,9 +_,31 @@
this.f_19859_ = this.m_146908_();
}
@@ -439,6 +439,15 @@
+ this.updateFluidHeightAndDoFluidPushing(com.google.common.base.Predicates.alwaysTrue());
+ }
+
+ private static final class FluidCalcs {
+ double height = 0.0D;
+ Vec3 direction = Vec3.f_82478_;
+ int count = 0;
+ double height(double height) {
+ return (this.height = this.height >= height ? this.height : height);
+ }
+ }
+
+ public void updateFluidHeightAndDoFluidPushing(Predicate<FluidState> shouldUpdate) {
if (this.m_146899_()) {
- return false;
@@ -446,11 +455,14 @@
} else {
AABB aabb = this.m_20191_().m_82406_(0.001D);
int i = Mth.m_14107_(aabb.f_82288_);
@@ -2997,25 +_,28 @@
Vec3 vec3 = Vec3.f_82478_;
int k1 = 0;
@@ -2994,28 +_,32 @@
double d0 = 0.0D;
boolean flag = this.m_6063_();
boolean flag1 = false;
- Vec3 vec3 = Vec3.f_82478_;
- int k1 = 0;
BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos();
+ it.unimi.dsi.fastutil.objects.Object2ObjectMap<net.minecraftforge.fluids.FluidType, org.apache.commons.lang3.tuple.MutableTriple<Double, Vec3, Integer>> interimCalcs = new it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap<>(net.minecraftforge.fluids.FluidType.SIZE.get() - 1);
+ it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap<net.minecraftforge.fluids.FluidType, FluidCalcs> fluidPushCalcs = null;
for(int l1 = i; l1 < j; ++l1) {
for(int i2 = k; i2 < l; ++i2) {
@@ -464,59 +476,50 @@
if (d1 >= aabb.f_82289_) {
flag1 = true;
- d0 = Math.max(d1 - aabb.f_82289_, d0);
- if (flag) {
+ org.apache.commons.lang3.tuple.MutableTriple<Double, Vec3, Integer> interim = interimCalcs.computeIfAbsent(fluidType, t -> org.apache.commons.lang3.tuple.MutableTriple.of(0.0D, Vec3.f_82478_, 0));
+ interim.setLeft(Math.max(d1 - aabb.f_82289_, interim.getLeft()));
+ if (this.isPushedByFluid(fluidType)) {
+ if (fluidPushCalcs == null)
+ fluidPushCalcs = new it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap<net.minecraftforge.fluids.FluidType, FluidCalcs>(2);
+ var interim = fluidPushCalcs.computeIfAbsent(fluidType, type -> new FluidCalcs());
+ d0 = interim.height(d1 - aabb.f_82289_);
+ flag = this.isPushedByFluid(fluidType);
if (flag) {
Vec3 vec31 = fluidstate.m_76179_(this.m_9236_(), blockpos$mutableblockpos);
- if (d0 < 0.4D) {
- vec31 = vec31.m_82490_(d0);
+ if (interim.getLeft() < 0.4D) {
+ vec31 = vec31.m_82490_(interim.getLeft());
if (d0 < 0.4D) {
vec31 = vec31.m_82490_(d0);
}
- vec3 = vec3.m_82549_(vec31);
- ++k1;
+ interim.setMiddle(interim.getMiddle().m_82549_(vec31));
+ interim.setRight(interim.getRight() + 1);
+ interim.direction = interim.direction.m_82549_(vec31);
+ interim.count++;
}
}
}
@@ -3023,27 +_,28 @@
@@ -3023,6 +_,10 @@
}
}
- if (vec3.m_82553_() > 0.0D) {
- if (k1 > 0) {
- vec3 = vec3.m_82490_(1.0D / (double)k1);
+ interimCalcs.forEach((fluidType, interim) -> {
+ if (interim.getMiddle().m_82553_() > 0.0D) {
+ if (interim.getRight() > 0) {
+ interim.setMiddle(interim.getMiddle().m_82490_(1.0D / (double)interim.getRight()));
}
if (!(this instanceof Player)) {
- vec3 = vec3.m_82541_();
+ interim.setMiddle(interim.getMiddle().m_82541_());
+ if (fluidPushCalcs == null) return;
+ fluidPushCalcs.forEach((fluidType, interim) -> {
+ var vec3 = interim.direction;
+ var k1 = interim.count;
if (vec3.m_82553_() > 0.0D) {
if (k1 > 0) {
vec3 = vec3.m_82490_(1.0D / (double)k1);
@@ -3033,6 +_,7 @@
}
Vec3 vec32 = this.m_20184_();
- vec3 = vec3.m_82490_(p_204033_ * 1.0D);
+ interim.setMiddle(interim.getMiddle().m_82490_(this.getFluidMotionScale(fluidType)));
+ var p_204033_ = this.getFluidMotionScale(fluidType);
vec3 = vec3.m_82490_(p_204033_ * 1.0D);
double d2 = 0.003D;
- if (Math.abs(vec32.f_82479_) < 0.003D && Math.abs(vec32.f_82481_) < 0.003D && vec3.m_82553_() < 0.0045000000000000005D) {
- vec3 = vec3.m_82541_().m_82490_(0.0045000000000000005D);
+ if (Math.abs(vec32.f_82479_) < 0.003D && Math.abs(vec32.f_82481_) < 0.003D && interim.getMiddle().m_82553_() < 0.0045000000000000005D) {
+ interim.setMiddle(interim.getMiddle().m_82541_().m_82490_(0.0045000000000000005D));
}
- this.m_20256_(this.m_20184_().m_82549_(vec3));
+ this.m_20256_(this.m_20184_().m_82549_(interim.getMiddle()));
if (Math.abs(vec32.f_82479_) < 0.003D && Math.abs(vec32.f_82481_) < 0.003D && vec3.m_82553_() < 0.0045000000000000005D) {
@@ -3042,8 +_,8 @@
this.m_20256_(this.m_20184_().m_82549_(vec3));
}
- this.f_19799_.put(p_204032_, d0);
- return flag1;
+ this.setFluidTypeHeight(fluidType, interim.getLeft());
+ this.setFluidTypeHeight(fluidType, interim.height);
+ });
}
}
@@ -43,3 +43,16 @@
crashreportcategory.m_128159_("Item ID", Item.m_41393_(p_36042_.m_41720_()));
crashreportcategory.m_128159_("Item data", p_36042_.m_41773_());
crashreportcategory.m_128165_("Item name", () -> {
@@ -530,7 +_,11 @@
if (this.f_35978_.m_213877_()) {
return false;
} else {
- return !(p_36009_.m_20280_(this.f_35978_) > 64.0D);
+ // Vanilla uses 64 which is 8*8. Default reach is 3.0 so add 5.0 padding
+ // magic number so required Mixins can still target here
+ double reachOld = 64.0;
+ double reach = p_36009_.m_21133_(net.minecraftforge.common.ForgeMod.ENTITY_REACH.get()) + 5.0;
+ return !(p_36009_.m_20280_(this.f_35978_) > reach * reach);
}
}
@@ -251,15 +251,18 @@
}
float f4 = 0.0F;
@@ -1121,8 +_,9 @@
@@ -1121,8 +_,12 @@
if (flag3) {
float f3 = 1.0F + EnchantmentHelper.m_44821_(this) * f;
- for(LivingEntity livingentity : this.m_9236_().m_45976_(LivingEntity.class, p_36347_.m_20191_().m_82377_(1.0D, 0.25D, 1.0D))) {
- if (livingentity != this && livingentity != p_36347_ && !this.m_7307_(livingentity) && (!(livingentity instanceof ArmorStand) || !((ArmorStand)livingentity).m_31677_()) && this.m_20280_(livingentity) < 9.0D) {
+ for(LivingEntity livingentity : this.m_9236_().m_45976_(LivingEntity.class, this.m_21120_(InteractionHand.MAIN_HAND).getSweepHitBox(this, p_36347_))) {
+ double entityReachSq = Mth.m_144952_(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_ && !this.m_7307_(livingentity) && (!(livingentity instanceof ArmorStand) || !((ArmorStand)livingentity).m_31677_()) && this.m_20280_(livingentity) < entityReachSq) {
+ // 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.
+ // magic number so required Mixins can still target here
+ var reachOld = 9.0;
+ var reach = this.m_21133_(net.minecraftforge.common.ForgeMod.ENTITY_REACH.get());
+ if (livingentity != this && livingentity != p_36347_ && !this.m_7307_(livingentity) && (!(livingentity instanceof ArmorStand) || !((ArmorStand)livingentity).m_31677_()) && this.m_20280_(livingentity) < reach * reach) {
livingentity.m_147240_((double)0.4F, (double)Mth.m_14031_(this.m_146908_() * ((float)Math.PI / 180F)), (double)(-Mth.m_14089_(this.m_146908_() * ((float)Math.PI / 180F))));
livingentity.m_6469_(this.m_269291_().m_269075_(this), f3);
}
@@ -10,3 +10,11 @@
if (p_219950_ != null) {
lootparams$builder.m_287239_(p_219950_.m_36336_()).m_287286_(LootContextParams.f_81455_, p_219950_);
}
@@ -171,6 +_,6 @@
}
default boolean m_219954_(Player p_219955_) {
- return !this.m_213877_() && this.m_20182_().m_82509_(p_219955_.m_20182_(), 8.0D);
+ return !this.m_213877_() && this.m_20182_().m_82509_(p_219955_.m_20182_(), p_219955_.m_21133_(net.minecraftforge.common.ForgeMod.ENTITY_REACH.get()) + 5.0); // Vanilla uses 8 default reach is 3.0 so add 5.0 padding
}
}
@@ -1,5 +1,18 @@
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -67,7 +_,11 @@
protected static boolean m_38889_(ContainerLevelAccess p_38890_, Player p_38891_, Block p_38892_) {
return p_38890_.m_39299_((p_38916_, p_38917_) -> {
- return !p_38916_.m_8055_(p_38917_).m_60713_(p_38892_) ? false : p_38891_.m_20275_((double)p_38917_.m_123341_() + 0.5D, (double)p_38917_.m_123342_() + 0.5D, (double)p_38917_.m_123343_() + 0.5D) <= 64.0D;
+ // Vanilla uses 64 which is 8*8. Default reach is 4.5 so add 3.5 padding
+ // magic number so required Mixins can still target here
+ double reachOld = 64.0;
+ double reach = p_38891_.m_21133_(net.minecraftforge.common.ForgeMod.BLOCK_REACH.get()) + 3.5;
+ return !p_38916_.m_8055_(p_38917_).m_60713_(p_38892_) ? false : p_38891_.m_20275_((double)p_38917_.m_123341_() + 0.5D, (double)p_38917_.m_123342_() + 0.5D, (double)p_38917_.m_123343_() + 0.5D) <= reach * reach;
}, true);
}
@@ -229,6 +_,8 @@
ItemStack itemstack1 = p_150438_.get();
this.f_150394_.set(p_150436_, itemstack1);
@@ -0,0 +1,15 @@
--- a/net/minecraft/world/inventory/HorseInventoryMenu.java
+++ b/net/minecraft/world/inventory/HorseInventoryMenu.java
@@ -62,7 +_,11 @@
}
public boolean m_6875_(Player p_39661_) {
- return !this.f_39654_.m_149511_(this.f_39653_) && this.f_39653_.m_6542_(p_39661_) && this.f_39654_.m_6084_() && this.f_39654_.m_20270_(p_39661_) < 8.0F;
+ // Make pehkui's mixin happy so we stop getting crash reports. It requires the magic float 8.0F
+ float magic_number = 8.0F;
+ // Default reach is 3 vanilla uses 8 here so add 5 padding
+ double reach = p_39661_.m_21133_(net.minecraftforge.common.ForgeMod.ENTITY_REACH.get()) + 5;
+ return !this.f_39654_.m_149511_(this.f_39653_) && this.f_39653_.m_6542_(p_39661_) && this.f_39654_.m_6084_() && this.f_39654_.m_20270_(p_39661_) < reach;
}
private boolean m_150577_(AbstractHorse p_150578_) {
@@ -0,0 +1,15 @@
--- a/net/minecraft/world/inventory/ItemCombinerMenu.java
+++ b/net/minecraft/world/inventory/ItemCombinerMenu.java
@@ -108,7 +_,11 @@
public boolean m_6875_(Player p_39780_) {
return this.f_39770_.m_39299_((p_39785_, p_39786_) -> {
- return !this.m_8039_(p_39785_.m_8055_(p_39786_)) ? false : p_39780_.m_20275_((double)p_39786_.m_123341_() + 0.5D, (double)p_39786_.m_123342_() + 0.5D, (double)p_39786_.m_123343_() + 0.5D) <= 64.0D;
+ // Vanilla uses 64 which is 8*8. Default reach is 4.5 so add 3.5 padding
+ // magic number so required Mixins can still target here
+ double reachOld = 64.0;
+ double reach = p_39780_.m_21133_(net.minecraftforge.common.ForgeMod.BLOCK_REACH.get()) + 3.5;
+ return !this.m_8039_(p_39785_.m_8055_(p_39786_)) ? false : p_39780_.m_20275_((double)p_39786_.m_123341_() + 0.5D, (double)p_39786_.m_123342_() + 0.5D, (double)p_39786_.m_123343_() + 0.5D) <= reach * reach;
}, true);
}
@@ -109,7 +109,7 @@
float f7 = f2 * f4;
- double d0 = 5.0D;
- Vec3 vec31 = vec3.m_82520_((double)f6 * 5.0D, (double)f5 * 5.0D, (double)f7 * 5.0D);
+ double d0 = p_41437_.getBlockReach();
+ double d0 = p_41437_.m_21133_(net.minecraftforge.common.ForgeMod.BLOCK_REACH.get()) + 0.5; // Block reach is 4.5, vanilla uses 5.0 here, so add 0.5 padding
+ Vec3 vec31 = vec3.m_82520_((double)f6 * d0, (double)f5 * d0, (double)f7 * d0);
return p_41436_.m_45547_(new ClipContext(vec3, vec31, ClipContext.Block.OUTLINE, p_41438_, p_41437_));
}
@@ -40,7 +40,7 @@
}
+ private boolean isFlammable(LevelReader level, BlockPos pos, Direction face) {
+ return pos.m_123342_() >= level.m_141937_() && pos.m_123342_() < level.m_151558_() && !level.m_46805_(pos) ? false : level.m_8055_(pos).isFlammable(level, pos, face);
+ return pos.m_123342_() >= level.m_141937_() && pos.m_123342_() < level.m_151558_() && !level.m_46805_(pos) ? false : level.m_8055_(pos).m_278200_() && level.m_8055_(pos).isFlammable(level, pos, face);
+ }
+
@Nullable
+1 -1
View File
@@ -37,7 +37,7 @@ project(":mcp").projectDir = file("projects/mcp")
project(":fmlonly").projectDir = file("projects/fmlonly")
project(":forge").projectDir = file("projects/forge")
if (!System.env.TEAMCITY_VERSION) {
if (false && !'true'.equals(System.env.CI)) {
include ':clean'
project(":clean").projectDir = file("projects/clean")
}
@@ -0,0 +1,68 @@
{
"elements": [
{
"faces": {
"down": {
"forge_data": {
"block_light": 10,
"sky_light": 10
},
"texture": "#all"
},
"east": {
"forge_data": {
"block_light": 10,
"sky_light": 10
},
"texture": "#all"
},
"north": {
"forge_data": {
"block_light": 10,
"sky_light": 10
},
"texture": "#all"
},
"south": {
"forge_data": {
"block_light": 10,
"sky_light": 10
},
"texture": "#all"
},
"up": {
"forge_data": {
"block_light": 10,
"sky_light": 10
},
"texture": "#all"
},
"west": {
"forge_data": {
"block_light": 10,
"sky_light": 10
},
"texture": "#all"
}
},
"forge_data": {
"block_light": 15,
"sky_light": 15
},
"from": [
0,
0,
0
],
"to": [
16,
16,
16
]
}
],
"render_type": "minecraft:cutout",
"textures": {
"all": "minecraft:block/diamond_ore"
}
}
@@ -10,6 +10,7 @@
},
"loader": "forge:item_layers",
"render_types": {},
"render_types_fast": {},
"textures": {
"layer0": "minecraft:item/coal",
"layer1": "minecraft:item/stick",
@@ -172,11 +172,18 @@ public abstract class EntityRenderersEvent extends Event implements IModBusEvent
* @return the skin renderer, or {@code null} if no renderer is registered for that skin name
* @see #getSkins()
*/
@SuppressWarnings("unchecked")
public <R extends EntityRenderer<? extends Player>> @Nullable R getPlayerSkin(String skinName)
{
return (R) skinMap.get(skinName);
}
/** @deprecated Use getEntitySkin, return type down graded to EntityRenderer instead of LivingEntityRenderer */
@Nullable
@SuppressWarnings("unchecked")
public <R extends LivingEntityRenderer<? extends Player, ? extends EntityModel<? extends Player>>> R getSkin(String skinName)
{
return (R) skinMap.get(skinName);
return skinMap.get(skinName) instanceof LivingEntityRenderer<?,?> renderer ? (R) renderer : null;
}
/**
@@ -187,11 +194,18 @@ public abstract class EntityRenderersEvent extends Event implements IModBusEvent
* @param <R> the type of the renderer
* @return the renderer, or {@code null} if no renderer is registered for that entity type
*/
@SuppressWarnings("unchecked")
public <T extends Entity, R extends EntityRenderer<T>> @Nullable R getEntityRenderer(EntityType<? extends T> entityType)
{
return (R) renderers.get(entityType);
}
/** @deprecated Use getEntityRenderer, return type down graded to EntityRenderer instead of LivingEntityRenderer */
@Nullable
@SuppressWarnings("unchecked")
public <T extends LivingEntity, R extends LivingEntityRenderer<T, ? extends EntityModel<T>>> R getRenderer(EntityType<? extends T> entityType)
{
return (R) renderers.get(entityType);
return renderers.get(entityType) instanceof LivingEntityRenderer<?,?> renderer ? (R) renderer : null;
}
/**
@@ -361,6 +361,10 @@ public class ModelBuilder<T extends ModelBuilder<T>> extends ModelFile {
partObj.addProperty("shade", part.shade);
}
if (!part.getFaceData().equals(ForgeFaceData.DEFAULT)) {
partObj.add("forge_data", ForgeFaceData.CODEC.encodeStart(JsonOps.INSTANCE, part.getFaceData()).result().get());
}
JsonObject faces = new JsonObject();
for (Direction dir : Direction.values()) {
BlockElementFace face = part.faces.get(dir);
@@ -40,6 +40,7 @@ import com.mojang.serialization.Lifecycle;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.ResourceLocationException;
import net.minecraft.Util;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider;
@@ -420,7 +421,7 @@ public class ForgeHooks
return message; // Vanilla should never get here with the patches we use, but let's be safe with dumb mods
return onServerChatSubmittedEvent(sender, getRawText(message), message);
});
}, Util.backgroundExecutor());
}
static final Pattern URL_PATTERN = Pattern.compile(
@@ -95,6 +95,10 @@ public class ForgeI18n {
}
public static String parseFormat(final String format, final Object... args) {
// No format elements and no quotes: MessageFormat would return the pattern unchanged, so skip parsing it
if (format != null && format.indexOf('{') < 0 && format.indexOf('\'') < 0) {
return format;
}
final ExtendedMessageFormat extendedMessageFormat = new ExtendedMessageFormat(format, customFactories);
return extendedMessageFormat.format(args);
}
@@ -628,7 +628,7 @@ public interface IForgeBlock
*/
default boolean isFlammable(BlockState state, BlockGetter level, BlockPos pos, Direction direction)
{
return state.ignitedByLava() || state.getFlammability(level, pos, direction) > 0;
return state.getFlammability(level, pos, direction) > 0;
}
/**
@@ -6,7 +6,6 @@
package net.minecraftforge.common.extensions;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
@@ -14,30 +13,29 @@ import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ForgeMod;
public interface IForgePlayer
{
private Player self()
{
return (Player) this;
public interface IForgePlayer {
private Player self() {
return (Player)this;
}
/**
* The entity reach is increased by 3 for creative players, unless it is currently zero, which disables attacks and entity interactions.
* This comes from {@link net.minecraft.client.multiplayer.MultiPlayerGameMode#getPickRange() MultiPlayerGameMode.getPickRange()}
* If you want the raw value, get the attribute yourself.
* @return The entity reach of this player.
*/
default double getEntityReach()
{
default double getEntityReach() {
double range = self().getAttributeValue(ForgeMod.ENTITY_REACH.get());
return range == 0 ? 0 : range + (self().isCreative() ? 3 : 0);
}
/**
* The reach distance is increased by 0.5 for creative players, unless it is currently zero, which disables interactions.
* This comes from {@link net.minecraft.client.multiplayer.MultiPlayerGameMode#getPickRange() MultiPlayerGameMode.getPickRange()}
* If you want the raw value, get the attribute yourself.
* @return The reach distance of this player.
*/
default double getBlockReach()
{
default double getBlockReach() {
double reach = self().getAttributeValue(ForgeMod.BLOCK_REACH.get());
return reach == 0 ? 0 : reach + (self().isCreative() ? 0.5 : 0);
}
@@ -50,8 +48,7 @@ public interface IForgePlayer
* @return If the player can attack the entity.
* @apiNote Do not use for block checks, as this method uses {@link #getEntityReach()}
*/
default boolean canReach(Vec3 entityHitVec, double padding)
{
default boolean canReach(Vec3 entityHitVec, double padding) {
return self().getEyePosition().closerThan(entityHitVec, getEntityReach() + padding);
}
@@ -63,11 +60,27 @@ public interface IForgePlayer
* @return If the player can attack the passed entity.
* @apiNote Prefer using {@link #canReach(Vec3, double)} if you have a {@link HitResult} available.
*/
default boolean canReach(Entity entity, double padding)
{
default boolean canReach(Entity entity, double padding) {
return isCloseEnough(entity, getEntityReach() + padding);
}
/**
* Checks if the player can reach an entity.<br>
* On the server, additional padding is added to account for movement/lag.
*
* Unlike {@link #getEntityReach()} or {@link #canReach(Entity,double)} this does not
* add the 3.0 creative mode implicit padding.
*
* @param entity The entity being range-checked.
* @param padding Extra validation distance.
* @return If the player can attack the passed entity.
* @apiNote Prefer using {@link #canReach(Vec3, double)} if you have a {@link HitResult} available.
*/
default boolean canReachRaw(Entity entity, double padding) {
double range = self().getAttributeValue(ForgeMod.ENTITY_REACH.get()) + padding;
return isCloseEnough(entity, range);
}
/**
* Checks if the player can reach a block.<br>
* On the server, additional padding is added to account for movement/lag.
@@ -75,10 +88,25 @@ public interface IForgePlayer
* @param padding Extra validation distance.
* @return If the player can interact with this location.
*/
default boolean canReach(BlockPos pos, double padding)
{
default boolean canReach(BlockPos pos, double padding) {
double reach = this.getBlockReach() + padding;
return self().getEyePosition().distanceToSqr(Vec3.atCenterOf(pos)) < reach * reach;
return self().getEyePosition().distanceToSqr(Vec3.atCenterOf(pos)) <= reach * reach;
}
/**
* Checks if the player can reach a block.<br>
* On the server, additional padding is added to account for movement/lag.
*
* Unlike {@link #getBlockReach()} or {@link #canReach(BlockPos,double)} this does not
* add the 0.5 creative mode implicit padding.
*
* @param pos The position being range-checked.
* @param padding Extra validation distance.
* @return If the player can interact with this location.
*/
default boolean canReachRaw(BlockPos pos, double padding) {
double reach = self().getAttributeValue(ForgeMod.BLOCK_REACH.get()) + padding;
return self().getEyePosition().distanceToSqr(Vec3.atCenterOf(pos)) <= reach * reach;
}
/**
@@ -88,8 +116,12 @@ public interface IForgePlayer
* @return If the eye-to-center distance between this player and the passed entity is less than dist.
* @implNote This method inflates the bounding box by the pick radius, which differs from vanilla. But vanilla doesn't use the pick radius, the only entity with > 0 is AbstractHurtingProjectile.
*/
default boolean isCloseEnough(Entity entity, double dist)
{
default boolean isCloseEnough(Entity entity, double dist) {
// This cuses the "eye-to-closest-corner" checks, which can cause issues with servers.
// https://github.com/MinecraftForge/MinecraftForge/issues/9309
// But to not break expectations of others, its staying until a config can be set.
// The vanilla code is:
// return entity.getBoundingBox().distanceToSqr(self().getEyePosition()) < dist * dist;
Vec3 eye = self().getEyePosition();
AABB aabb = entity.getBoundingBox().inflate(entity.getPickRadius());
return aabb.distanceToSqr(eye) < dist * dist;
@@ -18,33 +18,30 @@ import java.util.function.Supplier;
* for each of your collections.<p/>
* This list does not support modification operations, but the underlying lists may be mutated safely externally.
*/
public class ConcatenatedListView<T> implements List<T>
{
public class ConcatenatedListView<T> implements List<T> {
@SafeVarargs
public static <T> ConcatenatedListView<T> of(List<T>... lists)
{
public static <T> ConcatenatedListView<T> of(List<T>... lists) {
return new ConcatenatedListView<>(List.of(lists));
}
public static <T> List<T> of(List<? extends List<? extends T>> members)
{
return switch (members.size()) {
case 0 -> List.of();
case 1 -> Collections.unmodifiableList(members.get(0));
default -> new ConcatenatedListView<>(members);
};
public static <T> List<T> of(List<? extends List<? extends T>> members) {
if (members.isEmpty())
return List.of();
if (members.size() == 1)
return (Collections.unmodifiableList(members.get(0)));
@SuppressWarnings("unchecked")
var typed = (List<List<T>>)members;
return new ConcatenatedListView<>(typed);
}
private final List<? extends List<? extends T>> lists;
private final List<List<T>> lists;
private ConcatenatedListView(List<? extends List<? extends T>> lists)
{
private ConcatenatedListView(List<List<T>> lists) {
this.lists = lists;
}
@Override
public int size()
{
public int size() {
int size = 0;
for (var list : lists)
size += list.size();
@@ -52,28 +49,26 @@ public class ConcatenatedListView<T> implements List<T>
}
@Override
public boolean isEmpty()
{
for (List<? extends T> list : lists)
public boolean isEmpty() {
for (List<? extends T> list : lists) {
if (!list.isEmpty())
return false;
}
return true;
}
@Override
public boolean contains(Object o)
{
for (var list : lists)
public boolean contains(Object o) {
for (var list : lists) {
if (list.contains(o))
return true;
}
return false;
}
@Override
public T get(int index)
{
for (var list : lists)
{
public T get(int index) {
for (var list : lists) {
int size = list.size();
if (index < size)
return list.get(index);
@@ -83,11 +78,9 @@ public class ConcatenatedListView<T> implements List<T>
}
@Override
public int indexOf(Object o)
{
public int indexOf(Object o) {
int offset = 0;
for (var list : lists)
{
for (var list : lists) {
int foundIndex = list.indexOf(o);
if (foundIndex >= 0)
return offset + foundIndex;
@@ -97,11 +90,9 @@ public class ConcatenatedListView<T> implements List<T>
}
@Override
public int lastIndexOf(Object o)
{
public int lastIndexOf(Object o) {
int offset = 0;
for (var list : Lists.reverse(lists))
{
for (var list : Lists.reverse(lists)) {
int foundIndex = list.lastIndexOf(o);
if (foundIndex >= 0)
return offset + foundIndex;
@@ -112,20 +103,17 @@ public class ConcatenatedListView<T> implements List<T>
@NotNull
@Override
public Iterator<T> iterator()
{
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(Iterables.concat(lists)).iterator();
}
@Override
public Spliterator<T> spliterator()
{
public Spliterator<T> spliterator() {
return Iterables.unmodifiableIterable(Iterables.concat(lists)).spliterator();
}
// Delegate to a concatenated collection
private <C extends Collection<T>> C concatenate(Supplier<C> collectionFactory)
{
private <C extends Collection<T>> C concatenate(Supplier<C> collectionFactory) {
var concat = collectionFactory.get();
for (var list : lists)
concat.addAll(list);
@@ -374,7 +374,7 @@ public class HandshakeHandler
});
// we're done when sentMessages is empty
if (sentMessages.isEmpty() && packetPosition >= messageList.size()-1 && pendingFutures.isEmpty()) {
if (sentMessages.isEmpty() && packetPosition >= messageList.size() && pendingFutures.isEmpty()) {
// clear ourselves - we're done!
this.manager.channel().attr(NetworkConstants.FML_HANDSHAKE_HANDLER).set(null);
LOGGER.debug(FMLHSMARKER, "Handshake complete!");
@@ -15,6 +15,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.IExtensionPoint;
import net.minecraftforge.fml.ModList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -127,11 +128,21 @@ public record ServerStatusPing(
return Objects.hash(channels, mods, fmlNetworkVer);
}
private List<Map.Entry<ResourceLocation, ChannelData>> getChannelsForMod(String modId)
private HashMap<String, List<Map.Entry<ResourceLocation, ChannelData>>> getChannelsByMod()
{
return channels.entrySet().stream()
.filter(c -> c.getKey().getNamespace().equals(modId))
.toList();
var ret = new HashMap<String, List<Map.Entry<ResourceLocation, ChannelData>>>();
for (var channel : channels.entrySet())
{
var namespace = channel.getKey().getNamespace();
var list = ret.get(namespace);
if (list == null)
{
list = new ArrayList<>();
ret.put(namespace, list);
}
list.add(channel);
}
return ret;
}
private List<Map.Entry<ResourceLocation, ChannelData>> getNonModChannels()
@@ -161,11 +172,12 @@ public record ServerStatusPing(
buf.writeBoolean(false); // placeholder for whether we are truncating
buf.writeShort(mods.size()); // short so that we can replace it later in case of truncation
int writtenCount = 0;
var channelsByMod = getChannelsByMod();
for (var modEntry : mods.entrySet())
{
var isIgnoreServerOnly = modEntry.getValue().equals(NetworkConstants.IGNORESERVERONLY);
var channelsForMod = getChannelsForMod(modEntry.getKey());
var channelsForMod = channelsByMod.getOrDefault(modEntry.getKey(), List.of());
var channelSizeAndVersionFlag = channelsForMod.size() << 1;
if (isIgnoreServerOnly)
{
@@ -331,7 +331,7 @@ public class GameData {
if (forgeRegistry != null)
forgeRegistry.freeze();
LOGGER.debug(REGISTRIES, "Applying holder lookups: {}", registryKey.location());
ObjectHolderRegistry.applyObjectHolders(registryKey.location()::equals);
ObjectHolderRegistry.applyObjectHolders(registryKey.location());
LOGGER.debug(REGISTRIES, "Holder lookups applied: {}", registryKey.location());
} catch (Throwable t)
{
@@ -19,7 +19,7 @@ import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("rawtypes")
class ObjectHolderRef implements Consumer<Predicate<ResourceLocation>>
class ObjectHolderRef implements Consumer<Predicate<ResourceLocation>>, Runnable
{
private static final Logger LOGGER = LogManager.getLogger();
private final Field field;
@@ -87,7 +87,12 @@ class ObjectHolderRef implements Consumer<Predicate<ResourceLocation>>
{
if (registry == null || !filter.test(registry.getRegistryName()))
return;
run();
}
@Override
public void run()
{
Object thing;
if (registry.containsKey(injectedObject))
{
@@ -7,10 +7,11 @@ package net.minecraftforge.registries;
import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@@ -40,12 +41,24 @@ public class ObjectHolderRegistry
* The internal list is backed by a HashSet so it is HIGHLY recommended you implement a proper equals
* and hashCode function to de-duplicate callers here.
* The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on.
*
* @deprecated Use {{@link #addHandler(ResourceLocation, Runnable)} if you are only targeting a single registry
*/
public static synchronized void addHandler(Consumer<Predicate<ResourceLocation>> ref)
{
objectHolders.add(ref);
}
/**
* Registers a handler that is run after the named registry has been injected and finalized.
* Handlers are kept in a HashSet per registry, so the same instance is only ever registered once.
*/
public static synchronized void addHandler(ResourceLocation registry, Runnable ref)
{
namedHandlers.computeIfAbsent(registry, id -> new HashSet<>()).add(ref);
}
/**
* Removed the specified handler from the notification list.
*
@@ -60,12 +73,36 @@ public class ObjectHolderRegistry
return objectHolders.remove(ref);
}
/**
* Removes a handler registered with {@link #addHandler(ResourceLocation, Runnable)}.
*
* @return true if handler was matched and removed.
*/
public static synchronized boolean removeHandler(ResourceLocation registry, Runnable ref)
{
var handlers = namedHandlers.get(registry);
return handlers != null && handlers.remove(ref);
}
/**
* Registers a check that is run once, before the handlers of the next holder pass.
* Used to report holders that point at a registry that does not exist.
*/
public static synchronized void addValidator(Runnable ref)
{
validators.add(ref);
}
//==============================================================
// Everything below is internal, do not use.
//==============================================================
private static final Logger LOGGER = LogManager.getLogger();
private static final Set<Consumer<Predicate<ResourceLocation>>> objectHolders = new HashSet<>();
// Order is not guaranteed, the old implementation kept every handler in one HashSet.
private static final Map<ResourceLocation, Set<Runnable>> namedHandlers = new HashMap<>();
private static final List<Runnable> validators = new ArrayList<>();
private static final Type OBJECT_HOLDER = Type.getType(ObjectHolder.class);
private static final Type MOD = Type.getType(Mod.class);
// Hardcoded list of vanilla classes that should have object holders for each field of the given registry type.
@@ -83,48 +120,59 @@ public class ObjectHolderRegistry
public static void findObjectHolders()
{
LOGGER.debug(ForgeRegistry.REGISTRIES,"Processing ObjectHolder annotations");
final List<ModFileScanData.AnnotationData> annotations = ModList.get().getAllScanData().stream()
.map(ModFileScanData::getAnnotations)
.flatMap(Collection::stream)
.filter(a -> OBJECT_HOLDER.equals(a.annotationType()) || MOD.equals(a.annotationType()))
.toList();
final HashMap<Type, String> classModIds = new HashMap<>();
final List<ModFileScanData.AnnotationData> annotations = new ArrayList<>();
if (annotations.stream().noneMatch(a -> OBJECT_HOLDER.equals(a.annotationType())))
return; // No object holders found, skip the rest of the processing
for (var data : ModList.get().getAllScanData()) {
for (var annotation : data.getAnnotations()) {
if (OBJECT_HOLDER.equals(annotation.annotationType())) {
annotations.add(annotation);
}
// Gather all @Mod classes so that @ObjectHolder's in those classes don't need to specify the mod id; modder convenience
else if (MOD.equals(annotation.annotationType())) {
classModIds.put(annotation.clazz(), (String)annotation.annotationData().get("value"));
}
}
}
// this is never actually true as we have an annotation in ConditionalRecipe, but also we want to always run the vanilla holders
/*
if (annotations.isEmpty()) {
return; // No object holders found, skip the rest of the processing,
}
*/
Map<Type, String> classModIds = Maps.newHashMap();
Map<Type, Class<?>> classCache = Maps.newHashMap();
// Gather all @Mod classes so that @ObjectHolder's in those classes don't need to specify the mod id; modder convenience
annotations.stream()
.filter(a -> MOD.equals(a.annotationType()))
.forEach(data -> classModIds.put(data.clazz(), (String)data.annotationData().get("value")));
// Validate all the vanilla class-level object holders then scan those first
VANILLA_OBJECT_HOLDERS.forEach(data -> {
try
{
for (var data : VANILLA_OBJECT_HOLDERS) {
try {
Class<?> holderClass = Class.forName(data.holderClass(), true, ObjectHolderRegistry.class.getClassLoader());
Class<?> registryClass = Class.forName(data.registryType(), true, ObjectHolderRegistry.class.getClassLoader());
Type holderType = Type.getType(holderClass);
classCache.put(holderType, holderClass);
scanTarget(classModIds, classCache, holderType, null, registryClass, data.registryName(), "minecraft", true, true);
}
catch (ClassNotFoundException e)
{
} catch (ClassNotFoundException e) {
throw new RuntimeException("Vanilla class not found, should not be possible", e);
}
});
}
// Scan actual fields annotated with @ObjectHolder second
annotations.stream()
.filter(a -> OBJECT_HOLDER.equals(a.annotationType())).filter(a -> a.targetType() == ElementType.FIELD)
.forEach(data -> scanTarget(classModIds, classCache, data.clazz(),
data.memberName(), null, (String)data.annotationData().get("registryName"),
(String)data.annotationData().get("value"), false, false));
for (var data : annotations) {
if (data.targetType() == ElementType.FIELD) {
var registry = (String)data.annotationData().get("registryName");
var name = (String)data.annotationData().get("value");
scanTarget(classModIds, classCache, data.clazz(), data.memberName(), null, registry, name, false, false);
}
// This is where we WOULD scan for class targets, but Shrimp broke that in 1.19 and so I don't want to re-add a feature that apparently nobody used.
}
LOGGER.debug(ForgeRegistry.REGISTRIES,"Found {} ObjectHolder annotations", objectHolders.size());
int count = objectHolders.size();
for (var set : namedHandlers.values())
count += set.size();
LOGGER.debug(ForgeRegistry.REGISTRIES, "Found {} ObjectHolder annotations", count);
}
private static void scanTarget(Map<Type, String> classModIds, Map<Type, Class<?>> classCache, Type type,
@@ -149,9 +197,10 @@ public class ObjectHolderRegistry
throw new RuntimeException(ex);
}
}
ResourceLocation regName = ResourceLocation.parse(registryName);
if (isClass)
{
scanClassForFields(classModIds, type, new ResourceLocation(registryName), registryClass, value, clazz, extractFromValue);
scanClassForFields(classModIds, type, regName, registryClass, value, clazz, extractFromValue);
}
else
{
@@ -168,9 +217,9 @@ public class ObjectHolderRegistry
try
{
Field f = clazz.getDeclaredField(annotationTarget);
ObjectHolderRef ref = ObjectHolderRef.create(new ResourceLocation(registryName), f, value, extractFromValue);
ObjectHolderRef ref = ObjectHolderRef.create(regName, f, value, extractFromValue);
if (ref != null)
addHandler(ref);
addHandler(regName, ref);
}
catch (NoSuchFieldException ex)
{
@@ -191,7 +240,7 @@ public class ObjectHolderRegistry
continue;
ObjectHolderRef ref = ObjectHolderRef.create(registryName, f, value + ':' + f.getName().toLowerCase(Locale.ENGLISH), extractFromExistingValues);
if (ref != null)
addHandler(ref);
addHandler(registryName, ref);
}
}
@@ -207,38 +256,115 @@ public class ObjectHolderRegistry
throw new IllegalStateException("No registry name was declared for " + declaration);
}
public static void applyObjectHolders()
{
try
{
private static final Predicate<ResourceLocation> ALWAYS_TRUE = name -> true;
public static void applyObjectHolders() {
try {
LOGGER.debug(ForgeRegistry.REGISTRIES, "Applying holder lookups");
applyObjectHolders(key -> true);
var exceptions = new ArrayList<Exception>();
validate(exceptions);
// Call named registries
for (var handlers : namedHandlers.values())
apply(handlers, exceptions);
// Call legacy for backward compatibility
if (!objectHolders.isEmpty())
apply(ALWAYS_TRUE, objectHolders, exceptions);
throwIfNeeded(exceptions);
LOGGER.debug(ForgeRegistry.REGISTRIES, "Holder lookups applied");
} catch (RuntimeException e)
{
} catch (RuntimeException e) {
// It is more important that the calling contexts continue without exception to prevent further cascading errors
LOGGER.error("", e);
}
}
/**
* @deprecated use {@link #applyObjectHolders(ResourceLocation)}
*/
public static void applyObjectHolders(Predicate<ResourceLocation> filter)
{
RuntimeException aggregate = new RuntimeException("Failed to apply some object holders, see suppressed exceptions for details");
objectHolders.forEach(objectHolder -> {
try
{
objectHolder.accept(filter);
}
catch (Exception e)
{
aggregate.addSuppressed(e);
}
});
LOGGER.debug(ForgeRegistry.REGISTRIES, "Apply filtered holder lookups");
var exceptions = new ArrayList<Exception>();
validate(exceptions);
if (aggregate.getSuppressed().length > 0)
{
throw aggregate;
// call new registries if needed
for (var entry : namedHandlers.entrySet()) {
if (filter.test(entry.getKey())) {
apply(entry.getValue(), exceptions);
}
}
// Call legacy
if (!objectHolders.isEmpty())
apply(filter, objectHolders, exceptions);
throwIfNeeded(exceptions);
LOGGER.debug(ForgeRegistry.REGISTRIES, "Holder lookups applied");
}
/** Runs the handlers of one registry. */
public static void applyObjectHolders(ResourceLocation registry) {
var exceptions = new ArrayList<Exception>();
validate(exceptions);
var handlers = namedHandlers.get(registry);
if (handlers != null)
apply(handlers, exceptions);
if (!objectHolders.isEmpty())
apply(registry::equals, objectHolders, exceptions);
throwIfNeeded(exceptions);
}
// Each validator runs once, at the first holder pass after it was registered.
private static void validate(List<Exception> exceptions) {
List<Runnable> pending;
synchronized (ObjectHolderRegistry.class) {
if (validators.isEmpty())
return;
pending = new ArrayList<>(validators);
validators.clear();
}
for (var validator : pending) {
try {
validator.run();
} catch (Exception e) {
exceptions.add(e);
}
}
}
private static void apply(Set<Runnable> handlers, List<Exception> exceptions) {
for (var handler : handlers) {
try {
handler.run();
} catch (Exception e) {
exceptions.add(e);
}
}
}
private static void apply(Predicate<ResourceLocation> filter, Set<Consumer<Predicate<ResourceLocation>>> handlers, List<Exception> exceptions) {
for (var handler : handlers) {
try {
handler.accept(filter);
} catch (Exception e) {
exceptions.add(e);
}
}
}
private static void throwIfNeeded(List<Exception> exceptions) {
if (exceptions.isEmpty())
return;
var aggregate = new RuntimeException("Failed to apply some object holders, see suppressed exceptions for details");
for (var e : exceptions)
aggregate.addSuppressed(e);
throw aggregate;
}
private record VanillaObjectHolderData(String holderClass, String registryName, String registryType) {}
@@ -26,8 +26,7 @@ public final class RegistryObject<T> implements Supplier<T>
@Nullable
private final ResourceLocation name;
@Nullable
private ResourceKey<T> key;
private final boolean optionalRegistry;
private final ResourceKey<T> key;
@Nullable
private T value;
@Nullable
@@ -135,7 +134,6 @@ public final class RegistryObject<T> implements Supplier<T>
private RegistryObject() {
this.name = null;
this.key = null;
this.optionalRegistry = false;
}
@SuppressWarnings("unchecked")
@@ -145,12 +143,7 @@ public final class RegistryObject<T> implements Supplier<T>
throw new IllegalArgumentException("Invalid registry argument, must not be null");
this.name = name;
this.key = (ResourceKey<T>) ResourceKey.create(registry.getRegistryKey(), name);
this.optionalRegistry = false;
ObjectHolderRegistry.addHandler(pred ->
{
if (pred.test(registry.getRegistryName()))
this.updateReference((IForgeRegistry<? extends T>) registry);
});
ObjectHolderRegistry.addHandler(registry.getRegistryName(), () -> this.updateReference((IForgeRegistry<? extends T>) registry));
this.updateReference((IForgeRegistry<? extends T>) registry);
}
@@ -158,31 +151,14 @@ public final class RegistryObject<T> implements Supplier<T>
{
this.name = name;
this.key = ResourceKey.create(ResourceKey.createRegistryKey(registryName), name);
this.optionalRegistry = optionalRegistry;
final Throwable callerStack = new Throwable("Calling Site from mod: " + modid);
ObjectHolderRegistry.addHandler(new Consumer<>()
{
private boolean registryExists = false;
private boolean invalidRegistry = false;
@Override
public void accept(Predicate<ResourceLocation> pred)
{
if (invalidRegistry)
return;
if (!RegistryObject.this.optionalRegistry && !registryExists)
{
if (!registryExists(registryName))
{
invalidRegistry = true;
throw new IllegalStateException("Unable to find registry with key " + registryName + " for mod \"" + modid + "\". Check the 'caused by' to see further stack.", callerStack);
}
registryExists = true;
}
if (pred.test(registryName))
RegistryObject.this.updateReference(registryName);
}
});
if (!optionalRegistry) {
final Throwable callerStack = new Throwable("Calling Site from mod: " + modid);
ObjectHolderRegistry.addValidator(() -> {
if (!registryExists(registryName))
throw new IllegalStateException("Unable to find registry with key " + registryName + " for mod \"" + modid + "\". Check the 'caused by' to see further stack.", callerStack);
});
}
ObjectHolderRegistry.addHandler(registryName, () -> this.updateReference(registryName));
this.updateReference(registryName);
}
@@ -485,8 +461,8 @@ public final class RegistryObject<T> implements Supplier<T>
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj instanceof RegistryObject) {
return Objects.equals(((RegistryObject<?>)obj).name, name);
if (obj instanceof RegistryObject o) {
return o.key == key && Objects.equals(o.name, name);
}
return false;
}
@@ -247,6 +247,12 @@ public class NewModelLoaderTest
.with(BlockStateProperties.HORIZONTAL_FACING, Direction.SOUTH)
.addModels(new ConfiguredModel(model, 0, 180, false))
.partialState();
models().getBuilder("test_forge_data")
.texture("all", mcLoc("block/diamond_ore"))
.element().from(0.0F, 0.0F, 0.0F).to(16.0F, 16.0F, 16.0F)
.allFaces((dir, face) -> face.texture("#all").emissivity(10, 10))
.emissivity(15, 15).end()
.renderType("cutout");
}
}
}