Compare commits

...
81 Commits
Author SHA1 Message Date
LexManos 38e0661f67 1.18.2 RB 3
https://forums.minecraftforge.net/topic/149549-forge-403-minecraft-1182/
2024-12-31 10:42:48 -08:00
Jonathing a88bd8f009 Use the correct Forge version in the MDK (#10286) 2024-12-31 10:13:13 -08:00
Jonathing 315d308268 MDK now uses ForgeGradle 6 (#10285) 2024-12-31 09:43:31 -08:00
Jonathing b8a74727ce Bump CoreMods to fix dev environment issue (#10284) 2024-12-31 09:43:20 -08:00
Jonathing 84120fa8fd Bump CoreMods to 5.2.4 (#10268) 2024-12-23 22:48:32 -08:00
Jonathing 7fa30346ff Allow mipmap lowering to be disabled (#10249) 2024-12-23 22:48:18 -08:00
Jonathing 42d62ab0ea Add optional fix of use item duration, disabled by default (#10243) 2024-12-23 22:34:26 -08:00
JonathingandPaint_Ninja 79b46ad197 Backport some Vanilla 1.21 ResourceLocation methods (#10238)
Co-authored-by: Paint_Ninja <PaintNinja@users.noreply.github.com>
2024-12-23 22:33:53 -08:00
Jonathing 42b0728dae Disable clean on TeamCity (#10261) 2024-12-23 22:18:34 -08:00
Jonathing f6730b3d4a Debump Log4J to 2.17.0 (#10190) 2024-12-01 10:46:11 -08:00
Jonathing dea6c06d5a Bump JNA to 5.12.1 (#10188) 2024-11-26 12:55:26 -08:00
Jonathing c67b9d577b Bump Gradle to 8.8 (same as 1.19.2) (#10175) 2024-11-17 11:30:34 -08:00
Jonathing e0f8795c1f [1.18.2] Bump CoreMods to 5.2 (#10133)
Full Changelog: https://gist.github.com/Jonathing/c3ad28b2a048ac839a7baba5417ee870

The key features are:
- ES6 language support
- Thoroughly updated ASMAPI, with full documentation
- Bug fixes (some optional for backwards-compatibility)
- Partial internal code cleanup
- Request CoreMods to not apply fix for ASMAPI.findFirstInstructionBefore by default

Related dependency bumps:
- Updated ASM to 9.7.1
- Updated Nashorn to 15.4
- Updated Log4J to 2.19.0
2024-11-10 13:44:35 -08:00
Jonathingandthedarkcolour 99cff44383 Choose default JarJar mod file type based on parent JAR (#10026)
Co-authored-by: thedarkcolour <30441001+thedarkcolour@users.noreply.github.com>
2024-07-02 13:00:12 -07:00
Paint_NinjaandJ-RAP d11733a48c Fix NPE when acceptableValues in defineInList() does not allow nulls, fixes #9300, backport of #9903 (#9910)
Co-Authored-By: J-RAP <SrRapero720@hotmail.com>
2024-05-01 21:58:24 +01:00
Paint_Ninja f5a168dc4b Make common config screen registration tasks easier, backport of #9884 (#9920) 2024-05-01 21:57:58 +01:00
Paint_Ninja ff9db4b9f7 Bump ASM, fix userdev, fix crowdin (#9952) 2024-05-01 21:35:28 +01:00
J-RAP 51d4f1505f Bump some deps (#9877)
- CoreMods 5.0.1 -> 5.1.2
- ASM 9.5 -> 9.6
- Installer 2.1 -> 2.2
- Installer tools 1.3.0 -> 1.4.1
2024-03-10 21:26:07 +00:00
Paint_NinjaandSu5eD cb12df41e1 Improve mod description formatting in mods screen (#9840)
Co-authored-by: Su5eD <su5ed@outlook.com>
2023-12-31 16:04:22 +00:00
Paint_Ninja 1515adf026 Make common DisplayTest registration tasks easier (#9839)
Backport of #9822 to 1.18.2
2023-12-31 16:03:59 +00:00
Paint_Ninja 892e236a74 Optimise ForgeConfigSpec and make Range public (#9828)
Backport of #9810 to 1.18.2
2023-12-30 21:39:41 +00:00
J-RAP b2c52b37a7 Backport of displayTest option (#9775)
same how was merged in (MinecraftForge/MinecraftForge#8656)
2023-11-07 21:35:03 -08:00
Paint_Ninja 928204166e Update MDK readme (#9755) 2023-11-02 14:13:31 -07:00
Paint_Ninja f86a22ce35 Backport CrashReportAnalyser to 1.18.2 (#9759) 2023-11-02 14:13:21 -07:00
LexManos 13ee25369c Update README.md 2023-10-31 11:03:33 -07:00
Paint_Ninja 9523232a37 Improve logging for server connections (#9624) 2023-07-13 17:35:38 -07:00
sciwhiz12 1af3922642 [1.18.2] Add new helper without duplicate neighbor update call (#9235) 2023-06-01 23:37:37 +00:00
matyrobbrt dfef5615a0 Fix fluids not evaporating correctly. Fix #9514 2023-05-30 15:05:03 -07:00
Dennis C 92cc8fc679 [1.18.x] Add Forge "bosses" tag (#9513) 2023-05-29 22:35:15 +00:00
cech12 424f43868f [1.18.2] fix inconsistent vaporization in BucketItem & FluidType (#9341) 2023-05-29 19:22:13 +00:00
Andrii Horpenko d6cd51191e [1.18.x] Fixed PlayerInteractEvent.EntityInteractSpecific being called too late (#9494) 2023-05-29 19:17:26 +00:00
Brennan WardandBrennan Ward 435de780a8 [1.18.2] Fix incorrect SpecialSpawn fire location (#9481)
Co-authored-by: Brennan Ward <3682588+Shadows-of-Fire@users.noreply.github.com>
2023-05-18 03:53:40 -05:00
SizableShrimp 519399f856 [1.18.x] Make mixins work with JarJar (#9506)
Backport of #8900
Update ASM to 9.5
Update JarJar to 0.3.19
Update SecureJarHandler to 1.0.8
2023-05-18 03:52:41 -05:00
SizableShrimp 547ce9cb7c Fix forge grindstone hooks allowing stacks of non-stackable items (#9458) 2023-04-20 01:14:17 -07:00
Jonathan Colmenares b63dc48e19 Fire TickEvent.WorldTickEvent on ClientLevel tick (#9304) 2023-01-25 14:05:03 -08:00
Curle aa170facb7 Mark 1.18.2 Recommended Build 2 2023-01-02 04:08:25 +00:00
sciwhiz12 9919e5c1d5 Fix mob block breaking AI when chunk 0,0 is unloaded Fixes #9241 (#9250) 2022-12-30 10:56:34 -08:00
matyrobbrt 84a8997263 [1.18.x] Make check tasks fail and report errors (#9228)
* Make check tasks fail and report errors

* License header and sas
2022-12-20 21:50:05 +10:30
Jonathan Coates f7c4320b7f [1.18.x] Make Util.memoize thread-safe (#9166)
* Make Util.memoize thread-safe

* Add the forge marker present in the PR
2022-12-20 21:48:33 +10:30
Dennis C 84b1a40fe2 [1.18] Improve extensibility of DetectorRailBlock and PoweredRailBlock (#9156)
* Improve extensibility of DetectorRailBlock and PoweredRailBlock

* Add missing license header to BlockGrowFeatureTest
2022-12-20 21:48:18 +10:30
Minecraftschurli 03b75632d8 [1.18.x] Add supplier to FlowerBlock so it works with custom MobEffects (#9140)
* add supplier to FlowerBlock so it works with custom MobEffects

* address comments by @Matyrobbrt

* address comments by @ChampionAsh5357

* fix missing exc entry

* remove null check and rename constructor parameter
2022-12-20 21:48:08 +10:30
2c3cb2025e [1.18.x] Backport grindstone event from 1.19 (#9090)
* Backport grindstone event from 1.19

* Fix several bugs in the original PR related to canceling the event

Fix canceling the event not clearing the result item, causing it to dupe whatever the result with the previous item was
Fix canceling the event running vanilla behavior if vanilla behavior ran at all since the menu was opened and no other listener changed the XP

* Add comments to the test mod explaining the expected behavior

* Clarify docs on behavior of the XP

Makes it clear XP is ignored in on place if the output is empty or the event is canceled

* Fix broadcast changes not being called if the event overrides grindstone behavior

Not sure its strictly needed, but every vanilla branch in the function calls it and its a trivial change.

* Add enabled flag to the test mod

* Improve javadocs

(cherry picked from commit e12c849cc0)

* Formatting fixes

* Apply javadoc suggestions from review

Co-authored-by: matyrobbrt <65940752+Matyrobbrt@users.noreply.github.com>

* Correct capitalization in event name

* Javadoc improvements

Remove some redundancy in certain javadoc comments
Add @see to the header
Inline single line javadocs

* Add some funny curly brackets next to returns

Seems the kids these days have invented a new way to write return statements in javadocs

* Remove extra dots and capital letters

The return tag apparently puts more dots at the end, and having two dots on each sentence is not in the budget. Have also reached the capital letter quota

* Apply suggestions from code review

Co-authored-by: matyrobbrt <65940752+Matyrobbrt@users.noreply.github.com>

Co-authored-by: ferriarnus <61201275+ferriarnus@users.noreply.github.com>
Co-authored-by: matyrobbrt <65940752+Matyrobbrt@users.noreply.github.com>
2022-12-20 21:47:49 +10:30
sciwhiz12 0ca10b5569 [1.18.2] Fix compatibility checker task configuration (#9229) 2022-12-17 18:53:18 +01:00
Marc Hermans 1087e265aa Enable additional build types to handle pull request validation. (#9160) 2022-11-30 14:11:14 -08:00
Dennis C d3ca95aee2 [1.18.x] Improve logging of missing or unsupported dependencies (#9108) 2022-11-13 01:37:05 +00:00
Budschie c5f3f34c5a [1.18.x] Make LivingSetAttackTargetEvent compatible with the Brain/Behavior system. (#8918) 2022-10-14 17:45:13 +01:00
bconlon 9ef5d545f8 [1.18.x] Backport recipe grouping feature to 1.18. (#8984) 2022-10-14 17:44:30 +01:00
Magnus Jensen 1b0ffa2633 [1.18.x] Invalidate lazy optionals properly (#9082) 2022-10-14 17:41:09 +01:00
Magnus Jensen 763523d1c0 [1.18.x] Fix NPE when feeding cats and wolves (#9075) 2022-10-14 17:18:43 +01:00
Brennan Ward 24d3705e19 Revert "[1.18.x] Backport: Cache resource listing calls in resource packs (#8868)" (#9026)
This reverts commit 4df024ef43.
2022-09-09 17:12:59 -07:00
Jais Djurhuus-Kempel 40bce4b498 [1.18.x] Backport #8981 ("Add event to growing fungus") to 1.18 (#8998) 2022-09-08 16:19:07 +01:00
Rodrigo 845c5958e0 LTS Backport of ModMismachEvent (#8989) (#9010) 2022-09-08 16:11:20 +01:00
Brennan Ward 6faa5667d6 LTS Backport of #8927 (#9012) 2022-09-08 16:10:59 +01:00
Brennan Ward 8ab5cbca26 Backport Reach Distance fix (#9008) 2022-09-03 20:13:08 +01:00
Joshua Taylor 6ff02a6ba9 Backport version-checker user-agent changes to 1.18 (#8975) 2022-09-03 20:09:03 +01:00
Dennis C 4db9bc68d9 Fix circular dependency in static init of RecipeBookRegistry (#8996) 2022-08-27 11:17:17 -07:00
noeppi_noeppi 84d354a7dd Backport: Load custom ITransformationServices from the classpath in dev (#8350) 2022-08-08 15:07:38 +02:00
embeddedt 4df024ef43 [1.18.x] Backport: Cache resource listing calls in resource packs (#8868) 2022-08-08 15:05:43 +02:00
TheMuso 49c927a505 Backport: Re-add PotentialSpawns event (#8944) 2022-08-08 15:01:33 +02:00
Mysticdrew 73a937e088 [1.18.2][Backport] Let isRemotePresent check minecraft:register for packet ids from other mod loaders (#8943) 2022-08-08 14:58:16 +02:00
matyrobbrt 65e8de880d Fix crossbows not firing ArrowLooseEvent (#8888) 2022-08-03 15:01:29 -07:00
Dennis C 1dacb882e0 [1.18] Allow blocks to provide a dynamic MaterialColor for display on maps (#8855) 2022-07-17 22:27:48 +01:00
Silver_David 22a51250b8 [1.18.x] Add RenderLevelStageEvent to expand on and replace RenderLevelLastEvent (#8603) 2022-07-17 15:33:26 +01:00
sciwhiz12 d264674a85 [1.18] Fix EnumArgument to use enum names for suggestions (#8746)
Previously, the suggestions used the string representation of the enum
through Enum#toString, which can differ from the name of the enum as
required by Enum#valueOf, causing invalid suggestions (both in gui and
through the error message).

Using Enum#name fixes this discrepancy, so now the suggestions are
always valid inputs regardless of the return of Enum#toString.

Fixes #8618
2022-07-17 15:32:10 +01:00
TelepathicGrunt af9b4c1fe5 [1.18] Add MC-105317 fix into patch to rotate entities in structures properly (#8793) 2022-07-17 15:31:37 +01:00
Marc Hermans 5a6baccee5 [1.18] Update to the latest JarJar (#8848) 2022-07-17 15:30:58 +01:00
Robert Kämpf 6443adcb6f [1.18.x] Fix a weird interaction between shulker boxes and hoppers. (#8824) 2022-07-17 15:30:12 +01:00
Paint_Ninja fc649f4e06 [1.18.x] Implement full support for IPv6 (#8376) 2022-07-17 15:26:58 +01:00
Joseph Tarbit 2886f3ca34 [1.16] Make tryEmptyContainer respect doDrain (#8318) 2022-07-17 15:16:26 +01:00
Marc Hermans 1af16d70cc Jar-In-Jar (#8657)
* Jar-In-Jar

* Change to use the custom 4.x SPI.

* Bump JarJar to fix some version range selectors.

* Address requested changes.

* Mark the JiJ locator as a locator.

* Remove the unused imports.
2022-07-10 00:05:42 +09:30
sciwhiz12 87abbdb8ef Fix static member ordering crash in UnitSprite (#8839)
The previous ordering caused LOCATION to be null during the construction
of the UnitSprite INSTANCE, leading to errors as the atlas location for
the sprite would be null.

Fixes #8795
2022-07-09 18:53:10 +09:30
covers1624 1b25285462 Backport: ForgeChunkManager ticking ticket fixes. (#8784) 2022-07-09 18:52:29 +09:30
ferriarnus 0f4e3146e0 Delete LootItemRandomChanceCondition.java.patch (#8734) 2022-07-09 18:51:00 +09:30
Magnus Jensen b46f7c0d85 Use stack sensitive translation key by default (#8674) 2022-07-09 18:50:10 +09:30
Dennis C 28d71761cd Add callback after a BlockState was changed and the neighbors were updated (#8617) 2022-07-09 18:49:14 +09:30
Dennis C dbf1c8f10f [1.18.x] Allow safely registering RenderType predicates at any time (#8671)
* Allow safely registering RenderType predicates at any time

* Requested changes
2022-06-30 21:51:18 +09:30
LexManos f7dd6396e4 Add Filtered Message to ServerChatEvent 2022-06-29 12:05:27 -07:00
King Lemming 1a46f205d6 [1.18] Make ConfigValue implement Supplier. (#8777) 2022-06-20 23:08:24 +01:00
gigaherz be0facc2aa Make IVertexConsumers such as the lighting pipeline, be aware of which format they are dealing with (#8665)
Also fix Lighting pipeline ignoring the overlay coords from the block renderer.
2022-06-09 21:41:00 +02:00
cpw 67d4a0163b Revert "TeamCity change in 'MinecraftForge / MinecraftForge' project: build features of 'Build' build configuration were updated"
This reverts commit 0cc85bc314.
2022-06-08 20:56:41 -04:00
cpw 0cc85bc314 TeamCity change in 'MinecraftForge / MinecraftForge' project: build features of 'Build' build configuration were updated 2022-06-08 22:50:15 +00:00
182 changed files with 5591 additions and 1632 deletions

No files matched your search

+73 -1
View File
@@ -1,5 +1,7 @@
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
@@ -30,11 +32,13 @@ project {
buildType(Build)
buildType(BuildSecondaryBranches)
buildType(PullRequests)
buildType(PullRequestChecks)
buildType(PullRequestCompatibility)
params {
text("docker_jdk_version", "17", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "7.4", 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.18.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.19.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*)
@@ -134,3 +138,71 @@ object PullRequests : BuildType({
""".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()
}
})
Vendored
-125
View File
@@ -1,125 +0,0 @@
@Library('forge-shared-library')_
pipeline {
options {
disableConcurrentBuilds()
}
agent {
docker {
image 'gradle:7-jdk16'
//args '-v forgegc:/home/gradle/.gradle_cache/'
}
}
environment {
GRADLE_ARGS = '--no-daemon --console=plain' // No daemon for now as FG3 kinda derps. //'-Dorg.gradle.daemon.idletimeout=5000'
DISCORD_WEBHOOK = credentials('forge-discord-jenkins-webhook')
DISCORD_PREFIX = "Job: Forge Branch: ${BRANCH_NAME} Build: #${BUILD_NUMBER}"
JENKINS_HEAD = 'https://wiki.jenkins-ci.org/download/attachments/2916393/headshot.png'
}
stages {
stage('notify_start') {
when {
not {
changeRequest()
}
}
steps {
discordSend(
title: "${DISCORD_PREFIX} Started",
successful: true,
result: 'ABORTED', //White border
thumbnail: JENKINS_HEAD,
webhookURL: DISCORD_WEBHOOK
)
}
}
/*
stage('pull_cache') {
steps {
sh 'cp -r /home/gradle/.gradle_cache/jdks/ /home/gradle/.gradle/jdks/'
sh 'cp -r /home/gradle/.gradle_cache/wrapper/ /home/gradle/.gradle/wrapper/'
sh 'cp -r /home/gradle/.gradle_cache/caches/ /home/gradle/.gradle/caches/'
}
}
*/
stage('setup') {
steps {
withGradle {
sh './gradlew ${GRADLE_ARGS} --refresh-dependencies --continue setup'
}
script {
env.MYVERSION = sh(returnStdout: true, script: './gradlew :forge:properties -q | grep "^version:" | cut -d" " -f2').trim()
env.FMLONLY_VERSION = sh(returnStdout: true, script: './gradlew :fmlonly:properties -q | grep "^version:" | cut -d" " -f2').trim()
}
}
}
stage('changelog') {
when {
not {
changeRequest()
}
}
steps {
writeChangelog(currentBuild, 'build/changelog.txt')
}
}
stage('publish') {
when {
not {
changeRequest()
}
}
environment {
CROWDIN = credentials('forge-crowdin')
KEYSTORE = credentials('forge-jenkins-keystore-old')
KEYSTORE_KEYPASS = credentials('forge-jenkins-keystore-old-keypass')
KEYSTORE_STOREPASS = credentials('forge-jenkins-keystore-old-keypass')
}
steps {
withCredentials([usernamePassword(credentialsId: 'maven-forge-user', usernameVariable: 'MAVEN_USER', passwordVariable: 'MAVEN_PASSWORD')]) {
withGradle {
sh './gradlew ${GRADLE_ARGS} publish -PkeystoreKeyPass=${KEYSTORE_KEYPASS} -PkeystoreStorePass=${KEYSTORE_STOREPASS} -Pkeystore=${KEYSTORE} -PcrowdinKey=${CROWDIN}'
}
}
}
post {
success {
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:forge ${env.MYVERSION} latest")], propagate: false, wait: false
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:fmlonly ${env.FMLONLY_VERSION} latest")], propagate: false, wait: false
}
}
}
stage('test_publish_pr') { //Publish to local repo to test full process, but don't include credentials so it can't sign/publish to maven
when {
changeRequest()
}
environment {
CROWDIN = credentials('forge-crowdin')
}
steps {
withGradle {
sh './gradlew ${GRADLE_ARGS} publish -PcrowdinKey=${CROWDIN}'
}
}
}
}
post {
always {
script {
archiveArtifacts artifacts: 'projects/forge/build/libs/**/*.*', fingerprint: true, onlyIfSuccessful: true, allowEmptyArchive: true
if (env.CHANGE_ID == null) { // This is unset for non-PRs
discordSend(
title: "${DISCORD_PREFIX} Finished ${currentBuild.currentResult}",
description: '```\n' + getChanges(currentBuild) + '\n```',
successful: currentBuild.resultIsBetterOrEqualTo("SUCCESS"),
result: currentBuild.currentResult,
thumbnail: JENKINS_HEAD,
webhookURL: DISCORD_WEBHOOK
)
}
}
}
}
}
+183 -119
View File
@@ -1,15 +1,11 @@
buildscript {
repositories {
mavenLocal()
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:[5.1.39,)'
}
}
import de.undercouch.gradle.tasks.download.Download
import groovy.json.JsonBuilder
import net.minecraftforge.forge.tasks.checks.CheckATs
import net.minecraftforge.forge.tasks.checks.CheckExcs
import net.minecraftforge.forge.tasks.checks.CheckPatches
import net.minecraftforge.forge.tasks.checks.CheckSAS
import net.minecraftforge.forge.tasks.checks.CheckTask
import net.minecraftforge.forge.tasks.checks.CheckMode
import java.nio.file.Files
import java.util.Date
@@ -28,9 +24,12 @@ import org.gradle.plugins.ide.eclipse.model.SourceFolder
plugins {
id 'org.cadixdev.licenser' version '0.6.1'
id 'com.github.ben-manes.versions' version '0.36.0'
id 'net.minecraftforge.gradleutils' version '2.+'
id 'net.minecraftforge.gradleutils' version '[2.2,2.3)'
id 'eclipse'
id 'de.undercouch.download' version '4.1.2'
id 'idea'
id 'de.undercouch.download' version '5.1.3'
id 'net.minecraftforge.gradle.patcher' version '[6.0,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0,6.2)' apply false
}
Util.init() //Init all our extension methods!
@@ -48,27 +47,27 @@ ext {
MAPPING_VERSION = '1.18.2'
MC_VERSION = '1.18.2'
MCP_VERSION = '20220404.173914'
SPI_VERSION = '4.0.10'
SPI_VERSION = '4.0.15-4.x'
ACCESSTRANSFORMERS_VERSION = '8.0.4'
COREMODS_VERSION = '5.0.1'
COREMODS_VERSION = '5.2.5'
EVENTBUS_VERSION = '5.0.3'
MODLAUNCHER_VERSION = '9.1.3'
SECUREJARHANDLER_VERSION = '1.0.3'
SECUREJARHANDLER_VERSION = '1.0.8'
BOOTSTRAPLAUNCHER_VERSION = '1.0.0'
ASM_VERSION = '9.2'
INSTALLER_VERSION = '2.1.+'
ASM_VERSION = '9.7.1'
INSTALLER_VERSION = '2.2.+'
MIXIN_VERSION = '0.8.5'
JARJAR_VERSION = '0.3.19'
GIT_INFO = gradleutils.gitInfo
VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, '[0-9]', MC_VERSION)
// FML_VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, 'FML', MC_VERSION)
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
SPECIAL_SOURCE = 'net.md-5:SpecialSource:1.10.0'
BINPATCH_TOOL = 'net.minecraftforge:binarypatcher:1.0.12:fatjar'
INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.3.0'
INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.4.1'
JAR_SPLITTER = 'net.minecraftforge:jarsplitter:1.1.4'
FART = 'net.minecraftforge:ForgeAutoRenamingTool:0.1.22:all'
FART = 'net.minecraftforge:ForgeAutoRenamingTool:0.1.25:all'
MIN_TAG_FOR_CHANGELOG = "39.0"
LAST_RB = GIT_INFO.tag.rsplit('.', 1)[1] as int >= 1 ? GIT_INFO.tag + '.0' : null
}
@@ -138,6 +137,10 @@ subprojects {
name = 'forge'
url = 'https://maven.minecraftforge.net/'
}
maven {
name = 'minecraft'
url = 'https://libraries.minecraft.net/'
}
}
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
@@ -199,46 +202,53 @@ project(':mcp') {
}
}
project(':clean') {
evaluationDependsOn(':mcp')
apply plugin: 'net.minecraftforge.gradle.patcher'
tasks.withType(AbstractPublishToMaven.class).forEach{tsk -> tsk.enabled = false } // We don't want to publish anything
if (!System.env.TEAMCITY_VERSION) {
project(':clean') {
evaluationDependsOn(':mcp')
apply plugin: 'net.minecraftforge.gradle.patcher'
tasks.withType(AbstractPublishToMaven.class).forEach{tsk -> tsk.enabled = false } // We don't want to publish anything
dependencies {
implementation 'net.minecraftforge:forgespi:' + SPI_VERSION
}
dependencies {
implementation 'net.minecraftforge:forgespi:' + SPI_VERSION
}
patcher {
parent = project(':mcp')
mcVersion = MC_VERSION
patchedSrc = file('src/main/java')
patcher {
parent = project(':mcp')
mcVersion = MC_VERSION
patchedSrc = file('src/main/java')
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
runs {
clean_client {
client true
taskName 'clean_client'
ideaModule "${rootProject.name}.${project.name}.main"
runs {
clean_client {
client true
taskName 'clean_client'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.client.main.Main'
workingDirectory project.file('run')
main 'net.minecraft.client.main.Main'
workingDirectory project.file('run')
args '--gameDir', '.'
args '--version', MC_VERSION
args '--assetsDir', downloadAssets.output
args '--assetIndex', '{asset_index}'
args '--accessToken', '0'
args '--gameDir', '.'
args '--version', MC_VERSION
args '--assetsDir', downloadAssets.output
args '--assetIndex', '{asset_index}'
args '--accessToken', '0'
}
clean_server {
client false
taskName 'clean_server'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.server.Main'
workingDirectory project.file('run')
}
}
}
clean_server {
client false
taskName 'clean_server'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.server.Main'
workingDirectory project.file('run')
}
// Disabling clean with CI causes an error if we don't have this...
compileJava {
dependsOn 'extractMapped'
}
}
}
@@ -251,6 +261,7 @@ def sharedDeps = {
moduleonly "org.ow2.asm:asm-util:${ASM_VERSION}"
moduleonly "org.ow2.asm:asm-analysis:${ASM_VERSION}"
moduleonly "cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}"
moduleonly "net.minecraftforge:JarJarFileSystems:${JARJAR_VERSION}"
installer "cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}"
installer "org.ow2.asm:asm:${ASM_VERSION}"
@@ -262,7 +273,7 @@ def sharedDeps = {
installer 'org.antlr:antlr4-runtime:4.9.1' //Dep of AccessTransformer
installer "net.minecraftforge:eventbus:${EVENTBUS_VERSION}"
installer "net.minecraftforge:forgespi:${SPI_VERSION}"
installer "net.minecraftforge:coremods:${COREMODS_VERSION}"
installer("net.minecraftforge:coremods:${COREMODS_VERSION}")
installer "cpw.mods:modlauncher:${MODLAUNCHER_VERSION}"
installer 'net.minecraftforge:unsafe:0.2.+'
installer 'com.electronwill.night-config:core:3.6.4'
@@ -273,7 +284,11 @@ def sharedDeps = {
installer 'org.jline:jline-reader:3.12.+' //Dep of TerminaalConsoleeAppender
installer 'org.jline:jline-terminal:3.12.+' //Dep of TerminaalConsoleeAppender
installer "org.spongepowered:mixin:${MIXIN_VERSION}"
installer 'org.openjdk.nashorn:nashorn-core:15.3'
installer 'org.openjdk.nashorn:nashorn-core:15.4'
installer "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
installer "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
installer 'net.java.dev.jna:jna:5.12.1'
installer 'net.java.dev.jna:jna-platform:5.12.1'
/*
installer 'org.lwjgl:lwjgl:3.2.2'
@@ -324,7 +339,7 @@ def sharedFmlonlyForge = { Project prj ->
}
prj.task([type: DownloadMavenArtifact], 'downloadInstaller') {
artifact = "net.minecraftforge:installer:${INSTALLER_VERSION}:shrunk"
artifact = "net.minecraftforge:installer:${INSTALLER_VERSION}:fatjar"
changing = true
}
@@ -420,6 +435,8 @@ def sharedFmlonlyForge = { Project prj ->
run.args '--fml.forgeGroup', prj.group
run.args '--fml.mcpVersion', MCP_VERSION
run.jvmArgs '-Djava.net.preferIPv6Addresses=system'
if (run.name.contains('client')) {
run.client true
} else {
@@ -429,7 +446,7 @@ def sharedFmlonlyForge = { Project prj ->
// SecureJarHandler bootstrap values.
run.property 'ignoreList', prj.configurations.moduleonly.files.collect {it.name.replaceAll(/([-_]([.\d]*\d+)|\.jar$)/, '') }.join(',') + ",client-extra,fmlcore,javafmllanguage,lowcodelanguage,mclanguage,${prj.name}-"
// FIXME: Without this jna doesn't work at runtime. Someone figure out why please?
run.property 'mergeModules', 'jna-5.10.0.jar,jna-platform-5.10.0.jar,java-objc-bridge-1.0.0.jar'
run.property 'mergeModules', 'jna-5.12.1.jar,jna-platform-5.12.1.jar,java-objc-bridge-1.0.0.jar'
if (userdevRuns.contains(run)) {
run.property 'legacyClassPath.file', '{minecraft_classpath_file}'
run.jvmArgs '-p', '{modules}'
@@ -440,6 +457,7 @@ def sharedFmlonlyForge = { Project prj ->
run.jvmArgs '--add-modules', 'ALL-MODULE-PATH'
// Additions to these JVM module args should be mirrored to server_files/args.txt and other similar blocks in the buildscript
run.jvmArgs '--add-opens', 'java.base/java.util.jar=cpw.mods.securejarhandler'
run.jvmArgs '--add-opens', 'java.base/java.lang.invoke=cpw.mods.securejarhandler'
run.jvmArgs '--add-exports', 'java.base/sun.security.util=cpw.mods.securejarhandler'
run.jvmArgs '--add-exports', 'jdk.naming.dns/com.sun.jndi.dns=java.naming'
}
@@ -630,6 +648,7 @@ project(':fmlonly') {
'--add-modules', 'ALL-MODULE-PATH',
// Additions to these JVM module args should be mirrored to server_files/args.txt and other similar blocks in the buildscript
'--add-opens', 'java.base/java.util.jar=cpw.mods.securejarhandler',
'--add-opens', 'java.base/java.lang.invoke=cpw.mods.securejarhandler',
'--add-exports', 'java.base/sun.security.util=cpw.mods.securejarhandler',
'--add-exports', 'jdk.naming.dns/com.sun.jndi.dns=java.naming'
]
@@ -849,10 +868,15 @@ project(':fmlonly') {
// Since we need the modules in the bootstrap, we need to make sure they are compiled before we do each run
afterEvaluate { prepareRuns.dependsOn(PACKED_DEPS) }
// Disabling clean with CI causes an error if we don't have this...
compileJava {
dependsOn 'extractMapped'
}
}
project(':forge') {
evaluationDependsOn(':clean')
evaluationDependsOn(System.env.TEAMCITY_VERSION ? ':mcp' : ':clean')
apply plugin: 'java-library'
apply plugin: 'net.minecraftforge.gradle.patcher'
apply plugin: 'org.cadixdev.licenser'
@@ -926,13 +950,19 @@ project(':forge') {
patcher {
excs.from file("$rootDir/src/main/resources/forge.exc")
parent = project(':clean')
parent = project(System.env.TEAMCITY_VERSION ? ':mcp' : ':clean')
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
}
runs {
forge_client {
property 'eventbus.checkTypesOnDispatch', 'true'
@@ -1108,9 +1138,12 @@ project(':forge') {
]
}
task crowdin(type: Crowdin) {
key = project.hasProperty('crowdinKey') ? project.crowdinKey : null
//TODO: Merge our english.json?
tasks.register('downloadCrowdin', Download) {
src 'https://files.minecraftforge.net/crowdin.zip'
dest file('build/crowdin.zip')
useETag 'all'
onlyIfModified true
quiet true
}
sharedFmlonlyForge.call(project)
@@ -1146,86 +1179,98 @@ project(':forge') {
}
}
task checkATs(type: CheckATs, dependsOn: [extractInheritance, createSrg2Mcp]) {
inheritance = extractInheritance.output
tasks.register('checkAll') {
dependsOn 'checkLicenses'
group = 'checks'
}
tasks.register('checkAllAndFix') {
dependsOn 'findFieldInstanceChecks', 'checkLicenses'
group = 'checks'
}
CheckTask.registerTask(tasks, 'ATs', CheckATs) {
dependsOn extractInheritance, createSrg2Mcp
ats.from patcher.accessTransformers
inheritance = extractInheritance.output
mappings = createSrg2Mcp.output
}
task checkSAS(type: CheckSAS, dependsOn: extractInheritance) {
inheritance = extractInheritance.output
CheckTask.registerTask(tasks, 'SAS', CheckSAS) {
dependsOn extractInheritance
sass.from patcher.sideAnnotationStrippers
inheritance = extractInheritance.output
}
task checkExcs(type: CheckExcs, dependsOn: jar) {
CheckTask.registerTask(tasks, 'Excs', CheckExcs) {
dependsOn jar
binary = jar.archiveFile.get().asFile
excs.from patcher.excs
}
task checkAll(dependsOn: [checkATs, checkSAS, checkExcs, findFieldInstanceChecks]){}
task checkPatchesAndFix(type: CheckPatches, dependsOn: genPatches) {
CheckTask.registerTask(tasks, 'Patches', CheckPatches) {
dependsOn genPatches
patchDir = file("$rootDir/patches")
autoFix = true
}
task checkPatches(type: CheckPatches, dependsOn: genPatches) {
patchDir = file("$rootDir/patches")
autoFix = false
patchesWithS2SArtifact = [
'minecraft/net/minecraft/client/renderer/ViewArea.java.patch',
'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch',
]
}
genPatches {
finalizedBy checkPatches
finalizedBy checkAndFixPatches
autoHeader true
lineEnding = '\n'
}
def baseForgeVersion = LAST_RB == null ? null : "${MC_VERSION}-${LAST_RB}"
task downloadBaseCompatibilityBinPatches(type: DownloadBaseCompatibilityBinPatches) {
inputVersion = baseForgeVersion
def baseForgeVersionProperty = project.objects.property(String)
baseForgeVersionProperty.set(project.provider { TeamcityRequests.attemptFindBase(rootDir) ?: getLatestForgeVersion(MC_VERSION) })
baseForgeVersionProperty.finalizeValueOnRead()
def jarCompatibilityTaskSetup = { task ->
task.group = 'jar compatibility'
task.onlyIf {
baseForgeVersionProperty.getOrNull() != null
}
}
task applyBaseCompatibilityJarBinPatches(type: ApplyBinPatches) {
group = 'jar compatibility'
onlyIf {
baseForgeVersion != null
}
inputs.property 'baseForgeVersion', baseForgeVersion
tasks.register('setupCheckJarCompatibility', SetupCheckJarCompatibility) {
inputVersion = baseForgeVersionProperty
}
clean = createJoinedSRG.output
patch = downloadBaseCompatibilityBinPatches.output
tasks.register('applyBaseCompatibilityJarBinPatches', ApplyBinPatches) {
jarCompatibilityTaskSetup(it)
clean = project.tasks.createJoinedSRG.output
patch = project.tasks.named('setupCheckJarCompatibility').flatMap { it.baseBinPatchesOutput }
output = project.layout.buildDirectory.dir(name).map { it.file('output.jar') }
}
task checkJarCompatibility(type: CheckJarCompatibility) {
group = 'jar compatibility'
onlyIf {
baseForgeVersion != null
}
inputs.property 'baseForgeVersion', baseForgeVersion
tasks.register('mergeBaseForgeJar', MergeJars) {
jarCompatibilityTaskSetup(it)
if (baseForgeVersion != null) {
def baseForgeUniversal = project.configurations.detachedConfiguration(project.dependencies.create("net.minecraftforge:forge:${baseForgeVersion}:universal"))
def fmlLibs = project.configurations.detachedConfiguration(PACKED_DEPS.collect {
def artifactId = it.split(':')[1]
return project.dependencies.create("net.minecraftforge:${artifactId}:${baseForgeVersion}")
}.toArray(Dependency[]::new))
inputJars.from(project.tasks.named('applyBaseCompatibilityJarBinPatches').flatMap { it.output })
inputJars.from(baseForgeVersionProperty.map { inputVersion ->
def output = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-universal.jar") }.get().asFile
project.rootProject.extensions.download.run {
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-universal.jar"
dest output
}
return output
})
}
baseJar = project.tasks.applyBaseCompatibilityJarBinPatches.output
baseLibraries.from(project.file(project.provider {
baseForgeUniversal.resolve().iterator().next()
}))
baseLibraries.from(project.tasks.createJoinedSRG.output)
baseLibraries.from(fmlLibs)
tasks.register('checkJarCompatibility', CheckJarCompatibility) {
jarCompatibilityTaskSetup(it)
dependsOn 'setupCheckJarCompatibility'
inputJar = project.tasks.reobfJar.output
concreteLibraries.from(PACKED_DEPS.collect { rootProject.tasks.getByPath(it).archiveFile })
baseJar = project.tasks.named('mergeBaseForgeJar').flatMap { it.output }
baseLibraries.from(project.tasks.named('createJoinedSRG').flatMap { it.output })
commonLibraries.from(project.configurations.minecraftImplementation)
commonLibraries.from(project.configurations.installer)
commonLibraries.from(project.configurations.moduleonly)
}
inputJar = project.tasks.named('reobfJar').flatMap { it.output }
concreteLibraries.from(project.PACKED_DEPS.collect { project.rootProject.tasks.getByPath(it).archiveFile })
commonLibraries.from(project.configurations.minecraftImplementation)
commonLibraries.from(project.configurations.installer)
commonLibraries.from(project.configurations.moduleonly)
}
task launcherJson(type: LauncherJson) {
@@ -1247,13 +1292,15 @@ project(':forge') {
'--fml.mcVersion', MC_VERSION,
'--fml.forgeGroup', project.group,
'--fml.mcpVersion', MCP_VERSION],
jvm: ["-DignoreList=${forge_client.properties.ignoreList},\${version_name}.jar",
jvm: ['-Djava.net.preferIPv6Addresses=system',
"-DignoreList=${forge_client.properties.ignoreList},\${version_name}.jar",
"-DmergeModules=${forge_client.properties.mergeModules}",
'-DlibraryDirectory=${library_directory}',
'-p', Util.getArtifacts(project, configurations.moduleonly, false).values().collect{'${library_directory}/' + it.downloads.artifact.path}.join('${classpath_separator}'),
'--add-modules', 'ALL-MODULE-PATH',
// Additions to these JVM module args should be mirrored to server_files/args.txt and other similar blocks in the buildscript
'--add-opens', 'java.base/java.util.jar=cpw.mods.securejarhandler',
'--add-opens', 'java.base/java.lang.invoke=cpw.mods.securejarhandler',
'--add-exports', 'java.base/sun.security.util=cpw.mods.securejarhandler',
'--add-exports', 'jdk.naming.dns/com.sun.jndi.dns=java.naming'
]
@@ -1483,9 +1530,12 @@ project(':forge') {
from extraTxts
// add crowdin locales
from { crowdin.output.present && crowdin.output.get().asFile.exists() ? zipTree(crowdin.output) : null}
dependsOn crowdin
duplicatesStrategy = 'exclude'
from zipTree(downloadCrowdin.dest).matching {
include 'assets/forge/lang/*.json'
}
dependsOn downloadCrowdin
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
doFirst {
MANIFESTS.each{ pkg, values ->
@@ -1530,7 +1580,7 @@ project(':forge') {
exclude it
}
filter(ReplaceTokens, tokens: [
FORGE_VERSION: project.version,
FORGE_VERSION: FORGE_VERSION,
FORGE_GROUP: project.group,
FORGE_NAME: project.name,
MC_VERSION: MC_VERSION,
@@ -1596,11 +1646,17 @@ project(':forge') {
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
// Disabling clean with CI causes an error if we don't have this...
compileJava {
dependsOn 'extractMapped'
}
}
//evaluationDependsOnChildren()
task setup() { //These must be strings so that we can do lazy resolution. Else we need evaluationDependsOnChildren above
dependsOn ':clean:extractMapped'
if (!System.env.TEAMCITY_VERSION)
dependsOn ':clean:extractMapped'
if (symlinkValid)
dependsOn ':fmlonly:extractMapped'
dependsOn ':forge:extractMapped'
@@ -1616,4 +1672,12 @@ if (System.env.TEAMCITY_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...
downloadJavadoc = true
downloadSources = true
}
}
+7 -4
View File
@@ -1,9 +1,12 @@
repositories {
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
mavenCentral()
}
dependencies {
implementation 'org.ow2.asm:asm:9.1'
implementation 'org.ow2.asm:asm-tree:9.1'
implementation 'net.minecraftforge:srgutils:0.4.+'
implementation 'org.ow2.asm:asm:9.5'
implementation 'org.ow2.asm:asm-tree:9.5'
implementation 'net.minecraftforge:srgutils:0.5.+'
implementation 'commons-io:commons-io:2.8.0'
implementation 'com.google.code.gson:gson:2.10'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
}
@@ -1,267 +0,0 @@
package net.minecraftforge.forge.tasks
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.Opcodes
abstract class CheckATs extends DefaultTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@InputFile @Optional abstract RegularFileProperty getMappings()
@TaskAction
protected void exec() {
def mappings = mappings.map { IMappingFile.load(it.asFile) }.getOrNull()
Util.init()
def parse = { line ->
def idx = line.indexOf('#')
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
def (modifier, cls, desc) = (line.trim() + ' ').split(' ', -1)
def key = cls + (desc.isEmpty() ? '' : ' ' + desc)
return [modifier, cls, desc, comment, key]
}
def accessLevel = { access ->
if ((access & Opcodes.ACC_PUBLIC) != 0) return 3
if ((access & Opcodes.ACC_PROTECTED) != 0) return 2
if ((access & Opcodes.ACC_PRIVATE) != 0) return 0
return 1
}
def accessStr = { access ->
if (access.endsWith('-f') || access.endsWith('+f'))
return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
def json = inheritance.get().asFile.json()
ats.each { f ->
TreeMap lines = [:]
def group = null
for (def line : f.readLines()) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
def (modifier, cls, desc, comment, key) = parse.call(line.substring(7))
if (desc != '*' && desc != '*()' && desc != '<init>')
throw new IllegalStateException('Invalid group: ' + line)
group = [modifier: modifier, cls: cls, desc: desc, comment: comment,
existing: [] as Set,
children: [] as TreeSet,
group: true
]
if (lines.containsKey(key))
throw new IllegalStateException('Duplicate group: ' + line)
lines[key] = group
} else if (group != null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
def (modifier, cls, desc, comment, key) = parse.call(line)
group['existing'].add(key)
}
} else if (line.startsWith('#endgroup')) {
throw new IllegalStateException('Invalid group ending: ' + line)
} else if (line.startsWith('#')) {
//Nom
} else {
def (modifier, cls, desc, comment, key) = parse.call(line)
if (lines.containsKey(key)) {
println('Duplicate: ' + line)
continue
}
lines[key] = [modifier: modifier, cls: cls, desc: desc, comment: comment, group: false]
}
}
// Process Groups, this will remove any entries outside the group that is covered by the group
for (def key : new ArrayList<>(lines.keySet())) {
def entry = lines.get(key)
if (entry != null && entry['group']) {
def cls = entry['cls']
def jcls = json.get(cls.replaceAll('\\.', '/'))
if (jcls == null) {
lines.remove(key)
println('Invalid Group: ' + key)
} else if ('*' == entry['desc']) {
if (!jcls.containsKey('fields')) {
lines.remove(key)
println('Invalid Group, Class has no fields: ' + key)
} else {
jcls['fields'].each { field, value ->
def fkey = cls + ' ' + field
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
if (lines.containsKey(fkey)) {
lines.remove(fkey)
} else if (!entry['existing'].contains(fkey)) {
println('Added: ' + fkey)
}
entry['children'].add(fkey)
} else if (lines.containsKey(fkey)) {
lines.remove(fkey)
println('Removed: ' + fkey)
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
} else if ('*()' == entry['desc']) {
if (!jcls.containsKey('methods')) {
lines.remove(key)
println('Invalid Group, Class has no methods: ' + key)
} else {
jcls['methods'].each{ mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$'))
return
key = cls + ' ' + mtd.replace(' ', '')
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
if (lines.containsKey(key)) {
lines.remove(key)
} else if (!entry['existing'].contains(key)) {
println('Added: ' + key)
}
entry['children'].add(key)
} else if (lines.containsKey(key)) {
lines.remove(key)
println('Removed: ' + key)
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
} else if ('<init>' == entry['desc']) { //Make all public non-abstract subclasses
json.each{ tcls,value ->
if (!value.containsKey('methods') || ((value['access'] & Opcodes.ACC_ABSTRACT) != 0))
return
def parents = [] as Set
def parent = tcls
while (parent != null && json.containsKey(parent)) {
parents.add(parent)
def p = json[parent]
parent = p == null ? null : p['superName']
}
if (parents.contains(cls.replaceAll('\\.', '/'))) {
value['methods'].each{ mtd, v ->
if (mtd.startsWith('<init>')) {
def child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel.call(v['access']) < 3) {
if (lines.containsKey(child)) {
lines.remove(child)
} else if (!entry['existing'].contains(child)) {
println('Added: ' + child)
}
entry['children'].add(child)
} else if (lines.containsKey(child)) {
lines.remove(child)
println('Removed: ' + child)
}
}
}
}
}
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
}
}
}
// Process normal lines, remove invalid and remove narrowing
for (def key : new ArrayList<>(lines.keySet())) {
def entry = lines.get(key)
if (entry != null && !entry['group']) {
def cls = entry['cls']
def jcls = json.get(cls.replaceAll('\\.', '/'))
if (jcls == null) {
lines.remove(key)
println('Invalid: ' + key)
} else if (entry['desc'] == '') {
if (accessLevel.call(jcls['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
}
} else if (!entry['desc'].contains('(')) {
if (!jcls.containsKey('fields') || !jcls['fields'].containsKey(entry['desc'])) {
lines.remove(key)
println('Invalid: ' + key)
} else {
def value = jcls['fields'][entry['desc']]
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
println(entry.comment)
}
}
} else {
def jdesc = entry['desc'].replace('(', ' (')
if (!jcls.containsKey('methods') || !jcls['methods'].containsKey(jdesc)) {
lines.remove(key)
println('Invalid: ' + key)
} else {
def value = jcls['methods'][jdesc]
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
lines.remove(key)
println('Invalid Narrowing: ' + key)
}
}
}
}
}
def data = []
def remapComment = { entry ->
if (!mappings || !entry || !entry.desc) return null
def comment = entry.comment?.substring(1)?.trim()
def jsonCls = json.get(entry.cls.replaceAll('\\.', '/'))
def mappingsClass = mappings?.getClass(jsonCls.name)
if (!mappingsClass) return entry.comment
def idx = entry.desc.indexOf('(')
def mappedName = idx == -1
? mappingsClass.remapField(entry.desc)
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
if (!mappedName) return entry.comment
if (mappedName == '<init>')
mappedName = 'constructor'
if (comment?.startsWith(mappedName))
return '# ' + comment
if (comment && comment.indexOf(' ') != -1) {
def split = comment.split(" - ").toList()
if (split[0].indexOf(' ') != -1)
// The first string is more than one word, so append before it
return "# ${mappedName} - ${comment}"
split.remove(0)
return "# ${mappedName} - ${String.join(' - ', split)}"
}
return '# ' + mappedName
}
lines.each { key,value ->
if (!value.group) {
def comment = remapComment.call(value)
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add('#group ' + value.modifier + ' ' + key + (value.comment == null ? '' : ' ' + value.comment))
value.children.each {
def line = value.modifier + ' ' + it
def (modifier, cls, desc, comment) = parse.call(line)
comment = remapComment.call([modifier: modifier, cls: cls, desc: desc, comment: comment])
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
f.text = data.join('\n')
}
}
}
@@ -1,94 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends DefaultTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@TaskAction
protected void exec() {
Util.init()
def known = []
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
def visitor = new ClassVisitor(Opcodes.ASM9) {
private String cls
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
excs.each { f ->
def lines = []
f.eachLine { line ->
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
println('Invalid: ' + line)
return
}
def (key, value) = line.split('=', 2)
if (!known.contains(key)) {
println('Unknown: ' + line)
return
}
def (cls, desc) = key.split('\\.', 2)
if (!desc.contains('(')) {
println('Invalid: ' + line)
return
}
def name = desc.split('\\(', 2)[0]
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length != Type.getArgumentTypes(desc).length) {
println('Invalid: ' + line)
return
}
lines.add(line)
}
f.text = lines.sort().join('\n')
}
}
}
@@ -1,219 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.Paths
import java.util.regex.Pattern
abstract class CheckPatches extends DefaultTask {
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input boolean autoFix = false
@TaskAction
protected void exec() {
def hasS2SArtifact = [
Paths.get("patches/minecraft/net/minecraft/client/renderer/ViewArea.java.patch"),
Paths.get("patches/minecraft/net/minecraft/data/models/blockstates/Variant.java.patch")
]
def verified = true;
patchDir.get().asFileTree.each { patch ->
def patchPath = project.rootDir.toPath().relativize(patch.toPath())
verified &= verifyPatch(patch, autoFix, patchPath.toString(), hasS2SArtifact.contains(patchPath))
}
if (!verified)
throw new RuntimeException('One or more patches failed verification. Check the log for errors.')
}
def verifyPatch(patch, fix, patchPath, hasS2SArtifact) {
def hunk_start_pattern = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
def white_space_pattern = Pattern.compile('^[+\\-]\\s*$')
def import_pattern = Pattern.compile('^[+\\-]\\s*import.*')
def field_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
def method_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
def class_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
def accessMap = [("private"):0, (null):1, ("protected"):2, ("public"):3]
def hasProblem = false;
def lines = patch.readLines()
def hunksStart = 0
def onlyWhiteSpace = false
def didFix = false
def newLines = []
def whiteSpaceErrors = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i = 2
for (; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (hunk_start_pattern.matcher(line).find()) {
if (onlyWhiteSpace) {
if (fix || hasS2SArtifact) {
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
didFix = true
}
else {
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
hasProblem = true
}
}
else {
if (!whiteSpaceErrors.empty)
hasProblem = true
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
whiteSpaceErrors.clear()
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim.equals(currTrim)) {
prefixChange = true
}
def pMatcher = field_pattern.matcher(prevLine)
def cMatcher = field_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
pMatcher = method_pattern.matcher(prevLine)
cMatcher = method_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
pMatcher = class_pattern.matcher(prevLine)
cMatcher = class_pattern.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
def nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
def nextTrim = nextLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim.equals(currTrim)) {
prefixChange = true
}
}
}
def isWhiteSpaceChange = white_space_pattern.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && import_pattern.matcher(line).find()
}
else if (isWhiteSpaceChange) {
def prevLineChange = prevLine.startsWithAny('+','-')
def nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
whiteSpaceErrors.add(String.format("Patch contains white space change in valid hunk at line %d (cannot auto fix), file: %s", i + 1, patchPath))
}
}
if (line.contains("\t")) {
if (!fix) {
logger.lifecycle("Patch contains tabs on line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
else {
logger.lifecycle("Fixing tabs on line {}, file: {}", i + 1, patchPath)
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
didFix = true
}
}
if (import_pattern.matcher(line).find() && !hasS2SArtifact) {
logger.lifecycle("Patch contains import change on line {}, file: {}", i + 1, patchPath)
hasProblem = true
}
}
}
if (onlyWhiteSpace) {
if (fix || hasS2SArtifact) {
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
didFix = true
}
else {
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
hasProblem = true
}
}
else {
if (!whiteSpaceErrors.empty)
hasProblem = true
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
}
if (didFix) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch.toPath())
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
patch.withWriter('UTF-8') { writer ->
newLines.each { l -> writer.write(l) }
}
}
}
return !hasProblem
}
}
@@ -1,107 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.TaskAction
abstract class CheckSAS extends DefaultTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@TaskAction
protected void exec() {
Util.init()
def json = inheritance.get().asFile.json()
sass.each { f ->
def lines = []
f.eachLine { line ->
if (line[0] == '\t') return //Skip any tabbed lines, those are ones we add
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
def (cls, name, desc) = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
cls = cls.replaceAll('\\.', '/')
if (json[cls] == null) {
println('Invalid: ' + line)
} else if (name.isEmpty()) { //Class SAS
def toAdd = []
def clsSided = isSided(json[cls])
def sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
}
*/
if (json[cls]['methods'] != null) {
for (entry in json[cls]['methods']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else
println('Invalid: ' + line)
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else
*/
println('Invalid: ' + line)
} else { // Methods
if (json[cls]['methods'] == null || !isSided(json[cls]['methods'][name + ' ' + desc]))
println('Invalid: ' + line)
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(json, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(json, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
f.text = lines.join('\n')
}
}
protected static isSided(json) {
if (json == null || json['annotations'] == null)
return false
for (ann in json['annotations']) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;'.equals(ann.desc))
return true
}
return false
}
protected static findChildMethods(json, cls, desc)
{
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -1,39 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
abstract class DownloadBaseCompatibilityBinPatches extends DefaultTask {
DownloadBaseCompatibilityBinPatches() {
group = 'jar compatibility'
onlyIf {
inputVersion.getOrNull() != null
}
output.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
}
@TaskAction
void run() {
def dep = project.dependencies.create("net.minecraftforge:forge:${inputVersion.get()}:userdev")
def baseForgeUserdev = project.configurations.detachedConfiguration(dep).resolve().iterator().next()
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
Files.copy(joinedLzma.toPath(), output.get().asFile.toPath())
}
@Input
@Optional
abstract Property<String> getInputVersion()
@OutputFile
abstract RegularFileProperty getOutput()
}
@@ -0,0 +1,49 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
@CompileStatic
@TupleConstructor
final class InheritanceData implements Annotatable {
String name
int access
String superName
List<String> interfaces = []
Map<String, Method> methods = [:]
Map<String, Field> fields = [:]
List<Annotation> annotations = []
@TupleConstructor
static final class Method implements Annotatable {
int access
String override
List<Annotation> annotations = []
}
@TupleConstructor
static final class Field implements Annotatable {
int access
String desc
List<Annotation> annotations = []
}
@TupleConstructor
static final class Annotation {
String desc
}
private static final Gson GSON = new Gson()
static Map<String, InheritanceData> parse(File file) {
try (final reader = file.newReader()) {
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
}
}
}
@CompileStatic
interface Annotatable {
List<InheritanceData.Annotation> getAnnotations()
}
@@ -0,0 +1,44 @@
package net.minecraftforge.forge.tasks
import org.apache.commons.io.IOUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
abstract class MergeJars extends DefaultTask {
MergeJars() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
}
@TaskAction
void run() {
def jars = inputJars.files
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
for (def jar : jars) {
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
def entry
while ((entry = zin.getNextEntry()) != null) {
ZipEntry _new = new ZipEntry(entry.getName())
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
zout.putNextEntry(_new)
IOUtils.copy(zin, zout)
}
}
}
}
}
@InputFiles
abstract ConfigurableFileCollection getInputJars()
@OutputFile
abstract RegularFileProperty getOutput()
}
@@ -0,0 +1,57 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.StandardCopyOption
abstract class SetupCheckJarCompatibility extends DefaultTask {
SetupCheckJarCompatibility() {
group = 'jar compatibility'
onlyIf {
inputVersion.getOrNull() != null
}
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
}
@TaskAction
void run() {
def inputVersion = inputVersion.get()
def fmlLibs = project.configurations.detachedConfiguration(project.PACKED_DEPS.collect {
def artifactId = it.split(':')[1]
return project.dependencies.create("net.minecraftforge:${artifactId}:${inputVersion}")
}.toArray(Dependency[]::new))
project.tasks.named('checkJarCompatibility') {
baseLibraries.from(project.provider {
fmlLibs.resolvedConfiguration.lenientConfiguration.files
})
}
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
project.rootProject.extensions.download.run {
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
dest baseForgeUserdev
}
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
@Input
@Optional
abstract Property<String> getInputVersion()
@OutputFile
abstract RegularFileProperty getBaseBinPatchesOutput()
}
@@ -0,0 +1,74 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import org.eclipse.jgit.api.Git
import javax.annotation.Nullable
import java.util.stream.Collectors
@CompileStatic
class TeamcityRequests {
public static final Gson GSON = new Gson()
@Nullable
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
conn.setRequestProperty('Accept', 'application/json')
conn.setReadTimeout(5 * 1000)
conn.setConnectTimeout(5 * 1000)
conn.connect()
if (conn.responseCode !== 200) {
return null
}
try (final InputStream is = conn.getInputStream()) {
GSON.fromJson(new InputStreamReader(is), clazz.getType())
}
}
@Nullable
static Map<String, Build> buildsByCommit() throws IOException {
final Map<String, Build> builds = [:]
jsonRequest(new TypeToken<Builds>() {}, "https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),count:300,status:SUCCESS")
?.build?.forEach {
it.revisions.revision.each { rev -> builds[rev.version] = it }
}
return builds
}
@Nullable
static String attemptFindBase(File gitPath) {
try (final git = Git.open(gitPath)) {
final Map<String, Build> buildByCommit = buildsByCommit()
for (final commit in git.log().setMaxCount(100).call()) {
// Find the first commit which was built on CI
final build = buildByCommit[commit.id.name]
if (build) {
return build.number
}
}
} catch (Exception ignored) {}
return null
}
static final class Builds {
public List<Build> build
}
static final class Build {
public String number
public Revisions revisions
}
static final class Revisions {
public int count
public List<Revision> revision
}
static final class Revision {
public String version
}
}
@@ -18,17 +18,17 @@ public class Util {
return md.digest().collect {String.format "%02x", it}.join()
}
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
Date.metaClass.iso8601 = { ->
def format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
def result = format.format(delegate)
return result[0..21] + ':' + result[22..-1]
}
String.metaClass.rsplit = { String del, int limit = -1 ->
def lst = new ArrayList()
def x = 0, idx
@@ -41,7 +41,7 @@ public class Util {
return lst
}
}
public static String[] getClasspath(project, libs, artifact) {
def ret = []
artifactTree(project, artifact).each { key, lib ->
@@ -51,7 +51,7 @@ public class Util {
}
return ret
}
public static def getArtifacts(project, config, classifiers) {
def ret = [:]
config.resolvedConfiguration.resolvedArtifacts.each {
@@ -75,7 +75,7 @@ public class Util {
url = "https://maven.minecraftforge.net/${path}"
}
//TODO remove when Mojang launcher is updated
if (!classifiers && art.classifier != null) {
if (!classifiers && art.classifier != null) {
//Mojang launcher doesn't currently support classifiers, so... move it to part of the version, and force the extension to 'jar'
// However, keep the path normal so that our mirror system works.
art.version = "${art.version}-${art.classifier}"
@@ -96,14 +96,14 @@ public class Util {
}
return ret
}
public static def getMavenPath(task) {
public static def getMavenPath(task) {
def classifier = task.archiveClassifier.get()
def dep = "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
return "${task.project.group.replace('.', '/')}/${task.project.name}/${task.project.version}/${task.project.name}-${task.project.version}".toString() + (classifier == '' ? '' : '-' + classifier) + '.jar'
}
public static def getMavenDep(task) {
public static def getMavenDep(task) {
def classifier = task.archiveClassifier.get()
return "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
}
@@ -127,7 +127,7 @@ public class Util {
def files = cfg.resolve()
return getArtifacts(project, cfg, true)
}
private static boolean checkExists(url) {
try {
def code = new URL(url).openConnection().with {
@@ -142,4 +142,10 @@ public class Util {
throw e
}
}
static String getLatestForgeVersion(mcVersion) {
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
final ver = json.promos["$mcVersion-latest"]
ver === null ? null : (mcVersion + '-' + ver)
}
}
@@ -0,0 +1,310 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
import net.minecraftforge.forge.tasks.InheritanceData
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.objectweb.asm.Opcodes
@CompileStatic
abstract class CheckATs extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@InputFile @Optional abstract RegularFileProperty getMappings()
@Override
void check(Reporter reporter, boolean fix) {
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
ats.each {
final lines = process(it, reporter, inheritance)
if (fix) {
it.text = joinBack(lines, inheritance, mappings).join('\n')
}
}
}
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
final Map<String, ATParser.Entry> constructorGroups = [:]
final itr = lines.entrySet().iterator()
final toRemove = []
while (itr.hasNext()) {
final next = itr.next()
String key = next.key
final entry = next.value
if (entry === null) continue
final binaryName = entry.cls.replaceAll('\\.', '/')
// Process Groups, this will remove any entries outside the group that is covered by the group
if (entry.group) {
final jcls = inheritance[binaryName]
if (jcls === null) {
itr.remove()
reporter.report("Invalid group: $key")
} else if ('*' == entry.desc) {
if (!jcls.fields) {
itr.remove()
reporter.report("Invalid group, class has no fields: $key")
} else {
jcls.fields.each { field, value ->
final fkey = entry.cls + ' ' + field
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(fkey)) {
toRemove.add(fkey)
} else if (!entry.existing.contains(fkey)) {
reporter.report("Missing group entry: $fkey")
}
entry.children.add(fkey)
} else if (lines.containsKey(fkey)) {
toRemove.add(fkey)
reporter.report("Found invalid group entry: $fkey")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('*()' == entry.desc) {
if (!jcls.methods) {
itr.remove()
reporter.report("Invalid group, class has no methods: $key")
} else {
jcls.methods.each { mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
key = entry.cls + ' ' + mtd.replace(' ', '')
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(key)) {
toRemove.add(key)
} else if (!entry.existing.contains(key)) {
reporter.report("Missing group entry: $key")
}
entry.children.add(key)
} else if (lines.containsKey(key)) {
toRemove.add(key)
reporter.report("Found invalid group entry: $key")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
constructorGroups.put(binaryName, entry)
}
}
// Process normal lines, remove invalid and remove narrowing
else {
def jcls = inheritance.get(binaryName)
if (jcls === null) {
itr.remove()
reporter.report("Invalid: $key")
} else if (entry.desc == '') {
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
} else if (!entry.desc.contains('(')) {
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.fields[entry.desc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
}
}
} else {
final jdesc = entry.desc.replace('(', ' (')
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.methods[jdesc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
}
}
}
}
inheritance.each { tcls, value ->
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
String parent = tcls
while (parent !== null) {
constructorGroups[parent]?.tap { entry ->
value.methods.each { mtd, v ->
if (mtd.startsWith('<init>')) {
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel(v.access) < 3) {
if (lines.containsKey(child)) {
toRemove.add(child)
} else if (child !in entry.existing) {
reporter.report("Missing group entry: $child")
}
entry.children.add(child)
} else if (lines.containsKey(child)) {
toRemove.add(child)
reporter.report("Found invalid group entry: $child")
}
}
}
}
parent = inheritance[parent]?.superName
}
}
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
toRemove.each(lines.&remove)
return lines
}
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
final List<String> data = []
final remapComment = { ATParser.Entry entry ->
if (!mappings || !entry || !entry.desc) return null
final comment = entry.comment?.substring(1)?.trim()
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
final mappingsClass = mappings?.getClass(jsonCls.name)
if (mappingsClass === null) return entry.comment
final idx = entry.desc.indexOf('(')
String mappedName = idx == -1
? mappingsClass.remapField(entry.desc)
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
if (!mappedName) return entry.comment
if (mappedName == '<init>')
mappedName = 'constructor'
if (comment?.startsWith(mappedName))
return '# ' + comment
if (comment && comment.indexOf(' ') !== -1) {
def split = comment.split(' - ').toList()
if (split[0].indexOf(' ') !== -1)
// The first string is more than one word, so append before it
return "# ${mappedName} - ${comment}"
split.remove(0)
return "# ${mappedName} - ${String.join(' - ', split)}"
}
return '# ' + mappedName
}
lines.each { key, value ->
if (!value.group) {
def comment = remapComment.call(value)
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
value.children.each {
final line = value.modifier + ' ' + it
final entry = ATParser.parseEntry(line)
final comment = remapComment(entry)
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
return data
}
static int accessStr(String access) {
if (access.endsWith('-f') || access.endsWith('+f')) return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
static int accessLevel(int access) {
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
return 1
}
}
@CompileStatic
class ATParser {
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
TreeMap<String, Entry> outLines = new TreeMap<>()
Entry group = null
for (final line : lines) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
final entry = parseEntry(line.substring(7))
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
reporter.report("Invalid group: $line", false)
}
entry.group = true
entry.children = []
entry.existing = []
group = entry
if (outLines.containsKey(entry.key)) {
reporter.report("Duplicate group: $line", false)
}
outLines[entry.key] = group
} else if (group !== null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
final key = parseEntry(line).key
group.existing.add(key)
}
} else if (line.startsWith('#endgroup')) {
reporter.report("Invalid group ending: $line", false)
} else if (line.startsWith('#')) {
//Nom
} else {
final entry = parseEntry(line)
if (outLines.containsKey(entry.key)) {
reporter.report("Found duplicate: $line")
continue
}
outLines[entry.key] = entry
}
}
return outLines
}
static Entry parseEntry(String line) {
final idx = line.indexOf('#')
final String comment = idx === -1 ? null : line.substring(idx)
if (idx !== -1) line = line.substring(0, idx - 1)
final data = (line.trim() + ' ').split(' ', -1)
new Entry(data[0], data[1], data[2], comment)
}
@TupleConstructor
static final class Entry {
String modifier, cls, desc, comment
Set<String> existing
TreeSet<String> children
boolean group = false
@Lazy
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
Object getAt(String key) {
return getProperty(key)
}
}
}
@@ -0,0 +1,93 @@
package net.minecraftforge.forge.tasks.checks
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends CheckTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@Override
void check(Reporter reporter, boolean fix) {
final Set<String> known = []
collectKnown(known)
excs.each { f ->
final lines = []
f.eachLine { line ->
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
return
}
if (idx != -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
reporter.report("Invalid: $line")
return
}
def (String key, String value) = line.split('=', 2)
if (!known.contains(key)) {
reporter.report("Unknown: $line")
return
}
String desc = key.split('\\.', 2)[1]
if (!desc.contains('(')) {
reporter.report("Invalid: $line")
return
}
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
reporter.report("Invalid: $line")
return
}
lines.add(line)
}
if (fix) f.text = lines.sort().join('\n')
}
}
private void collectKnown(Collection<String> known) {
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
final visitor = new ClassVisitor(Opcodes.ASM9) {
private String cls
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
}
}
@@ -0,0 +1,9 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
@CompileStatic
enum CheckMode {
CHECK,
FIX
}
@@ -0,0 +1,177 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
@CompileStatic
abstract class CheckPatches extends CheckTask {
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
@Override
void check(Reporter reporter, boolean fix) {
final patchDir = getPatchDir().get().asFile.toPath()
Files.walk(patchDir).withCloseable {
it.filter(Files.&isRegularFile).forEach { path ->
final String relativeName = patchDir.relativize(path).toString()
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
}
}
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
final oldFixedErrors = reporter.fixed.size()
final lines = Files.readAllLines(patch)
int hunksStart = 0
boolean onlyWhiteSpace = false
final List<String> newLines = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i
for (i = 2; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (HUNK_START_PATTERN.matcher(line).find()) {
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
int toRemove = i - hunksStart
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim == currTrim) {
prefixChange = true
}
def pMatcher = FIELD_PATTERN.matcher(prevLine)
def cMatcher = FIELD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = METHOD_PATTERN.matcher(prevLine)
cMatcher = METHOD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = CLASS_PATTERN.matcher(prevLine)
cMatcher = CLASS_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
final nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
final currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim == currTrim) {
prefixChange = true
}
}
}
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
} else if (isWhiteSpaceChange) {
final prevLineChange = prevLine.startsWithAny('+','-')
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false)
}
}
if (line.contains('\t')) {
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
}
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
}
}
}
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch)
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
Files.newBufferedWriter(patch).withCloseable {
newLines.each { l -> it.write(l) }
}
}
}
}
}
@@ -0,0 +1,110 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Annotatable
import net.minecraftforge.forge.tasks.InheritanceData
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
@CompileStatic
abstract class CheckSAS extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
sass.each { f ->
final lines = []
f.eachLine { line ->
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
final idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
cls = cls.replaceAll('\\.', '/')
if (inheritance[cls] === null) {
reporter.report("Invalid: $line")
} else if (name.isEmpty()) { //Class SAS
final toAdd = []
final clsSided = isSided(inheritance[cls])
boolean sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
} */
final clsInh = inheritance[cls]
if (clsInh.methods) {
for (entry in clsInh.methods) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else {
reporter.report("Invalid: $line")
}
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else */
reporter.report("Invalid: $line")
} else { // Methods
final clsInh = inheritance[cls]
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
reporter.report("Invalid: $line")
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
if (fix) f.text = lines.join('\n')
}
}
protected static isSided(Annotatable annotatable) {
if (annotatable === null) return false
for (ann in annotatable.annotations) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
return true
}
return false
}
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -0,0 +1,109 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.VerificationTask
@CompileStatic
abstract class CheckTask extends DefaultTask implements VerificationTask {
@Input
abstract Property<CheckMode> getMode()
private boolean ignoreFailures = false
@Input
@Override
boolean getIgnoreFailures() {
return ignoreFailures
}
@Override
void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures
}
@TaskAction
void run() {
Util.init()
final doFix = getMode().get() === CheckMode.FIX
final Reporter reporter = new Reporter(doFix)
check(reporter, doFix)
if (reporter.messages) {
if (getMode().get() === CheckMode.CHECK) {
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
}
} else {
if (logger.isEnabled(LogLevel.DEBUG)) {
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
} else {
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
}
if (reporter.notFixed) {
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
}
}
}
}
}
abstract void check(Reporter reporter, boolean fix)
@CompileStatic
static final class Reporter {
final boolean trackFixed
Reporter(boolean trackFixed) {
this.trackFixed = trackFixed
}
public final List<String> messages = []
public final List<String> fixed = []
public final List<String> notFixed = []
void report(String message, boolean canBeFixed = true) {
messages.add(message)
if (trackFixed) {
if (canBeFixed) {
fixed.add(message)
} else {
notFixed.add(message)
}
}
}
}
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
taskName = taskName.capitalize()
tasks.register("check$taskName", clazz) { CheckTask task ->
configuration.setDelegate((T) task)
configuration.call(task)
task.mode.set(CheckMode.CHECK)
task.group = 'checks'
}
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
configuration.setDelegate((T) task)
configuration.call(task)
task.mode.set(CheckMode.FIX)
task.group = 'checks'
}
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
}
}
+14 -11
View File
@@ -4,14 +4,16 @@ MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.18.2-&query=$.promos["1.18.2-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)](https://files.minecraftforge.net)
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.18.2-&query=$.promos["1.18.2-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)](https://files.minecraftforge.net) [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)](https://discord.gg/UvedJ9m) [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)](https://www.patreon.com/LexManos)
)](https://files.minecraftforge.net) [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)](https://www.patreon.com/LexManos)
Forge is a free, open-source modding API all of your favourite mods use!
| Version | Support |
| ------------- | ------------- |
| 1.18.x | Active |
| 1.16.x | LTS |
| 1.20.x | Active |
| 1.19.x | Legacy |
| 1.18.x | Legacy |
| 1.16.x | Legacy |
* [Download]
* [Forum]
@@ -28,25 +30,25 @@ You can download the installer for the *Recommended Build* or the
more unstable as a result. The installer will attempt to install Forge
into your vanilla launcher environment, where you can then create a new
profile using that version and play the game!
For support and questions, visit [the Support Forum](https://www.minecraftforge.net/forum/forum/18-support-bug-reports/).
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
[Here is a short video from Rorax showing how to install and setup Forge.](https://www.youtube.com/watch?v=lB3ArN_-3Oc)
# Creating Mods
[See the "Getting Started" section in the Forge Documentation](https://mcforge.readthedocs.io/en/latest/gettingstarted/).
[See the "Getting Started" section in the Forge Documentation](https://docs.minecraftforge.net/en/1.18.x/gettingstarted/).
# Contribute to Forge
If you wish to actually inspect Forge, submit PRs or otherwise work
with Forge itself, you're in the right place!
[See the guide to setting up a Forge workspace](http://mcforge.readthedocs.io/en/latest/forgedev/).
[See the guide to setting up a Forge workspace](https://docs.minecraftforge.net/en/1.18.x/forgedev/).
### Pull requests
[See the "Making Changes and Pull Requests" section in the Forge documentation](https://mcforge.readthedocs.io/en/latest/forgedev/#making-changes-and-pull-requests).
[See the "Making Changes and Pull Requests" section in the Forge documentation](https://docs.minecraftforge.net/en/1.18.x/forgedev/#making-changes-and-pull-requests).
Please read the contributing guidelines found [here](CONTRIBUTING.md) before making a pull request.
@@ -60,6 +62,7 @@ We require all contributors to acknowledge the [Forge Contributor
*Forge is a large project with many collaborators working on it around the clock. Forge is and will always remain free to use and modify. However, it costs money to run such a large project as this, so please consider [becoming a patron](https://www.patreon.com/LexManos).*
[Download]: https://files.minecraftforge.net/
[Forum]: https://www.minecraftforge.net/forum/
[Discord]: https://discord.gg/UvedJ9m
[Documentation]: https://mcforge.readthedocs.io
[Forum]: https://forums.minecraftforge.net
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
[Discord]: https://discord.minecraftforge.net
[Documentation]: https://docs.minecraftforge.net/en/1.18.x/
@@ -44,5 +44,26 @@ public interface IExtensionPoint<T extends Record>
* }</pre>
*
*/
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {}
}
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
public static final String IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31";
/**
* Ignores any version information coming from the server - use for server only mods
*/
public static final Supplier<DisplayTest> IGNORE_SERVER_VERSION = () -> new DisplayTest(IGNORESERVERONLY, (remoteVersion, isFromServer) -> true);
/**
* Ignores all information and provides no information
*/
public static final Supplier<DisplayTest> IGNORE_ALL_VERSION = () -> new DisplayTest("", (remoteVersion, isFromServer) -> true);
/**
* An optional alternative to {@link #DisplayTest(Supplier, BiPredicate)} which accepts a constant version string
* instead of a {@link Supplier}.
* <p>Internally, the provided version string is wrapped in a Supplier for you.</p>
*/
public DisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
this(() -> version, remoteVersionTest);
}
}
}
@@ -20,6 +20,7 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -56,9 +57,24 @@ public abstract class ModContainer
this.namespace = this.modId;
this.modInfo = info;
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
// default displaytest extension checks for version string match
registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(()->this.modInfo.getVersion().toString(),
(incoming, isNetwork)->Objects.equals(incoming, this.modInfo.getVersion().toString())));
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest").orElse("MATCH_VERSION"); // missing defaults to DEFAULT type
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
case "MATCH_VERSION" -> // default displaytest checks for version string match
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
(incoming, isNetwork) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
case "IGNORE_SERVER_VERSION" -> // Ignores any version information coming from the server - use for server only mods
IExtensionPoint.DisplayTest.IGNORE_SERVER_VERSION;
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
IExtensionPoint.DisplayTest.IGNORE_ALL_VERSION;
case "NONE" -> null; // NO display test at all - use this if you're going to do your own display test
default -> // any other value throws an exception
throw new IllegalArgumentException("Invalid displayTest value supplied in mods.toml");
};
if (displayTestSupplier != null)
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTestSupplier);
else
extensionPoints.remove(IExtensionPoint.DisplayTest.class);
}
/**
@@ -127,6 +143,22 @@ public abstract class ModContainer
extensionPoints.put(point, extension);
}
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> displayTest);
}
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTest);
}
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
}
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
}
public void addConfig(final ModConfig modConfig) {
configs.put(modConfig.getType(), modConfig);
}
@@ -153,4 +185,4 @@ public abstract class ModContainer
* @param e Event to accept
*/
protected <T extends Event & IModBusEvent> void acceptEvent(T e) {}
}
}
@@ -11,11 +11,11 @@ import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.locating.IModFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -28,6 +28,7 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -222,7 +223,7 @@ public class ModLoader
}
}
private List<ModContainer> buildMods(final ModFile modFile)
private List<ModContainer> buildMods(final IModFile modFile)
{
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
@@ -244,7 +245,7 @@ public class ModLoader
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).collect(Collectors.toList());
}
private ModContainer buildModContainerFromTOML(final ModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
try {
final String modId = idToProviderEntry.getKey();
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
@@ -8,6 +8,7 @@ package net.minecraftforge.fml;
import net.minecraftforge.fml.config.IConfigSpec;
import net.minecraftforge.fml.config.ModConfig;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
public class ModLoadingContext
@@ -45,6 +46,44 @@ public class ModLoadingContext
getActiveContainer().registerExtensionPoint(point, extension);
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)}.</p>
* @param displayTest The {@link IExtensionPoint.DisplayTest} to register
*/
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
getActiveContainer().registerDisplayTest(() -> displayTest);
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest supplier with {@link #registerExtensionPoint(Class, Supplier)}.</p>
* @param displayTest The {@link Supplier<IExtensionPoint.DisplayTest>} to register
*/
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
getActiveContainer().registerDisplayTest(displayTest);
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)} that also
* creates the DisplayTest instance for you using the provided parameters.</p>
* @see IExtensionPoint.DisplayTest#DisplayTest(String, BiPredicate)
*/
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)} that also
* creates the DisplayTest instance for you using the provided parameters.</p>
* @see IExtensionPoint.DisplayTest#DisplayTest(Supplier, BiPredicate)
*/
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
}
@@ -117,14 +117,22 @@ public class VersionChecker
/**
* Returns the response body as a String for the given URL while following redirects
*/
private String openUrlString(URL url) throws IOException, URISyntaxException, InterruptedException {
private String openUrlString(URL url, IModInfo mod) throws IOException, URISyntaxException, InterruptedException {
URL currentUrl = url;
final StringBuilder sb = new StringBuilder();
sb.append("Java-http-client/").append(System.getProperty("java.version")).append(' ');
sb.append("MinecraftForge/").append(FMLLoader.versionInfo().mcAndForgeVersion()).append(' ');
sb.append(mod.getModId()).append('/').append(mod.getVersion());
final String userAgent = sb.toString();
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++)
{
var request = HttpRequest.newBuilder()
.uri(currentUrl.toURI())
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
.setHeader("Accept-Encoding", "gzip")
.setHeader("User-Agent", userAgent)
.GET()
.build();
@@ -166,7 +174,7 @@ public class VersionChecker
URL url = mod.getUpdateURL().get();
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
String data = openUrlString(url);
String data = openUrlString(url, mod);
LOGGER.debug("[{}] Received version check data:\n{}", mod.getModId(), data);
+4 -1
View File
@@ -16,7 +16,7 @@ dependencies {
api("org.ow2.asm:asm:${ASM_VERSION}")
api("org.ow2.asm:asm-tree:${ASM_VERSION}")
api("org.ow2.asm:asm-commons:${ASM_VERSION}")
api('net.minecraftforge:forgespi:4.0.+')
api("net.minecraftforge:forgespi:${SPI_VERSION}")
api('org.apache.logging.log4j:log4j-api:2.17.0')
api('org.slf4j:slf4j-api:1.8.0-beta4')
api('com.google.guava:guava:21.0')
@@ -27,6 +27,9 @@ dependencies {
api('com.electronwill.night-config:toml:3.6.4')
api('cpw.mods:modlauncher:9.0.+')
api('net.minecraftforge:coremods:5.0.+')
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
implementation('org.apache.logging.log4j:log4j-core:2.17.0')
annotationProcessor('org.apache.logging.log4j:log4j-core:2.17.0')
@@ -0,0 +1,38 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ClasspathLocatorUtils {
private static final Logger LOGGER = LogManager.getLogger();
public static Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
try {
Path path;
final URI uri = resource.toURI();
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
} else {
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
}
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
return path;
} catch (NullPointerException | URISyntaxException e) {
LOGGER.fatal(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
}
}
}
@@ -0,0 +1,61 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.apache.logging.log4j.LogManager;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class ClasspathTransformerDiscoverer implements ITransformerDiscoveryService {
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
@Override
public List<NamedPath> candidates(Path gameDirectory) {
return Collections.emptyList();
}
@Override
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
if (launchTarget != null && launchTarget.contains("dev")) {
this.scan(gameDirectory);
}
return List.copyOf(found);
}
private final static List<NamedPath> found = new ArrayList<>();
public static List<Path> allExcluded() {
return found.stream().map(np->np.paths()[0]).toList();
}
private void scan(final Path gameDirectory) {
try {
locateTransformers("META-INF/services/cpw.mods.modlauncher.api.ITransformationService");
locateTransformers("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator");
} catch (IOException e) {
LogManager.getLogger().error("Error during discovery of transform services from the classpath", e);
}
}
private void locateTransformers(String resource) throws IOException {
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Path path = ClasspathLocatorUtils.findJarPathFor(resource, url.toString(), url);
if (legacyClasspath.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
continue;
found.add(new NamedPath(path.toUri().toString(), path));
}
}
}
@@ -47,7 +47,7 @@ public class EarlyLoadingException extends RuntimeException {
return errorMessages;
}
EarlyLoadingException(final String message, final Throwable originalException, List<ExceptionData> errorMessages) {
public EarlyLoadingException(final String message, final Throwable originalException, List<ExceptionData> errorMessages) {
super(message, originalException);
this.errorMessages = errorMessages;
}
@@ -66,6 +66,9 @@ public class FMLLoader
throw new IncompatibleEnvironmentException("Incompatible modlauncher found "+modLauncherPackage.getSpecificationVersion());
}
// Allows us to communicate properties with other services through ModLauncher
setupBlackboardKeys();
accessTransformer = (AccessTransformerService) environment.findLaunchPlugin("accesstransformer").orElseThrow(()-> {
LOGGER.fatal(CORE,"Access Transformer library is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing AccessTransformer, cannot run");
@@ -127,6 +130,12 @@ public class FMLLoader
}
}
private static void setupBlackboardKeys() {
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
}
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments)
{
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
@@ -9,7 +9,6 @@ import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
@@ -17,7 +16,6 @@ import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.loading.toposort.CyclePresentException;
import net.minecraftforge.fml.loading.toposort.TopologicalSort;
import net.minecraftforge.forgespi.locating.IModFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
@@ -36,14 +34,15 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModSorter
{
private static final Logger LOGGER = LogManager.getLogger();
private final UniqueModListBuilder uniqueModListBuilder;
private List<ModFile> modFiles;
private List<ModInfo> sortedList;
private Map<String, ModInfo> modIdNameLookup;
private Map<String, IModInfo> modIdNameLookup;
private List<ModFile> systemMods;
private ModSorter(final List<ModFile> modFiles)
{
this.modFiles = modFiles;
this.uniqueModListBuilder = new UniqueModListBuilder(modFiles);
}
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors)
@@ -78,11 +77,12 @@ public class ModSorter
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
final MutableGraph<ModFileInfo> graph = GraphBuilder.directed().build();
AtomicInteger counter = new AtomicInteger();
Map<IModFileInfo, Integer> infos = modFiles.stream()
Map<ModFileInfo, Integer> infos = modFiles.stream()
.map(ModFile::getModFileInfo)
.filter(ModFileInfo.class::isInstance)
.map(ModFileInfo.class::cast)
.collect(toMap(Function.identity(), e -> counter.incrementAndGet()));
infos.keySet().forEach(i -> graph.addNode((ModFileInfo) i));
infos.keySet().forEach(graph::addNode);
modFiles.stream()
.map(ModFile::getModInfos)
.<IModInfo>mapMulti(Iterable::forEach)
@@ -125,10 +125,9 @@ public class ModSorter
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
{
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
final ModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
// soft dep that doesn't exist. Just return. No edge required.
if (targetModInfo == null) return;
final ModFileInfo target = targetModInfo.getOwningFile();
if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return;
if (self == target)
return; // in case a jar has two mods that have dependencies between
switch (dep.getOrdering()) {
@@ -139,10 +138,22 @@ public class ModSorter
private void buildUniqueList()
{
// Collect mod files by module name. This will be used for deduping purposes
final Map<String, List<IModFile>> modFilesByFirstId = modFiles.stream()
.collect(groupingBy(mf -> mf.getModFileInfo().moduleName()));
final UniqueModListBuilder.UniqueModListData uniqueModListData = uniqueModListBuilder.buildUniqueList();
this.modFiles = uniqueModListData.modFiles();
detectSystemMods(uniqueModListData.modFilesByFirstId());
modIdNameLookup = uniqueModListData.modFilesByFirstId().entrySet().stream()
.filter(e -> !e.getValue().get(0).getModInfos().isEmpty())
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().get(0).getModInfos().get(0)
));
}
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId)
{
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
final Set<String> systemMods = new HashSet<>();
// The minecraft mod is always a system mod
@@ -169,47 +180,6 @@ public class ModSorter
throw new IllegalStateException("Failed to find system mod: " + systemMod);
}
}
// Select the newest by artifact version sorting of non-unique files thus identified
this.modFiles = modFilesByFirstId.entrySet().stream()
.map(this::selectNewestModInfo)
.map(Map.Entry::getValue)
.map(ModFile.class::cast)
.collect(toList());
// Transform to the full mod id list
final Map<String, List<ModInfo>> modIds = modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(Collection::stream)
.map(ModInfo.class::cast)
.collect(groupingBy(IModInfo::getModId));
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
final List<ModInfo> dupedMods = modIds.values().stream()
.filter(modInfos -> modInfos.size() > 1)
.map(modInfos -> modInfos.get(0))
.toList();
if (!dupedMods.isEmpty()) {
final List<EarlyLoadingException.ExceptionData> duplicateModErrors = dupedMods.stream()
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedmod",
dm, Objects.toString(dm))).toList();
throw new EarlyLoadingException("Duplicate mods found", null, duplicateModErrors);
}
modIdNameLookup = modIds.entrySet().stream()
.collect(toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
}
private Map.Entry<String, IModFile> selectNewestModInfo(Map.Entry<String, List<IModFile>> fullList) {
List<IModFile> modInfoList = fullList.getValue();
if (modInfoList.size() > 1) {
LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey());
modInfoList.sort(Comparator.<IModFile, ArtifactVersion>comparing(mf -> mf.getModInfos().get(0).getVersion()).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), modInfoList.get(0).getModInfos().get(0).getVersion());
}
return Map.entry(fullList.getKey(), modInfoList.get(0));
}
private List<EarlyLoadingException.ExceptionData> verifyDependencyVersions()
@@ -239,10 +209,24 @@ public class ModSorter
if (!missingVersions.isEmpty()) {
if (mandatoryMissing > 0) {
LOGGER.error(LOADING, "Missing mandatory dependencies: {}", missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).map(IModInfo.ModVersion::getModId).collect(Collectors.joining(", ")));
LOGGER.error(
LOADING,
"Missing or unsupported mandatory dependencies:\n{}",
missingVersions.stream()
.filter(IModInfo.ModVersion::isMandatory)
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
if (missingVersions.size() - mandatoryMissing > 0) {
LOGGER.error(LOADING, "Unsupported installed optional dependencies: {}", missingVersions.stream().filter(ver -> !ver.isMandatory()).map(IModInfo.ModVersion::getModId).collect(Collectors.joining(", ")));
LOGGER.error(
LOADING,
"Unsupported installed optional dependencies:\n{}",
missingVersions.stream()
.filter(ver -> !ver.isMandatory())
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
return missingVersions.stream()
@@ -254,6 +238,18 @@ public class ModSorter
return Collections.emptyList();
}
private static String formatDependencyError(IModInfo.ModVersion dependency, Map<String, ArtifactVersion> modVersions)
{
ArtifactVersion installed = modVersions.get(dependency.getModId());
return String.format(
"\tMod ID: '%s', Requested by: '%s', Expected range: '%s', Actual version: '%s'",
dependency.getModId(),
dependency.getOwner().getModId(),
dependency.getVersionRange(),
installed != null ? installed.toString() : "[MISSING]"
);
}
private boolean modVersionNotContained(final IModInfo.ModVersion mv, final Map<String, ArtifactVersion> modVersions)
{
return !(VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod", (modId, range) -> modVersions.containsKey(modId) &&
@@ -0,0 +1,132 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.*;
public class UniqueModListBuilder
{
private final static Logger LOGGER = LogManager.getLogger();
private final List<ModFile> modFiles;
public UniqueModListBuilder(final List<ModFile> modFiles) {this.modFiles = modFiles;}
public UniqueModListData buildUniqueList()
{
List<ModFile> uniqueModList;
List<ModFile> uniqueLibListWithVersion;
// Collect mod files by module name. This will be used for deduping purposes
final Map<String, List<ModFile>> modFilesByFirstId = modFiles.stream()
.filter(mf -> mf.getModFileInfo() != null)
.collect(groupingBy(UniqueModListBuilder::getModId));
final Map<String, List<ModFile>> libFilesWithVersionByModuleName = modFiles.stream()
.filter(mf -> mf.getModFileInfo() == null)
.collect(groupingBy(UniqueModListBuilder::getModId));
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueModList = modFilesByFirstId.entrySet().stream()
.map(this::selectNewestModInfo)
.toList();
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
.map(this::selectNewestModInfo)
.toList();
// Transform to the full mod id list
final Map<String, List<IModInfo>> modIds = uniqueModList.stream()
.filter(mf -> mf.getModFileInfo() != null) //Filter out non-mod files, we don't care about those for now.....
.map(ModFile::getModInfos)
.flatMap(Collection::stream)
.collect(groupingBy(IModInfo::getModId));
// Transform to the full lib id list
final Map<String, List<ModFile>> versionedLibIds = uniqueLibListWithVersion.stream()
.map(UniqueModListBuilder::getModId)
.collect(Collectors.toMap(
Function.identity(),
libFilesWithVersionByModuleName::get
));
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
final List<IModInfo> dupedMods = modIds.values().stream()
.filter(modInfos -> modInfos.size() > 1)
.map(modInfos -> modInfos.get(0))
.toList();
if (!dupedMods.isEmpty()) {
final List<EarlyLoadingException.ExceptionData> duplicateModErrors = dupedMods.stream()
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedmod", dm, Objects.toString(dm)))
.toList();
throw new EarlyLoadingException("Duplicate mods found", null, duplicateModErrors);
}
final List<ModFile> dupedLibs = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(modFiles -> modFiles.get(0))
.toList();
if (!dupedLibs.isEmpty()) {
final List<EarlyLoadingException.ExceptionData> duplicateLibErrors = dupedLibs.stream()
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedlib.versioned", dm, Objects.toString(dm)))
.toList();
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, duplicateLibErrors);
}
// Collect unique mod files by module name. This will be used for deduping purposes
final Map<String, List<ModFile>> uniqueModFilesByFirstId = uniqueModList.stream()
.collect(groupingBy(UniqueModListBuilder::getModId));
final List<ModFile> loadedList = new ArrayList<>();
loadedList.addAll(uniqueModList);
loadedList.addAll(uniqueLibListWithVersion);
return new UniqueModListData(loadedList, uniqueModFilesByFirstId);
}
private ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
List<ModFile> modInfoList = fullList.getValue();
if (modInfoList.size() > 1) {
LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey());
modInfoList.sort(Comparator.comparing(this::getVersion).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), modInfoList.get(0).getModInfos().get(0).getVersion());
}
return modInfoList.get(0);
}
private ArtifactVersion getVersion(final ModFile mf)
{
if (mf.getModFileInfo() == null) {
return mf.getJarVersion();
}
return mf.getModInfos().get(0).getVersion();
}
private static String getModId(ModFile modFile) {
if (modFile.getModFileInfo() == null || modFile.getModFileInfo().getMods().isEmpty()) {
return modFile.getSecureJar().name();
}
return modFile.getModFileInfo().moduleName();
}
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
}
@@ -0,0 +1,50 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.locating.IModFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileLocator
{
private static final Logger LOGGER = LogManager.getLogger();
protected Optional<InputStream> loadResourceFromModFile(final IModFile modFile, final Path path) {
try {
return Optional.of(Files.newInputStream(modFile.findResource(path.toString())));
}
catch (final FileNotFoundException e) {
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
return Optional.empty();
}
catch (final Exception e) {
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
return Optional.empty();
}
}
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path) {
try {
final Path pathInModFile = file.findResource(path.toString());
return createMod(pathInModFile);
}
catch (Exception e) {
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
return Optional.empty();
}
}
protected String identifyMod(final IModFile modFile) {
return modFile.getFileName();
}
}
@@ -6,30 +6,20 @@
package net.minecraftforge.fml.loading.moddiscovery;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.jarhandling.JarMetadata;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class AbstractJarFileLocator extends AbstractModLocator {
public abstract class AbstractJarFileLocator extends AbstractModLocator
{
private static final Logger LOGGER = LogManager.getLogger();
@Override
@@ -43,15 +33,4 @@ public abstract class AbstractJarFileLocator extends AbstractModLocator {
}
LOGGER.debug(LogMarkers.SCAN,"Scan finished: {}", file);
}
@Override
public List<IModFile> scanMods() {
return scanCandidates()
.map(this::createMod)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
public abstract Stream<Path> scanCandidates();
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class AbstractJarFileModLocator extends AbstractJarFileLocator implements IModLocator
{
@Override
public List<IModFile> scanMods()
{
return scanCandidates()
.map(this::createMod)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
public abstract Stream<Path> scanCandidates();
}
@@ -24,12 +24,18 @@ import java.util.Optional;
import java.util.function.Function;
import java.util.jar.Manifest;
public abstract class AbstractModLocator implements IModLocator {
private static final Logger LOGGER = LogManager.getLogger();
public abstract class AbstractModLocator implements IModLocator
{
private static final Logger LOGGER = LogManager.getLogger();
protected static final String MODS_TOML = "META-INF/mods.toml";
protected static final String MANIFEST = "META-INF/MANIFEST.MF";
protected Optional<IModFile> createMod(Path... path) {
return this.createMod(getDefaultJarModType(), path);
}
// FORGE: Backporting artifact, the defaultType must be the first parameter since path is varargs
protected Optional<IModFile> createMod(String defaultType, Path... path) {
var mjm = new ModJarMetadata();
var sj = SecureJar.from(
Manifest::new,
@@ -40,12 +46,15 @@ public abstract class AbstractModLocator implements IModLocator {
IModFile mod;
var type = sj.getManifest().getMainAttributes().getValue(ModFile.TYPE);
if (type == null) {
type = defaultType;
}
if (sj.findFile(MODS_TOML).isPresent()) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
} else if (type != null) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path);
mod = new ModFile(sj, this, this::manifestParser);
mod = new ModFile(sj, this, this::manifestParser, type);
} else {
return Optional.empty();
}
@@ -68,29 +77,49 @@ public abstract class AbstractModLocator implements IModLocator {
}
};
return new IModFileInfo() {
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
@Override public List<LanguageSpec> requiredLanguageLoaders() { return Collections.emptyList(); }
@Override public boolean showAsResourcePack() { return false; }
@Override public Map<String, Object> getFileProperties() { return Collections.emptyMap(); }
@Override public String getLicense() { return license; }
@Override public IModFile getFile() { return mod; }
@Override public IConfigurable getConfig() { return dummy; }
// These Should never be called as it's only called from ModJarMetadata.version and we bypass that
@Override public String moduleName() { return mod.getSecureJar().name(); }
@Override public String versionString() { return null; }
@Override public List<String> usesServices() { return null; }
@Override
public String toString() {
return "IModFileInfo(" + mod.getFilePath() + ")";
}
};
return new DefaultModFileInfo(mod, license, dummy);
}
@Override
public boolean isValid(final IModFile modFile) {
return true;
}
protected String getDefaultJarModType() {
return null;
}
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
@Override
public <T> Optional<T> getConfigElement(final String... strings)
{
return Optional.empty();
}
@Override
public List<? extends IConfigurable> getConfigList(final String... strings)
{
return null;
}
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
@Override public List<LanguageSpec> requiredLanguageLoaders() { return Collections.emptyList(); }
@Override public boolean showAsResourcePack() { return false; }
@Override public Map<String, Object> getFileProperties() { return Collections.emptyMap(); }
@Override
public String getLicense() { return license; }
@Override public IModFile getFile() { return mod; }
@Override public IConfigurable getConfig() { return configurable; }
// These Should never be called as it's only called from ModJarMetadata.version and we bypass that
@Override public String moduleName() { return mod.getSecureJar().name(); }
@Override public String versionString() { return null; }
@Override public List<String> usesServices() { return null; }
@Override
public String toString() {
return "IModFileInfo(" + mod.getFilePath() + ")";
}
}
}
@@ -5,18 +5,16 @@
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.fml.loading.ClasspathLocatorUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
@@ -24,7 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class ClasspathLocator extends AbstractJarFileLocator {
public class ClasspathLocator extends AbstractJarFileModLocator {
private static final Logger LOGGER = LogManager.getLogger();
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
private boolean enabled = false;
@@ -58,7 +56,7 @@ public class ClasspathLocator extends AbstractJarFileLocator {
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Path path = findJarPathFor(resource, resource, url);
Path path = ClasspathLocatorUtils.findJarPathFor(resource, resource, url);
if (claimed.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
continue;
ret.add(path);
@@ -71,22 +69,4 @@ public class ClasspathLocator extends AbstractJarFileLocator {
var launchTarget = (String) arguments.get("launchTarget");
enabled = launchTarget != null && launchTarget.contains("dev");
}
private Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
try {
Path path;
final URI uri = resource.toURI();
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
} else {
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
}
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
return path;
} catch (NullPointerException | URISyntaxException e) {
LOGGER.fatal(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
}
}
}
@@ -0,0 +1,163 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.jarjar.selection.JarSelector;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.jetbrains.annotations.NotNull;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
{
private static final Logger LOGGER = LogManager.getLogger();
@Override
public String name()
{
return "JarInJar";
}
@Override
public List<IModFile> scanMods(final Iterable<IModFile> loadedMods)
{
final List<IModFile> sources = Lists.newArrayList();
loadedMods.forEach(sources::add);
final List<IModFile> dependenciesToLoad = JarSelector.detectAndSelect(sources, this::loadResourceFromModFile, this::loadModFileFrom, this::identifyMod, this::exception);
if (dependenciesToLoad.isEmpty())
{
LOGGER.info("No dependencies to load found. Skipping!");
return Collections.emptyList();
}
LOGGER.info("Found {} dependencies adding them to mods collection", dependenciesToLoad.size());
return dependenciesToLoad;
}
@Override
public void initArguments(final Map<String, ?> arguments)
{
// NO-OP, for now
}
@Override
protected String getDefaultJarModType()
{
return IModFile.Type.GAMELIBRARY.name();
}
@SuppressWarnings("resource")
@Override
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path)
{
try
{
final Path pathInModFile = file.findResource(path.toString());
final URI filePathUri = new URI("jij:" + (pathInModFile.toAbsolutePath().toUri().getRawSchemeSpecificPart())).normalize();
final Map<String, ?> outerFsArgs = ImmutableMap.of("packagePath", pathInModFile);
final FileSystem zipFS = FileSystems.newFileSystem(filePathUri, outerFsArgs);
final Path pathInFS = zipFS.getPath("/");
final IModFile.Type parentType = file.getType();
final String modType;
if (parentType == IModFile.Type.LIBRARY || parentType == IModFile.Type.LANGPROVIDER) {
modType = IModFile.Type.LIBRARY.name();
} else {
modType = IModFile.Type.GAMELIBRARY.name();
}
return createMod(modType, pathInFS);
}
catch (Exception e)
{
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
throw new RuntimeException("Failed to load mod file " + file.getFileName(), e);
}
}
protected EarlyLoadingException exception(Collection<JarSelector.ResolutionFailureInformation<IModFile>> failedDependencies)
{
final List<EarlyLoadingException.ExceptionData> errors = failedDependencies.stream()
.filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure
.map(this::buildExceptionData)
.toList();
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
}
@NotNull
private EarlyLoadingException.ExceptionData buildExceptionData(final JarSelector.ResolutionFailureInformation<IModFile> entry)
{
return new EarlyLoadingException.ExceptionData(
getErrorTranslationKey(entry),
entry.identifier().group() + ":" + entry.identifier().artifact(),
entry.sources()
.stream()
.flatMap(this::getModWithVersionRangeStream)
.map(this::formatError)
.collect(Collectors.joining(", "))
);
}
@NotNull
private String getErrorTranslationKey(final JarSelector.ResolutionFailureInformation<IModFile> entry)
{
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
"fml.dependencyloading.conflictingdependencies" :
"fml.dependencyloading.mismatchedcontaineddependencies";
}
@NotNull
private Stream<ModWithVersionRange> getModWithVersionRangeStream(final JarSelector.SourceWithRequestedVersionRange<IModFile> file)
{
return file.sources()
.stream()
.map(IModFile::getModFileInfo)
.flatMap(modFileInfo -> modFileInfo.getMods().stream())
.map(modInfo -> new ModWithVersionRange(modInfo, file.requestedVersionRange(), file.includedVersion()));
}
@NotNull
private String formatError(final ModWithVersionRange modWithVersionRange)
{
return "\u00a7e" + modWithVersionRange.modInfo().getModId() + "\u00a7r - \u00a74" + modWithVersionRange.versionRange().toString() + "\u00a74 - \u00a72" + modWithVersionRange.artifactVersion().toString() + "\u00a72";
}
@Override
protected String identifyMod(final IModFile modFile)
{
if (modFile.getModFileInfo() == null || modFile.getModInfos().isEmpty())
{
return modFile.getFileName();
}
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
}
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion)
{}
}
@@ -15,7 +15,7 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MavenDirectoryLocator extends AbstractJarFileLocator {
public class MavenDirectoryLocator extends AbstractJarFileModLocator {
private List<Path> modCoords;
@Override
@@ -23,7 +23,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MinecraftLocator extends AbstractModLocator {
public class MinecraftLocator extends AbstractModLocator implements IModLocator {
private static final Logger LOGGER = LogManager.getLogger();
@Override
@@ -5,10 +5,14 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.UniqueModListBuilder;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.locating.IModFile;
@@ -24,43 +28,119 @@ import java.util.stream.Collectors;
public class ModDiscoverer {
private static final Logger LOGGER = LogManager.getLogger();
private final ServiceLoader<IModLocator> locators;
private final List<IModLocator> locatorList;
private final ServiceLoader<IModLocator> modLocators;
private final List<IModLocator> modLocatorList;
public ModDiscoverer(Map<String, ?> arguments) {
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupMessageManager.locatorConsumer().orElseGet(()-> s->{}));
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
locators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
locatorList = ServiceLoaderUtils.streamServiceLoader(()->locators, sce->LOGGER.error("Failed to load locator list", sce)).collect(Collectors.toList());
locatorList.forEach(l->l.initArguments(arguments));
LOGGER.debug(LogMarkers.CORE,"Found Mod Locators : {}", ()->locatorList.stream().map(iModLocator -> "("+iModLocator.name() + ":" + iModLocator.getClass().getPackage().getImplementationVersion()+")").collect(Collectors.joining(",")));
}
ModDiscoverer(List<IModLocator> locatorList) {
this.locatorList = locatorList;
this.locators = null;
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
modLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> modLocators, sce->LOGGER.error("Failed to load mod locator list", sce)).collect(Collectors.toList());
modLocatorList.forEach(l->l.initArguments(arguments));
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
{
LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", modLocatorList.stream()
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
}
}
public ModValidator discoverMods() {
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", locatorList.size());
var loadedFiles = new ArrayList<>();
for (IModLocator locator : locatorList) {
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
var modFiles = locator.scanMods();
for (IModFile mf : modFiles) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with locator {}", mf.getFileName(), mf.getType(), mf.getLocator());
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()));
}
loadedFiles.addAll(modFiles);
}
final var modFilesMap = loadedFiles.stream()
.map(ModFile.class::cast)
.collect(Collectors.groupingBy(IModFile::getType));
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size());
List<ModFile> loadedFiles = new ArrayList<>();
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
boolean successfullyLoadedMods = true;
var validator = new ModValidator(modFilesMap);
//Loop all mod locators to get the prime mods to load from.
for (IModLocator locator : modLocatorList) {
try {
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
var locatedFiles = locator.scanMods();
if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) {
LOGGER.error(LogMarkers.SCAN, "A mod locator returned a file which is not a ModFile instance!. They will be skipped!");
}
handleLocatedFiles(loadedFiles, locatedFiles);
} catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to load mods with locator {}", locator, exception);
discoveryErrorData.addAll(exception.getAllData());
}
}
//First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped.
Map<IModFile.Type, List<ModFile>> modFilesMap = Maps.newHashMap();
try {
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
//Grab the temporary results.
//This allows loading to continue to a base state, in case dependency loading fails.
modFilesMap = uniqueModsData.modFiles().stream()
.collect(Collectors.groupingBy(IModFile::getType));
loadedFiles = uniqueModsData.modFiles();
}
catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after mod discovery.", exception);
discoveryErrorData.addAll(exception.getAllData());
successfullyLoadedMods = false;
}
//We can continue loading if prime mods loaded successfully.
if (successfullyLoadedMods) {
LOGGER.debug(LogMarkers.SCAN, "Successfully Loaded {} mods. Attempting to load dependencies...", loadedFiles.size());
for (IModLocator locator : modLocatorList) {
try {
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
final List<IModFile> locatedMods = ImmutableList.copyOf(loadedFiles);
var locatedFiles = locator.scanMods(locatedMods);
if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) {
LOGGER.error(LogMarkers.SCAN, "A dependency locator returned a file which is not a ModFile instance!. They will be skipped!");
}
handleLocatedFiles(loadedFiles, locatedFiles);
}
catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to load dependencies with locator {}", locator, exception);
discoveryErrorData.addAll(exception.getAllData());
}
}
//Second processing run of the mod list. Any duplicates will cause resolution failure and only the mods list will be loaded.
try {
final UniqueModListBuilder modsAndDependenciesUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = modsAndDependenciesUniqueListBuilder.buildUniqueList();
//We now only need the mod files map, not the list.
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
.collect(Collectors.groupingBy(IModFile::getType));
} catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after dependency discovery.", exception);
discoveryErrorData.addAll(exception.getAllData());
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType));
}
}
else {
//Failure notify the listeners.
LOGGER.error(LogMarkers.SCAN, "Mod Discovery failed. Skipping dependency discovery.");
}
//Validate the loading. With a deduplicated list, we can now successfully process the artifacts and load
//transformer plugins.
var validator = new ModValidator(modFilesMap, discoveryErrorData);
validator.stage1Validation();
return validator;
}
private void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles)
{
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
for (IModFile mf : locatedModFiles) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getLocator());
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()));
}
loadedFiles.addAll(locatedModFiles);
}
}
@@ -19,6 +19,8 @@ import net.minecraftforge.forgespi.locating.IModLocator;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -51,9 +53,9 @@ public class ModFile implements IModFile {
private Throwable scanError;
private final SecureJar jar;
private final Type modFileType;
private final Manifest manifest;
private final Manifest manifest;
private final IModLocator locator;
private IModFileInfo modFileInfo;
private IModFileInfo modFileInfo;
private ModFileScanData fileModFileScanData;
private CompletableFuture<ModFileScanData> futureScanResult;
private List<CoreModFile> coreMods;
@@ -63,14 +65,18 @@ public class ModFile implements IModFile {
private SecureJar.Status securityStatus;
public ModFile(final SecureJar jar, final IModLocator locator, final ModFileFactory.ModFileInfoParser parser) {
this(jar, locator, parser, parseType(jar));
}
public ModFile(final SecureJar jar, final IModLocator locator, final ModFileFactory.ModFileInfoParser parser, String type) {
this.locator = locator;
this.jar = jar;
this.parser = parser;
manifest = this.jar.getManifest();
final Optional<String> value = Optional.ofNullable(manifest.getMainAttributes().getValue(TYPE));
modFileType = Type.valueOf(value.orElse("MOD"));
modFileType = Type.valueOf(type);
jarVersion = Optional.ofNullable(manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION)).orElse("0.0NONE");
this.modFileInfo = ModFileParser.readModList(this, this.parser);
}
@Override
@@ -201,4 +207,15 @@ public class ModFile implements IModFile {
public void setSecurityStatus(final SecureJar.Status status) {
this.securityStatus = status;
}
public ArtifactVersion getJarVersion()
{
return new DefaultArtifactVersion(this.jarVersion);
}
private static String parseType(final SecureJar jar) {
final Manifest m = jar.getManifest();
final Optional<String> value = Optional.ofNullable(m.getMainAttributes().getValue(TYPE));
return value.orElse("MOD");
}
}
@@ -64,7 +64,9 @@ public class ModInfo implements IModInfo, IConfigurable
.map(s -> StringSubstitutor.replace(s, ownFile.map(ModFileInfo::getFile).orElse(null)))
.map(DefaultArtifactVersion::new).orElse(DEFAULT_VERSION);
this.displayName = config.<String>getConfigElement("displayName").orElse(this.modId);
this.description = config.<String>getConfigElement("description").orElse("MISSING DESCRIPTION");
this.description = config.<String>getConfigElement("description")
.orElse("MISSING DESCRIPTION")
.replace("\r\n", "\n").stripIndent();
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile")).orElse(null)));
@@ -26,13 +26,15 @@ public class ModValidator {
private final List<ModFile> candidateMods;
private LoadingModList loadingModList;
private List<ModFile> brokenFiles;
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles) {
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles, final List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
this.modFiles = modFiles;
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
this.candidateMods.addAll(lst(modFiles.get(IModFile.Type.GAMELIBRARY)));
this.candidatePlugins = lst(modFiles.get(IModFile.Type.LANGPROVIDER));
this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY)));
this.discoveryErrorData = discoveryErrorData;
}
private static List<ModFile> lst(List<ModFile> files) {
@@ -50,22 +52,22 @@ public class ModValidator {
final List<ModFile> brokenFiles = new ArrayList<>();
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext(); )
{
ModFile mod = iterator.next();
if (!mod.getLocator().isValid(mod) || !mod.identifyMods()) {
LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", mod.getFilePath());
ModFile modFile = iterator.next();
if (!modFile.getLocator().isValid(modFile) || !modFile.identifyMods()) {
LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", modFile.getFilePath());
iterator.remove();
brokenFiles.add(mod);
brokenFiles.add(modFile);
}
}
return brokenFiles;
}
public ITransformationService.Resource getPluginResources() {
return new ITransformationService.Resource(IModuleLayerManager.Layer.PLUGIN, this.candidatePlugins.stream().map(ModFile::getSecureJar).toList());
return new ITransformationService.Resource(IModuleLayerManager.Layer.PLUGIN, this.candidatePlugins.stream().map(IModFile::getSecureJar).toList());
}
public ITransformationService.Resource getModResources() {
return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(ModFile::getSecureJar).toList());
return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(IModFile::getSecureJar).toList());
}
private List<EarlyLoadingException.ExceptionData> validateLanguages() {
@@ -84,7 +86,11 @@ public class ModValidator {
public BackgroundScanHandler stage2Validation() {
var errors = validateLanguages();
loadingModList = ModSorter.sort(candidateMods, errors);
var allErrors = new ArrayList<>(errors);
allErrors.addAll(this.discoveryErrorData);
loadingModList = ModSorter.sort(candidateMods, allErrors);
loadingModList.addCoreMods();
loadingModList.addAccessTransformers();
loadingModList.setBrokenFiles(brokenFiles);
@@ -23,7 +23,7 @@ import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.uncheck;
/**
* Support loading mods located in JAR files in the mods folder
*/
public class ModsFolderLocator extends AbstractJarFileLocator {
public class ModsFolderLocator extends AbstractJarFileModLocator {
private static final String SUFFIX = ".jar";
private static final Logger LOGGER = LogManager.getLogger();
private final Path modFolder;
@@ -1 +1,2 @@
net.minecraftforge.fml.loading.ModDirTransformerDiscoverer
net.minecraftforge.fml.loading.ModDirTransformerDiscoverer
net.minecraftforge.fml.loading.ClasspathTransformerDiscoverer
@@ -3,3 +3,4 @@ net.minecraftforge.fml.loading.moddiscovery.MavenDirectoryLocator
net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator
net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator
net.minecraftforge.fml.loading.moddiscovery.ClasspathLocator
net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+11 -5
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
@@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+8 -6
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
+6 -6
View File
@@ -16,14 +16,14 @@ Step 1: Open your command-line and browse to the folder where you extracted the
Step 2: You're left with a choice.
If you prefer to use Eclipse:
1. Run the following command: `gradlew genEclipseRuns` (`./gradlew genEclipseRuns` if you are on Mac/Linux)
1. Run the following command: `./gradlew genEclipseRuns`
2. Open Eclipse, Import > Existing Gradle Project > Select Folder
or run `gradlew eclipse` to generate the project.
If you prefer to use IntelliJ:
1. Open IDEA, and import project.
2. Select your build.gradle file and have it import.
3. Run the following command: `gradlew genIntellijRuns` (`./gradlew genIntellijRuns` if you are on Mac/Linux)
3. Run the following command: `./gradlew genIntellijRuns`
4. Refresh the Gradle Project in IDEA if required.
If at any point you are missing libraries in your IDE, or you've run into problems you can
@@ -40,7 +40,7 @@ https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
Additional Resources:
=========================
Community Documentation: http://mcforge.readthedocs.io/en/latest/gettingstarted/
LexManos' Install Video: https://www.youtube.com/watch?v=8VEdtQLuLO0
Forge Forum: https://forums.minecraftforge.net/
Forge Discord: https://discord.gg/UvedJ9m
Community Documentation: https://docs.minecraftforge.net/en/1.18.x/gettingstarted/
LexManos' Install Video: https://youtu.be/8VEdtQLuLO0
Forge Forums: https://forums.minecraftforge.net/
Forge Discord: https://discord.minecraftforge.net/
+93 -91
View File
@@ -1,24 +1,16 @@
buildscript {
repositories {
// These repositories are only for Gradle plugins, put any other repositories in the repository block further below
maven { url = 'https://maven.minecraftforge.net' }
mavenCentral()
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
}
}
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
plugins {
id 'eclipse'
id 'idea'
id 'maven-publish'
id 'net.minecraftforge.gradle' version '[6.0,6.2)'
}
apply plugin: 'net.minecraftforge.gradle'
version = mod_version
group = mod_group_id
version = '1.0'
group = 'com.yourname.modid' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = 'modid'
base {
archivesName = mod_id
}
// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17.
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
@@ -38,14 +30,36 @@ minecraft {
//
// 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@'
mappings channel: mapping_channel, version: mapping_version
// accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') // Currently, this location cannot be changed from the default.
// 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
// 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 {
client {
// applies to all the run configs below
configureEach {
workingDirectory project.file('run')
// Recommended logging data for a userdev environment
@@ -61,75 +75,38 @@ minecraft {
property 'forge.logging.console.level', 'debug'
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
property 'forge.enabledGameTestNamespaces', 'examplemod'
property 'forge.enabledGameTestNamespaces', mod_id
mods {
examplemod {
"${mod_id}" {
source sourceSets.main
}
}
}
server {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
client {
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
property 'forge.enabledGameTestNamespaces', 'examplemod'
property 'forge.enabledGameTestNamespaces', mod_id
}
mods {
examplemod {
source sourceSets.main
}
}
server {
property '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 {
workingDirectory project.file('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'
// 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'
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
property 'forge.enabledGameTestNamespaces', 'examplemod'
mods {
examplemod {
source sourceSets.main
}
}
property 'forge.enabledGameTestNamespaces', mod_id
}
data {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.console.level', 'debug'
// example of overriding the workingDirectory set in configureEach above
workingDirectory project.file('run-data')
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
args '--mod', 'examplemod', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
mods {
examplemod {
source sourceSets.main
}
}
args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
}
}
}
@@ -148,48 +125,73 @@ repositories {
}
dependencies {
// Specify the version of Minecraft to use. If this is any group other than 'net.minecraft', it is assumed
// that the dep is a ForgeGradle 'patcher' dependency, and its patches will be applied.
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft '@FORGE_GROUP@:@FORGE_NAME@:@FORGE_VERSION@'
// 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}"
// Real mod deobf dependency examples - these get remapped to your current mappings
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") // Adds JEI API as a compile dependency
// runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") // Adds the full JEI mod as a runtime dependency
// implementation fg.deobf("com.tterrag.registrate:Registrate:MC${mc_version}-${registrate_version}") // Adds registrate as a dependency
// Examples using mod jars from ./libs
// 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...
// For more info:
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
}
// Example for how to get properties into the manifest for reading at runtime.
jar {
manifest {
attributes([
"Specification-Title" : "examplemod",
"Specification-Vendor" : "examplemodsareus",
"Specification-Version" : "1", // We are version 1 of ourselves
"Implementation-Title" : project.name,
"Implementation-Version" : project.jar.archiveVersion,
"Implementation-Vendor" : "examplemodsareus",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
])
// 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 {
var replaceProperties = [
minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range,
forge_version: forge_version, forge_version_range: forge_version_range,
loader_version_range: loader_version_range,
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
filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) {
expand replaceProperties + [project: project]
}
}
// Example configuration to allow publishing using the maven-publish plugin
// This is the preferred method to reobfuscate your jar file
jar.finalizedBy('reobfJar')
// 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
// publish.dependsOn('reobfJar')
// Example for how to get properties into the manifest for reading at runtime.
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Specification-Title' : mod_id,
'Specification-Vendor' : mod_authors,
'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")
])
}
// This is the preferred method to reobfuscate your jar file
finalizedBy 'reobfJar'
}
// 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'
// }
publishing {
publications {
mavenJava(MavenPublication) {
register('mavenJava', MavenPublication) {
artifact jar
}
}
+56 -1
View File
@@ -1,4 +1,59 @@
# 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.daemon=false
## Environment Properties
# The Minecraft version must agree with the Forge version to get a valid artifact
minecraft_version=@MC_VERSION@
# 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@)
# 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@,)
# The loader version range can only use the major version of Forge/FML as bounds
loader_version_range=[@FORGE_SPEC_VERSION@,)
# The mapping channel to use for mappings.
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
#
# | 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 Minecraft Forge.
# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started
mapping_channel=@MAPPING_CHANNEL@
# The mapping version to query from the mapping channel.
# This must match the format required by the mapping channel.
mapping_version=@MAPPING_VERSION@
## Mod Properties
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
# Must match the String constant located in the main mod class annotated with @Mod.
mod_id=examplemod
# The human-readable display name for the mod.
mod_name=Example Mod
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved
# The mod version. See https://semver.org/
mod_version=1.0.0
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
mod_group_id=com.example.examplemod
# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.
mod_authors=YourNameHere, OtherNameHere
# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.
mod_description=Example mod description.\nNewline characters can be used and will be replaced properly.
+13
View File
@@ -0,0 +1,13 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven {
name = 'MinecraftForge'
url = 'https://maven.minecraftforge.net/'
}
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
+39 -37
View File
@@ -6,57 +6,59 @@
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="javafml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion="[@FORGE_SPEC_VERSION@,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
license="All rights reserved"
license="${mod_license}"
# A URL to refer people to when problems occur with this mod
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
# The modid of the mod
modId="examplemod" #mandatory
# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
# see the associated build.gradle script for how to populate this completely automatically during a build
version="${file.jarVersion}" #mandatory
# A display name for the mod
displayName="Example Mod" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
modId="${mod_id}" #mandatory
# The version number of the mod
version="${mod_version}" #mandatory
# A display name for the mod
displayName="${mod_name}" #mandatory
# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
# A file name (in the root of the mod JAR) containing a logo for display
logoFile="examplemod.png" #optional
#logoFile="examplemod.png" #optional
# A text field displayed in the mod UI
credits="Thanks for this example mod goes to Java" #optional
#credits="" #optional
# A text field displayed in the mod UI
authors="Love, Cheese and small house plants" #optional
authors="${mod_authors}" #optional
# Display Test controls the display for your mod in the server connection screen
# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod.
# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod.
# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component.
# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value.
# 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" # MATCH_VERSION is the default if nothing is specified (#optional)
# The description text for the mod (multi line!) (#mandatory)
description='''
This is a long form description of the mod. You can write whatever you want here
Have some lorem ipsum.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis lacinia magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed sagittis luctus odio eu tempus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque volutpat ligula eget lacus auctor sagittis. In hac habitasse platea dictumst. Nunc gravida elit vitae sem vehicula efficitur. Donec mattis ipsum et arcu lobortis, eleifend sagittis sem rutrum. Cras pharetra quam eget posuere fermentum. Sed id tincidunt justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
'''
description='''${mod_description}'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
[[dependencies.examplemod]] #optional
# the modid of the dependency
modId="forge" #mandatory
# Does this dependency have to exist - if not, ordering below must be specified
mandatory=true #mandatory
# The version range of the dependency
versionRange="[@FORGE_SPEC_VERSION@,)" #mandatory
# An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory
ordering="NONE"
# Side this dependency is applied on - BOTH, CLIENT or SERVER
side="BOTH"
[[dependencies.${mod_id}]] #optional
# the modid of the dependency
modId="forge" #mandatory
# Does this dependency have to exist - if not, ordering below must be specified
mandatory=true #mandatory
# The version range of the dependency
versionRange="${forge_version_range}" #mandatory
# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory
# BEFORE - This mod is loaded BEFORE the dependency
# AFTER - This mod is loaded AFTER the dependency
ordering="NONE"
# Side this dependency is applied on - BOTH, CLIENT, or SERVER
side="BOTH"
# Here's another dependency
[[dependencies.examplemod]]
modId="minecraft"
mandatory=true
[[dependencies.${mod_id}]]
modId="minecraft"
mandatory=true
# This version range declares a minimum of the current minecraft version up to but not including the next major version
versionRange="[@MC_VERSION@,@MC_NEXT_VERSION@)"
ordering="NONE"
side="BOTH"
versionRange="${minecraft_version_range}"
ordering="NONE"
side="BOTH"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"pack": {
"description": "examplemod resources",
"description": "${mod_id} resources",
"pack_format": 9,
"forge:resource_pack_format": 8,
"forge:data_pack_format": 9
@@ -8,7 +8,7 @@
this.f_85648_.clear();
return Pair.of(bufferbuilder$drawstate, bytebuffer);
}
@@ -444,4 +_,15 @@
@@ -444,4 +_,16 @@
this.f_166822_ = p_166829_;
}
}
@@ -22,5 +22,6 @@
+ this.f_85652_ += buffer.limit();
+ }
+
+ @Override
+ public VertexFormat getVertexFormat() { return this.f_85658_; }
}
@@ -0,0 +1,9 @@
--- a/com/mojang/blaze3d/vertex/SheetedDecalTextureGenerator.java
+++ b/com/mojang/blaze3d/vertex/SheetedDecalTextureGenerator.java
@@ -91,4 +_,6 @@
this.f_85878_ = p_85902_;
return this;
}
+
+ public VertexFormat getVertexFormat() { return f_85867_.getVertexFormat(); }
}
@@ -0,0 +1,25 @@
--- a/com/mojang/blaze3d/vertex/VertexMultiConsumer.java
+++ b/com/mojang/blaze3d/vertex/VertexMultiConsumer.java
@@ -91,6 +_,8 @@
this.f_86171_.m_141991_();
this.f_86172_.m_141991_();
}
+
+ public VertexFormat getVertexFormat() { return f_86171_.getVertexFormat(); }
}
@OnlyIn(Dist.CLIENT)
@@ -176,6 +_,13 @@
public void m_141991_() {
this.m_167144_(VertexConsumer::m_141991_);
+ }
+
+ public VertexFormat getVertexFormat()
+ {
+ if (f_167071_.length > 0)
+ return f_167071_[0].getVertexFormat();
+ return DefaultVertexFormat.f_85811_;
}
}
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/CrashReport.java
+++ b/net/minecraft/CrashReport.java
@@ -55,14 +_,8 @@
@@ -55,14 +_,9 @@
if (this.f_127506_ != null && this.f_127506_.length > 0) {
p_127520_.append("-- Head --\n");
p_127520_.append("Thread: ").append(Thread.currentThread().getName()).append("\n");
@@ -12,6 +12,7 @@
- }
-
- p_127520_.append("\n");
+ p_127520_.append(net.minecraftforge.logging.CrashReportAnalyser.appendSuspectedMods(this.f_127501_, this.f_127506_));
+ p_127520_.append("Stacktrace:");
+ p_127520_.append(net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(this.f_127506_));
}
@@ -19,3 +19,13 @@
if (SharedConstants.f_136183_) {
throw illegalargumentexception;
}
@@ -649,7 +_,8 @@
public static <T, R> Function<T, R> m_143827_(final Function<T, R> p_143828_) {
return new Function<T, R>() {
- private final Map<T, R> f_143852_ = Maps.newHashMap();
+ // FORGE: Allow using memoized functions from multiple threads.
+ private final Map<T, R> f_143852_ = Maps.newConcurrentMap();
public R apply(T p_211578_) {
return this.f_143852_.computeIfAbsent(p_211578_, p_143828_);
@@ -1,5 +1,14 @@
--- a/net/minecraft/client/ClientRecipeBook.java
+++ b/net/minecraft/client/ClientRecipeBook.java
@@ -52,7 +_,7 @@
for(Recipe<?> recipe : p_90643_) {
if (!recipe.m_5598_() && !recipe.m_142505_()) {
RecipeBookCategories recipebookcategories = m_90646_(recipe);
- String s = recipe.m_6076_();
+ String s = recipe.m_6076_().isEmpty() ? recipe.m_6423_().toString() : recipe.m_6076_(); // FORGE: Group value defaults to the recipe's ID if the recipe's explicit group is empty.
if (s.isEmpty()) {
map.computeIfAbsent(recipebookcategories, (p_90645_) -> {
return Lists.newArrayList();
@@ -104,6 +_,8 @@
} else if (recipetype == RecipeType.f_44113_) {
return RecipeBookCategories.SMITHING;
@@ -41,15 +41,21 @@
this.f_91068_ = new KeyboardHandler(this);
this.f_91068_.m_90887_(this.f_90990_.m_85439_());
RenderSystem.m_69580_(this.f_91066_.f_92035_, false);
@@ -456,6 +_,7 @@
@@ -456,6 +_,8 @@
this.f_91042_.m_83931_(0.0F, 0.0F, 0.0F, 0.0F);
this.f_91042_.m_83954_(f_91002_);
this.f_91036_ = new ReloadableResourceManager(PackType.CLIENT_RESOURCES);
+ net.minecraftforge.client.RecipeBookRegistry.initDefaultValues();
+ net.minecraftforge.client.loading.ClientModLoader.begin(this, this.f_91038_, this.f_91036_, this.f_91037_);
this.f_91038_.m_10506_();
this.f_91066_.m_92145_(this.f_91038_);
this.f_91039_ = new LanguageManager(this.f_91066_.f_92075_);
@@ -504,6 +_,7 @@
@@ -500,10 +_,12 @@
this.f_91052_ = new BlockRenderDispatcher(this.f_91051_.m_119430_(), blockentitywithoutlevelrenderer, this.f_91040_);
this.f_91036_.m_7217_(this.f_91052_);
this.f_91060_ = new LevelRenderer(this, this.f_90993_);
+ net.minecraftforge.fml.ModLoader.get().postEvent(new net.minecraftforge.client.event.RenderLevelStageEvent.RegisterStageEvent());
this.f_91036_.m_7217_(this.f_91060_);
this.m_91271_();
this.f_91036_.m_7217_(this.f_90997_);
this.f_91061_ = new ParticleEngine(this.f_91073_, this.f_90987_);
@@ -264,7 +270,7 @@
return;
}
+ if(!this.f_91074_.canInteractWith(entityhitresult.m_82443_(), 0)) return; //Forge: Entity may be traced via attack range, but the player may not have sufficient reach. No padding in client code.
+ if(!this.f_91074_.canInteractWith(entityhitresult.m_82443_(), 0)) break; //Forge: Entity may be traced via attack range, but the player may not have sufficient reach. No padding in client code.
InteractionResult interactionresult = this.f_91072_.m_105230_(this.f_91074_, entity, entityhitresult, interactionhand);
if (!interactionresult.m_19077_()) {
interactionresult = this.f_91072_.m_105226_(this.f_91074_, entity, interactionhand);
@@ -303,6 +309,22 @@
this.f_91026_.m_6180_("gui");
this.f_91065_.m_193832_(this.f_91012_);
this.f_91026_.m_7238_();
@@ -1660,6 +_,7 @@
this.f_91005_.m_120596_();
+ net.minecraftforge.event.ForgeEventFactory.onPreWorldTick(this.f_91073_, () -> true);
try {
this.f_91073_.m_104726_(() -> {
return true;
@@ -1675,6 +_,7 @@
throw new ReportedException(crashreport);
}
+ net.minecraftforge.event.ForgeEventFactory.onPostWorldTick(this.f_91073_, () -> true);
}
this.f_91026_.m_6182_("animateTick");
@@ -1694,6 +_,8 @@
this.f_91026_.m_6182_("keyboard");
this.f_91068_.m_90931_();
@@ -0,0 +1,11 @@
--- a/net/minecraft/client/gui/screens/ConnectScreen.java
+++ b/net/minecraft/client/gui/screens/ConnectScreen.java
@@ -71,6 +_,8 @@
}
if (!optional.isPresent()) {
+ ConnectScreen.f_95683_.error("Couldn't connect to server: Unknown host \"{}\"", p_169266_.m_171863_());
+ net.minecraftforge.network.DualStackUtils.logInitialPreferences();
p_169265_.execute(() -> {
p_169265_.m_91152_(new DisconnectedScreen(ConnectScreen.this.f_95686_, CommonComponents.f_130661_, ConnectScreen.f_169260_));
});
@@ -42,7 +42,7 @@
if (!Minecraft.m_91085_()) {
return Sheets.m_110792_();
} else {
@@ -340,9 +_,70 @@
@@ -340,9 +_,111 @@
}
}
@@ -55,8 +55,11 @@
+ // FORGE START
+
+ private static final java.util.function.Predicate<RenderType> SOLID_PREDICATE = type -> type == RenderType.m_110451_();
+ // Access to the two following editable maps is guarded by synchronization and they are lazily copied to the "readonly" maps on first read after modification
+ private static final Map<net.minecraftforge.registries.IRegistryDelegate<Block>, java.util.function.Predicate<RenderType>> blockRenderChecks = createRenderCheckMap(f_109275_);
+ private static final Map<net.minecraftforge.registries.IRegistryDelegate<Fluid>, java.util.function.Predicate<RenderType>> fluidRenderChecks = createRenderCheckMap(f_109276_);
+ @org.jetbrains.annotations.Nullable private static volatile Map<net.minecraftforge.registries.IRegistryDelegate<Block>, java.util.function.Predicate<RenderType>> blockRenderChecksReadOnly = null;
+ @org.jetbrains.annotations.Nullable private static volatile Map<net.minecraftforge.registries.IRegistryDelegate<Fluid>, java.util.function.Predicate<RenderType>> fluidRenderChecksReadOnly = null;
+
+ private static <T extends net.minecraftforge.registries.ForgeRegistryEntry<T>> Map<net.minecraftforge.registries.IRegistryDelegate<T>, java.util.function.Predicate<RenderType>> createRenderCheckMap(
+ Map<T, RenderType> vanillaMap
@@ -74,37 +77,75 @@
+ if (block instanceof LeavesBlock) {
+ return f_109277_ ? type == RenderType.m_110457_() : type == RenderType.m_110451_();
+ } else {
+ return blockRenderChecks.get(block.delegate).test(type);
+ return getBlockLayerPredicates().get(block.delegate).test(type);
+ }
+ }
+
+ public static boolean canRenderInLayer(FluidState fluid, RenderType type) {
+ return fluidRenderChecks.get(fluid.m_76152_().delegate).test(type);
+ return getFluidLayerPredicates().get(fluid.m_76152_().delegate).test(type);
+ }
+
+ public static void setRenderLayer(Block block, RenderType type) {
+ setRenderLayer(block, createMatchingLayerPredicate(type));
+ }
+
+ public static synchronized void setRenderLayer(Block block, java.util.function.Predicate<RenderType> predicate) {
+ checkClientLoading();
+ blockRenderChecks.put(block.delegate, predicate);
+ public static void setRenderLayer(Block block, java.util.function.Predicate<RenderType> predicate) {
+ synchronized (blockRenderChecks) {
+ blockRenderChecks.put(block.delegate, predicate);
+ blockRenderChecksReadOnly = null;
+ }
+ }
+
+ public static void setRenderLayer(Fluid fluid, RenderType type) {
+ setRenderLayer(fluid, createMatchingLayerPredicate(type));
+ }
+
+ public static synchronized void setRenderLayer(Fluid fluid, java.util.function.Predicate<RenderType> predicate) {
+ checkClientLoading();
+ fluidRenderChecks.put(fluid.delegate, predicate);
+ public static void setRenderLayer(Fluid fluid, java.util.function.Predicate<RenderType> predicate) {
+ synchronized (fluidRenderChecks) {
+ fluidRenderChecks.put(fluid.delegate, predicate);
+ fluidRenderChecksReadOnly = null;
+ }
+ }
+
+ private static void checkClientLoading() {
+ com.google.common.base.Preconditions.checkState(net.minecraftforge.client.loading.ClientModLoader.isLoading(),
+ "Render layers can only be set during client loading! " +
+ "This might ideally be done from `FMLClientSetupEvent`."
+ );
+ public static Map<net.minecraftforge.registries.IRegistryDelegate<Block>, java.util.function.Predicate<RenderType>> getBlockLayerPredicatesView() {
+ return java.util.Collections.unmodifiableMap(getBlockLayerPredicates());
+ }
+
+ private static Map<net.minecraftforge.registries.IRegistryDelegate<Block>, java.util.function.Predicate<RenderType>> getBlockLayerPredicates() {
+ Map<net.minecraftforge.registries.IRegistryDelegate<Block>, java.util.function.Predicate<RenderType>> map = blockRenderChecksReadOnly;
+ if (map == null) {
+ synchronized (blockRenderChecks) {
+ map = blockRenderChecksReadOnly;
+ if (map == null) {
+ blockRenderChecksReadOnly = map = copy(blockRenderChecks);
+ }
+ }
+ }
+ return map;
+ }
+
+
+ public static Map<net.minecraftforge.registries.IRegistryDelegate<Fluid>, java.util.function.Predicate<RenderType>> getFluidLayerPredicatesView() {
+ return java.util.Collections.unmodifiableMap(getFluidLayerPredicates());
+ }
+
+ private static Map<net.minecraftforge.registries.IRegistryDelegate<Fluid>, java.util.function.Predicate<RenderType>> getFluidLayerPredicates() {
+ Map<net.minecraftforge.registries.IRegistryDelegate<Fluid>, java.util.function.Predicate<RenderType>> map = fluidRenderChecksReadOnly;
+ if (map == null) {
+ synchronized (fluidRenderChecks) {
+ map = fluidRenderChecksReadOnly;
+ if (map == null) {
+ fluidRenderChecksReadOnly = map = copy(fluidRenderChecks);
+ }
+ }
+ }
+ return map;
+ }
+
+ private static <T> Map<net.minecraftforge.registries.IRegistryDelegate<T>, java.util.function.Predicate<RenderType>> copy(Map<net.minecraftforge.registries.IRegistryDelegate<T>, java.util.function.Predicate<RenderType>> map) {
+ var newMap = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(map);
+ newMap.defaultReturnValue(SOLID_PREDICATE);
+ return newMap;
+ }
+
+ private static java.util.function.Predicate<RenderType> createMatchingLayerPredicate(RenderType type) {
@@ -24,7 +24,7 @@
float f = this.f_109461_.f_91073_.m_46722_(1.0F) / (Minecraft.m_91405_() ? 1.0F : 2.0F);
if (!(f <= 0.0F)) {
Random random = new Random((long)this.f_109477_ * 312987231L);
@@ -1134,20 +_,23 @@
@@ -1134,20 +_,24 @@
RenderSystem.m_69421_(16640, Minecraft.f_91002_);
float f = p_109605_.m_109152_();
boolean flag2 = this.f_109461_.f_91073_.m_104583_().m_5781_(Mth.m_14107_(d0), Mth.m_14107_(d1)) || this.f_109461_.f_91065_.m_93090_().m_93715_();
@@ -35,6 +35,7 @@
- FogRenderer.m_109024_(p_109604_, FogRenderer.FogMode.FOG_SKY, f, flag2);
+ FogRenderer.setupFog(p_109604_, FogRenderer.FogMode.FOG_SKY, f, flag2, p_109601_);
});
+ net.minecraftforge.client.ForgeHooksClient.dispatchRenderStage(net.minecraftforge.client.event.RenderLevelStageEvent.Stage.AFTER_SKY, this, p_109600_, p_109607_, this.f_109477_, p_109604_, frustum);
profilerfiller.m_6182_("fog");
- FogRenderer.m_109024_(p_109604_, FogRenderer.FogMode.FOG_TERRAIN, Math.max(f, 32.0F), flag2);
+ FogRenderer.setupFog(p_109604_, FogRenderer.FogMode.FOG_TERRAIN, Math.max(f, 32.0F), flag2, p_109601_);
@@ -89,24 +90,50 @@
}
PoseStack posestack = RenderSystem.m_157191_();
@@ -1333,7 +_,7 @@
@@ -1333,7 +_,8 @@
this.f_109415_.m_83945_(this.f_109461_.m_91385_());
RenderStateShard.f_110126_.m_110185_();
profilerfiller.m_6182_("particles");
- this.f_109461_.f_91061_.m_107336_(p_109600_, multibuffersource$buffersource, p_109606_, p_109604_, p_109601_);
+ this.f_109461_.f_91061_.render(p_109600_, multibuffersource$buffersource, p_109606_, p_109604_, p_109601_, frustum);
+ net.minecraftforge.client.ForgeHooksClient.dispatchRenderStage(net.minecraftforge.client.event.RenderLevelStageEvent.Stage.AFTER_PARTICLES, this, p_109600_, p_109607_, this.f_109477_, p_109604_, frustum);
RenderStateShard.f_110126_.m_110188_();
} else {
profilerfiller.m_6182_("translucent");
@@ -1347,7 +_,7 @@
@@ -1347,7 +_,8 @@
profilerfiller.m_6182_("string");
this.m_172993_(RenderType.m_110503_(), p_109600_, d0, d1, d2, p_109607_);
profilerfiller.m_6182_("particles");
- this.f_109461_.f_91061_.m_107336_(p_109600_, multibuffersource$buffersource, p_109606_, p_109604_, p_109601_);
+ this.f_109461_.f_91061_.render(p_109600_, multibuffersource$buffersource, p_109606_, p_109604_, p_109601_, frustum);
+ net.minecraftforge.client.ForgeHooksClient.dispatchRenderStage(net.minecraftforge.client.event.RenderLevelStageEvent.Stage.AFTER_PARTICLES, this, p_109600_, p_109607_, this.f_109477_, p_109604_, frustum);
}
posestack.m_85836_();
@@ -1371,6 +_,7 @@
RenderStateShard.f_110127_.m_110185_();
profilerfiller.m_6182_("weather");
this.m_109703_(p_109606_, p_109601_, d0, d1, d2);
+ net.minecraftforge.client.ForgeHooksClient.dispatchRenderStage(net.minecraftforge.client.event.RenderLevelStageEvent.Stage.AFTER_WEATHER, this, p_109600_, p_109607_, this.f_109477_, p_109604_, frustum);
this.m_173012_(p_109604_);
RenderStateShard.f_110127_.m_110188_();
this.f_109418_.m_110023_(p_109601_);
@@ -1379,6 +_,7 @@
RenderSystem.m_69458_(false);
profilerfiller.m_6182_("weather");
this.m_109703_(p_109606_, p_109601_, d0, d1, d2);
+ net.minecraftforge.client.ForgeHooksClient.dispatchRenderStage(net.minecraftforge.client.event.RenderLevelStageEvent.Stage.AFTER_WEATHER, this, p_109600_, p_109607_, this.f_109477_, p_109604_, frustum);
this.m_173012_(p_109604_);
RenderSystem.m_69458_(true);
}
@@ -1521,6 +_,7 @@
VertexBuffer.m_85931_();
VertexBuffer.m_166875_();
this.f_109461_.m_91307_().m_7238_();
+ net.minecraftforge.client.ForgeHooksClient.dispatchRenderStage(p_172994_, this, p_172995_, p_172999_, this.f_109477_, this.f_109461_.f_91063_.m_109153_(), this.f_109442_ != null ? this.f_109442_ : this.f_172938_);
p_172994_.m_110188_();
}
@@ -1783,6 +_,11 @@
public void m_202423_(PoseStack p_202424_, Matrix4f p_202425_, float p_202426_, Camera p_202427_, boolean p_202428_, Runnable p_202429_) {
@@ -0,0 +1,10 @@
--- a/net/minecraft/client/renderer/OutlineBufferSource.java
+++ b/net/minecraft/client/renderer/OutlineBufferSource.java
@@ -105,5 +_,7 @@
public void m_5752_() {
this.f_109936_.m_5483_(this.f_109937_, this.f_109938_, this.f_109939_).m_6122_(this.f_85825_, this.f_85826_, this.f_85827_, this.f_85828_).m_7421_(this.f_109940_, this.f_109941_).m_5752_();
}
+
+ public com.mojang.blaze3d.vertex.VertexFormat getVertexFormat() { return f_109936_.getVertexFormat(); }
}
}
@@ -0,0 +1,9 @@
--- a/net/minecraft/client/renderer/SpriteCoordinateExpander.java
+++ b/net/minecraft/client/renderer/SpriteCoordinateExpander.java
@@ -54,4 +_,6 @@
public void m_5954_(float p_110808_, float p_110809_, float p_110810_, float p_110811_, float p_110812_, float p_110813_, float p_110814_, float p_110815_, float p_110816_, int p_110817_, int p_110818_, float p_110819_, float p_110820_, float p_110821_) {
this.f_110795_.m_5954_(p_110808_, p_110809_, p_110810_, p_110811_, p_110812_, p_110813_, p_110814_, this.f_110796_.m_118367_((double)(p_110815_ * 16.0F)), this.f_110796_.m_118393_((double)(p_110816_ * 16.0F)), p_110817_, p_110818_, p_110819_, p_110820_, p_110821_);
}
+
+ public com.mojang.blaze3d.vertex.VertexFormat getVertexFormat() { return f_110795_.getVertexFormat(); }
}
@@ -22,7 +22,7 @@
int j1 = Mth.m_14173_(i1);
- int k1;
+ int k1 = p_118311_;
+ if (false) // FORGE: do not lower the mipmap level
+ if (net.minecraftforge.common.ForgeConfig.CLIENT.allowMipmapLowering()) // FORGE: do not lower the mipmap level
if (j1 < p_118311_) {
f_118261_.warn("{}: dropping miplevel from {} to {}, because of minimum power of two: {}", this.f_118265_, p_118311_, j1, i1);
k1 = j1;
@@ -0,0 +1,31 @@
--- a/net/minecraft/client/server/LanServerDetection.java
+++ b/net/minecraft/client/server/LanServerDetection.java
@@ -33,7 +_,7 @@
this.setDaemon(true);
this.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LanServerDetection.f_120082_));
this.f_120088_ = new MulticastSocket(4445);
- this.f_120087_ = InetAddress.getByName("224.0.2.60");
+ this.f_120087_ = InetAddress.getByName(LanServerPinger.f_174974_);
this.f_120088_.setSoTimeout(5000);
this.f_120088_.joinGroup(this.f_120087_);
}
@@ -88,7 +_,19 @@
String s = LanServerPinger.m_120111_(p_120097_);
String s1 = LanServerPinger.m_120116_(p_120097_);
if (s1 != null) {
+ if (net.minecraftforge.network.DualStackUtils.checkIPv6(p_120098_)) {
+ final String ip;
+
+ // compress to short form if enabled in config
+ if (net.minecraftforge.common.ForgeConfig.CLIENT.compressLanIPv6Addresses.get())
+ ip = com.google.common.net.InetAddresses.toAddrString(p_120098_);
+ else
+ ip = p_120098_.getHostAddress();
+
+ s1 = "[" + ip + "]:" + s1;
+ } else {
s1 = p_120098_.getHostAddress() + ":" + s1;
+ }
boolean flag = false;
for(LanServer lanserver : this.f_120092_) {
@@ -0,0 +1,20 @@
--- a/net/minecraft/client/server/LanServerPinger.java
+++ b/net/minecraft/client/server/LanServerPinger.java
@@ -16,7 +_,7 @@
public class LanServerPinger extends Thread {
private static final AtomicInteger f_120101_ = new AtomicInteger(0);
private static final Logger f_120102_ = LogUtils.getLogger();
- public static final String f_174974_ = "224.0.2.60";
+ public static final String f_174974_ = net.minecraftforge.network.DualStackUtils.getMulticastGroup();
public static final int f_174975_ = 4445;
private static final long f_174976_ = 1500L;
private final String f_120103_;
@@ -39,7 +_,7 @@
while(!this.isInterrupted() && this.f_120105_) {
try {
- InetAddress inetaddress = InetAddress.getByName("224.0.2.60");
+ InetAddress inetaddress = InetAddress.getByName(f_174974_);
DatagramPacket datagrampacket = new DatagramPacket(abyte, abyte.length, inetaddress, 4445);
this.f_120104_.send(datagrampacket);
} catch (IOException ioexception) {
@@ -1,5 +1,14 @@
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -335,7 +_,7 @@
DispensibleContainerItem dispensiblecontaineritem = (DispensibleContainerItem)p_123562_.m_41720_();
BlockPos blockpos = p_123561_.m_7961_().m_142300_(p_123561_.m_6414_().m_61143_(DispenserBlock.f_52659_));
Level level = p_123561_.m_7727_();
- if (dispensiblecontaineritem.m_142073_((Player)null, level, blockpos, (BlockHitResult)null)) {
+ if (dispensiblecontaineritem.emptyContents((Player)null, level, blockpos, (BlockHitResult)null, p_123562_)) {
dispensiblecontaineritem.m_142131_((Player)null, level, p_123562_, blockpos);
return new ItemStack(Items.f_42446_);
} else {
@@ -393,9 +_,10 @@
level.m_46597_(blockpos, BaseFireBlock.m_49245_(level, blockpos));
level.m_142346_((Entity)null, GameEvent.f_157797_, blockpos);
@@ -29,7 +29,7 @@
}
public static Connection m_178300_(InetSocketAddress p_178301_, boolean p_178302_) {
+ if (p_178301_.getAddress() instanceof java.net.Inet6Address) System.setProperty("java.net.preferIPv4Stack", "false");
+ net.minecraftforge.network.DualStackUtils.checkIPv6(p_178301_.getAddress());
final Connection connection = new Connection(PacketFlow.CLIENTBOUND);
+ connection.activationHandler = net.minecraftforge.network.NetworkHooks::registerClientLoginChannel;
Class<? extends SocketChannel> oclass;
@@ -1,5 +1,18 @@
--- a/net/minecraft/resources/ResourceLocation.java
+++ b/net/minecraft/resources/ResourceLocation.java
@@ -38,10 +_,12 @@
}
}
+ /** Forge: Consider using {@link #parse(String)} instead, as Mojang made this constructor private in Vanilla 1.21 */
public ResourceLocation(String p_135809_) {
this(m_135832_(p_135809_, ':'));
}
+ /** Forge: Consider using {@link #fromNamespaceAndPath(String, String)} instead, as Mojang made this constructor private in Vanilla 1.21 */
public ResourceLocation(String p_135811_, String p_135812_) {
this(new String[]{p_135811_, p_135812_});
}
@@ -116,6 +_,12 @@
return i;
}
@@ -13,3 +26,19 @@
public String m_179910_() {
return this.toString().replace('/', '_').replace(':', '_');
}
@@ -182,5 +_,15 @@
public JsonElement serialize(ResourceLocation p_135855_, Type p_135856_, JsonSerializationContext p_135857_) {
return new JsonPrimitive(p_135855_.toString());
}
+ }
+
+ /** Forge: Backport of Vanilla 1.21 method */
+ public static ResourceLocation fromNamespaceAndPath(String namespace, String path) {
+ return new ResourceLocation(namespace, path);
+ }
+
+ /** Forge: Backport of Vanilla 1.21 method */
+ public static ResourceLocation parse(String location) {
+ return new ResourceLocation(location);
}
}
@@ -34,16 +34,41 @@
}
public <T> void m_140792_(TicketType<T> p_140793_, ChunkPos p_140794_, int p_140795_, T p_140796_) {
@@ -201,6 +_,14 @@
@@ -188,19 +_,37 @@
}
public <T> void m_140840_(TicketType<T> p_140841_, ChunkPos p_140842_, int p_140843_, T p_140844_) {
- Ticket<T> ticket = new Ticket<>(p_140841_, 33 - p_140843_, p_140844_);
+ addRegionTicket(p_140841_, p_140842_, p_140843_, p_140844_, false);
+ }
+
+ public <T> void addRegionTicket(TicketType<T> p_140841_, ChunkPos p_140842_, int p_140843_, T p_140844_, boolean forceTicks) {
+ Ticket<T> ticket = new Ticket<>(p_140841_, 33 - p_140843_, p_140844_, forceTicks);
long i = p_140842_.m_45588_();
this.m_140784_(i, ticket);
this.f_183901_.m_184151_(i, ticket);
}
public <T> void m_140849_(TicketType<T> p_140850_, ChunkPos p_140851_, int p_140852_, T p_140853_) {
- Ticket<T> ticket = new Ticket<>(p_140850_, 33 - p_140852_, p_140853_);
+ removeRegionTicket(p_140850_, p_140851_, p_140852_, p_140853_, false);
+ }
+
+ public <T> void removeRegionTicket(TicketType<T> p_140850_, ChunkPos p_140851_, int p_140852_, T p_140853_, boolean forceTicks) {
+ Ticket<T> ticket = new Ticket<>(p_140850_, 33 - p_140852_, p_140853_, forceTicks);
long i = p_140851_.m_45588_();
this.m_140818_(i, ticket);
this.f_183901_.m_184165_(i, ticket);
}
+ @Deprecated(forRemoval = true, since = "1.18.2") // Use addRegionTicket overload
+ public <T> void registerTicking(TicketType<T> type, ChunkPos pos, int distance, T value) {
+ this.m_140784_(pos.m_45588_(), new Ticket<>(type, 33 - distance, value, true));
+ addRegionTicket(type, pos, distance, value, true);
+ }
+
+ @Deprecated(forRemoval = true, since = "1.18.2") // Use removeRegionTicket overload
+ public <T> void releaseTicking(TicketType<T> type, ChunkPos pos, int distance, T value) {
+ this.m_140818_(pos.m_45588_(), new Ticket<>(type, 33 - distance, value, true));
+ removeRegionTicket(type, pos, distance, value, true);
+ }
+
private SortedArraySet<Ticket<?>> m_140857_(long p_140858_) {
@@ -17,18 +17,35 @@
levelchunk1.m_187632_(j);
if (flag2 && (this.f_8335_ || this.f_8336_) && this.f_8329_.m_6857_().m_61927_(chunkpos)) {
NaturalSpawner.m_47029_(this.f_8329_, levelchunk1, naturalspawner$spawnstate, this.f_8336_, this.f_8335_, flag1);
@@ -426,6 +_,14 @@
@@ -421,11 +_,29 @@
}
public <T> void m_8387_(TicketType<T> p_8388_, ChunkPos p_8389_, int p_8390_, T p_8391_) {
- this.f_8327_.m_140840_(p_8388_, p_8389_, p_8390_, p_8391_);
+ addRegionTicket(p_8388_, p_8389_, p_8390_, p_8391_, false);
+ }
+
+ public <T> void addRegionTicket(TicketType<T> p_8388_, ChunkPos p_8389_, int p_8390_, T p_8391_, boolean forceTicks) {
+ this.f_8327_.addRegionTicket(p_8388_, p_8389_, p_8390_, p_8391_, forceTicks);
}
public <T> void m_8438_(TicketType<T> p_8439_, ChunkPos p_8440_, int p_8441_, T p_8442_) {
this.f_8327_.m_140849_(p_8439_, p_8440_, p_8441_, p_8442_);
- this.f_8327_.m_140849_(p_8439_, p_8440_, p_8441_, p_8442_);
+ removeRegionTicket(p_8439_, p_8440_, p_8441_, p_8442_, false);
+ }
+
+ public <T> void removeRegionTicket(TicketType<T> p_8439_, ChunkPos p_8440_, int p_8441_, T p_8442_, boolean forceTicks) {
+ this.f_8327_.removeRegionTicket(p_8439_, p_8440_, p_8441_, p_8442_, forceTicks);
+ }
+
+ @Deprecated(forRemoval = true, since = "1.18.2") // Use addRegionTicket overload
+ public <T> void registerTickingTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
+ this.f_8327_.registerTicking(type, pos, distance, value);
+ addRegionTicket(type, pos, distance, value, true);
+ }
+
+ @Deprecated(forRemoval = true, since = "1.18.2") // Use removeRegionTicket overload
+ public <T> void releaseTickingTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
+ this.f_8327_.releaseTicking(type, pos, distance, value);
+ removeRegionTicket(type, pos, distance, value, true);
}
public void m_6692_(ChunkPos p_8400_, boolean p_8401_) {
@@ -15,11 +15,12 @@
});
final MinecraftServer f_9702_;
public volatile boolean f_9700_;
@@ -67,6 +_,7 @@
@@ -67,6 +_,8 @@
}
public void m_9711_(@Nullable InetAddress p_9712_, int p_9713_) throws IOException {
+ if (p_9712_ instanceof java.net.Inet6Address) System.setProperty("java.net.preferIPv4Stack", "false");
+ if (p_9712_ == null) p_9712_ = new java.net.InetSocketAddress(p_9713_).getAddress();
+ net.minecraftforge.network.DualStackUtils.checkIPv6(p_9712_);
synchronized(this.f_9703_) {
Class<? extends ServerSocketChannel> oclass;
LazyLoadedValue<? extends EventLoopGroup> lazyloadedvalue;
@@ -39,20 +39,20 @@
for(int i = 0; i < s.length(); ++i) {
if (!SharedConstants.m_136188_(s.charAt(i))) {
@@ -1129,8 +_,11 @@
@@ -1129,8 +_,10 @@
String s1 = p_143629_.m_143722_();
Component component = s1.isEmpty() ? null : new TranslatableComponent("chat.type.text", this.f_9743_.m_5446_(), s1);
Component component1 = new TranslatableComponent("chat.type.text", this.f_9743_.m_5446_(), s);
+ component1 = net.minecraftforge.common.ForgeHooks.onServerChatEvent(this, s, component1);
+ Component finalComponent = component1;
+ if (finalComponent != null)
this.f_9745_.m_6846_().m_143991_(component1, (p_184197_) -> {
- this.f_9745_.m_6846_().m_143991_(component1, (p_184197_) -> {
- return this.f_9743_.m_143421_(p_184197_) ? component : component1;
+ return this.f_9743_.m_143421_(p_184197_) ? component : finalComponent;
+ var event = net.minecraftforge.common.ForgeHooks.onServerChatEvent(this, s, component1, s1, component);
+ if (event != null && event.getComponent() != null)
+ this.f_9745_.m_6846_().m_143991_(event.getComponent(), (p_184197_) -> {
+ return this.f_9743_.m_143421_(p_184197_) ? event.getFilteredComponent() : event.getComponent();
}, ChatType.CHAT, this.f_9743_.m_142081_());
}
@@ -1216,12 +_,13 @@
@@ -1216,10 +_,10 @@
return;
}
@@ -64,11 +64,16 @@
+ if(!ServerGamePacketListenerImpl.this.f_9743_.canInteractWith(entity, 1.5D)) return; //Forge: If the entity cannot be reached, do nothing. Original check was dist < 6, range is 4.5, so vanilla used padding=1.5
ItemStack itemstack = ServerGamePacketListenerImpl.this.f_9743_.m_21120_(p_143679_).m_41777_();
InteractionResult interactionresult = p_143680_.m_143694_(ServerGamePacketListenerImpl.this.f_9743_, entity, p_143679_);
+ if (net.minecraftforge.common.ForgeHooks.onInteractEntityAt(f_9743_, entity, entity.m_20182_(), p_143679_) != null) return;
if (interactionresult.m_19077_()) {
CriteriaTriggers.f_10565_.m_61494_(ServerGamePacketListenerImpl.this.f_9743_, itemstack, entity);
if (interactionresult.m_19080_()) {
@@ -1243,7 +_,7 @@
@@ -1237,13 +_,15 @@
public void m_142143_(InteractionHand p_143682_, Vec3 p_143683_) {
this.m_143678_(p_143682_, (p_143686_, p_143687_, p_143688_) -> {
+ InteractionResult onInteractEntityAtResult = net.minecraftforge.common.ForgeHooks.onInteractEntityAt(f_9743_, entity, p_143683_, p_143682_);
+ if (onInteractEntityAtResult != null) return onInteractEntityAtResult;
return p_143687_.m_7111_(p_143686_, p_143683_, p_143688_);
});
}
public void m_141994_() {
if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && !(entity instanceof AbstractArrow) && entity != ServerGamePacketListenerImpl.this.f_9743_) {
@@ -42,14 +42,14 @@
}
@Nullable
@@ -317,6 +_,7 @@
public T m_20600_(ServerLevel p_20601_, @Nullable CompoundTag p_20602_, @Nullable Component p_20603_, @Nullable Player p_20604_, BlockPos p_20605_, MobSpawnType p_20606_, boolean p_20607_, boolean p_20608_) {
T t = this.m_20655_(p_20601_, p_20602_, p_20603_, p_20604_, p_20605_, p_20606_, p_20607_, p_20608_);
if (t != null) {
+ if (t instanceof net.minecraft.world.entity.Mob && net.minecraftforge.event.ForgeEventFactory.doSpecialSpawn((net.minecraft.world.entity.Mob) t, p_20601_, p_20605_.m_123341_(), p_20605_.m_123342_(), p_20605_.m_123343_(), null, p_20606_)) return null;
p_20601_.m_47205_(t);
}
@@ -342,6 +_,7 @@
Mob mob = (Mob)t;
mob.f_20885_ = mob.m_146908_();
mob.f_20883_ = mob.m_146908_();
+ if (!net.minecraftforge.event.ForgeEventFactory.doSpecialSpawn(mob, p_20656_, (float) mob.m_20185_(), (float) mob.m_20186_(), (float) mob.m_20189_(), null, p_20661_))
mob.m_6518_(p_20656_, p_20656_.m_6436_(mob.m_142538_()), p_20661_, (SpawnGroupData)null, p_20657_);
mob.m_8032_();
}
@@ -544,14 +_,23 @@
}
@@ -469,7 +469,7 @@
ItemStack itemstack = this.m_21120_(p_21159_);
if (!itemstack.m_41619_() && !this.m_6117_()) {
+ int duration = net.minecraftforge.event.ForgeEventFactory.onItemUseStart(this, itemstack, itemstack.m_41779_());
+ if (duration <= 0) return;
+ if (duration < net.minecraftforge.common.ForgeConfig.SERVER.getUseItemDuration()) return;
this.f_20935_ = itemstack;
- this.f_20936_ = itemstack.m_41779_();
+ this.f_20936_ = duration;
@@ -1,10 +1,15 @@
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -206,6 +_,7 @@
@@ -205,7 +_,11 @@
}
public void m_6710_(@Nullable LivingEntity p_21544_) {
this.f_21362_ = p_21544_;
+ net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget(this, p_21544_);
- this.f_21362_ = p_21544_;
+ net.minecraftforge.event.entity.living.LivingChangeTargetEvent changeTargetEvent = net.minecraftforge.common.ForgeHooks.onLivingChangeTarget(this, p_21544_, net.minecraftforge.event.entity.living.LivingChangeTargetEvent.LivingTargetType.MOB_TARGET);
+ if(!changeTargetEvent.isCanceled()) {
+ this.f_21362_ = changeTargetEvent.getNewTarget();
+ net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget(this, this.f_21362_); // TODO: Remove in 1.20
+ }
}
public boolean m_6549_(EntityType<?> p_21399_) {
@@ -0,0 +1,17 @@
--- a/net/minecraft/world/entity/ai/behavior/StartAttacking.java
+++ b/net/minecraft/world/entity/ai/behavior/StartAttacking.java
@@ -42,7 +_,11 @@
}
private void m_24213_(E p_24214_, LivingEntity p_24215_) {
- p_24214_.m_6274_().m_21879_(MemoryModuleType.f_26372_, p_24215_);
- p_24214_.m_6274_().m_21936_(MemoryModuleType.f_26326_);
- }
+ net.minecraftforge.event.entity.living.LivingChangeTargetEvent changeTargetEvent = net.minecraftforge.common.ForgeHooks.onLivingChangeTarget(p_24214_, p_24215_, net.minecraftforge.event.entity.living.LivingChangeTargetEvent.LivingTargetType.BEHAVIOR_TARGET);
+ if(!changeTargetEvent.isCanceled()) {
+ p_24214_.m_6274_().m_21879_(MemoryModuleType.f_26372_, changeTargetEvent.getNewTarget());
+ p_24214_.m_6274_().m_21936_(MemoryModuleType.f_26326_);
+ net.minecraftforge.common.ForgeHooks.onLivingSetAttackTarget(p_24214_, changeTargetEvent.getNewTarget(), net.minecraftforge.event.entity.living.LivingChangeTargetEvent.LivingTargetType.BEHAVIOR_TARGET); // TODO: Remove in 1.20
+ }
+ }
}
@@ -5,7 +5,18 @@
public boolean m_8036_() {
- if (!this.f_25837_.f_19853_.m_46469_().m_46207_(GameRules.f_46132_)) {
+ if (!net.minecraftforge.common.ForgeHooks.canEntityDestroy(this.f_25837_.f_19853_, this.f_25602_, this.f_25837_)) {
+ if (!net.minecraftforge.event.ForgeEventFactory.getMobGriefingEvent(this.f_25837_.f_19853_, this.f_25837_)) {
return false;
} else if (this.f_25600_ > 0) {
--this.f_25600_;
@@ -133,7 +_,9 @@
if (chunkaccess == null) {
return false;
} else {
- return chunkaccess.m_8055_(p_25851_).m_60713_(this.f_25836_) && chunkaccess.m_8055_(p_25851_.m_7494_()).m_60795_() && chunkaccess.m_8055_(p_25851_.m_6630_(2)).m_60795_();
+ net.minecraft.world.level.block.state.BlockState state = chunkaccess.m_8055_(p_25851_);
+ if (!(state.canEntityDestroy(p_25850_, p_25851_, this.f_25837_) && net.minecraftforge.event.ForgeEventFactory.onEntityDestroyBlock(this.f_25837_, p_25851_, state))) return false;
+ return state.m_60713_(this.f_25836_) && chunkaccess.m_8055_(p_25851_.m_7494_()).m_60795_() && chunkaccess.m_8055_(p_25851_.m_6630_(2)).m_60795_();
}
}
}
@@ -1,11 +1,12 @@
--- a/net/minecraft/world/entity/animal/Cat.java
+++ b/net/minecraft/world/entity/animal/Cat.java
@@ -400,7 +_,7 @@
@@ -399,8 +_,8 @@
if (this.m_21830_(p_28153_)) {
if (!(item instanceof DyeItem)) {
if (item.m_41472_() && this.m_6898_(itemstack) && this.m_21223_() < this.m_21233_()) {
+ this.m_5634_((float)itemstack.getFoodProperties(this).m_38744_());
this.m_142075_(p_28153_, p_28154_, itemstack);
- this.m_5634_((float)item.m_41473_().m_38744_());
+ this.m_5634_((float)itemstack.getFoodProperties(this).m_38744_());
return InteractionResult.CONSUME;
}
@@ -1,11 +1,15 @@
--- a/net/minecraft/world/entity/animal/Wolf.java
+++ b/net/minecraft/world/entity/animal/Wolf.java
@@ -329,7 +_,7 @@
@@ -325,11 +_,11 @@
} else {
if (this.m_21824_()) {
if (this.m_6898_(itemstack) && this.m_21223_() < this.m_21233_()) {
+ this.m_5634_((float)itemstack.getFoodProperties(this).m_38744_());
if (!p_30412_.m_150110_().f_35937_) {
itemstack.m_41774_(1);
}
- this.m_5634_((float)item.m_41473_().m_38744_());
+ this.m_5634_((float)itemstack.getFoodProperties(this).m_38744_());
this.m_146859_(GameEvent.f_157771_, this.m_146901_());
return InteractionResult.SUCCESS;
}
@@ -194,33 +194,14 @@
this.m_21008_(p_36159_, ItemStack.f_41583_);
}
@@ -992,7 +_,7 @@
double d1 = p_36201_.f_82481_;
double d2 = 0.05D;
@@ -986,6 +_,7 @@
return !this.f_36077_.f_35935_;
}
- while(d0 != 0.0D && this.f_19853_.m_45756_(this, this.m_142469_().m_82386_(d0, (double)(-this.f_19793_), 0.0D))) {
+ while(d0 != 0.0D && this.f_19853_.m_45756_(this, this.m_142469_().m_82386_(d0, (double)(-this.getStepHeight()), 0.0D))) {
if (d0 < 0.05D && d0 >= -0.05D) {
d0 = 0.0D;
} else if (d0 > 0.0D) {
@@ -1002,7 +_,7 @@
}
}
- while(d1 != 0.0D && this.f_19853_.m_45756_(this, this.m_142469_().m_82386_(0.0D, (double)(-this.f_19793_), d1))) {
+ while(d1 != 0.0D && this.f_19853_.m_45756_(this, this.m_142469_().m_82386_(0.0D, (double)(-this.getStepHeight()), d1))) {
if (d1 < 0.05D && d1 >= -0.05D) {
d1 = 0.0D;
} else if (d1 > 0.0D) {
@@ -1012,7 +_,7 @@
}
}
- while(d0 != 0.0D && d1 != 0.0D && this.f_19853_.m_45756_(this, this.m_142469_().m_82386_(d0, (double)(-this.f_19793_), d1))) {
+ while(d0 != 0.0D && d1 != 0.0D && this.f_19853_.m_45756_(this, this.m_142469_().m_82386_(d0, (double)(-this.getStepHeight()), d1))) {
if (d0 < 0.05D && d0 >= -0.05D) {
d0 = 0.0D;
} else if (d0 > 0.0D) {
+ // Forge: Don't update this method to use IForgeEntity#getStepHeight() - https://github.com/MinecraftForge/MinecraftForge/issues/8922
protected Vec3 m_5763_(Vec3 p_36201_, MoverType p_36202_) {
if (!this.f_36077_.f_35935_ && (p_36202_ == MoverType.SELF || p_36202_ == MoverType.PLAYER) && this.m_36343_() && this.m_36386_()) {
double d0 = p_36201_.f_82479_;
@@ -1037,10 +_,11 @@
}
@@ -13,3 +13,12 @@
} else if (p_39052_ >= 1 && p_39052_ < 28) {
if (!this.m_38903_(itemstack1, 28, 37, false)) {
return ItemStack.f_41583_;
@@ -141,7 +_,7 @@
this.f_39034_.m_8050_(1, p_39054_);
this.f_39034_.m_8050_(2, p_39055_);
this.f_39032_.m_6201_(1);
- this.f_39033_.m_39292_(Level::m_151543_);
+ this.f_39033_.m_39292_(Level::blockEntityChangedWithoutNeighborUpdates);
}
}
@@ -1,5 +1,53 @@
--- a/net/minecraft/world/inventory/GrindstoneMenu.java
+++ b/net/minecraft/world/inventory/GrindstoneMenu.java
@@ -35,6 +_,7 @@
}
};
private final ContainerLevelAccess f_39561_;
+ private int xp = -1;
public GrindstoneMenu(int p_39563_, Inventory p_39564_) {
this(p_39563_, p_39564_, ContainerLevelAccess.f_39287_);
@@ -45,12 +_,12 @@
this.f_39561_ = p_39568_;
this.m_38897_(new Slot(this.f_39560_, 0, 49, 19) {
public boolean m_5857_(ItemStack p_39607_) {
- return p_39607_.m_41763_() || p_39607_.m_150930_(Items.f_42690_) || p_39607_.m_41793_();
+ return true; //Allow all items in the slot, not just repairable
}
});
this.m_38897_(new Slot(this.f_39560_, 1, 49, 40) {
public boolean m_5857_(ItemStack p_39616_) {
- return p_39616_.m_41763_() || p_39616_.m_150930_(Items.f_42690_) || p_39616_.m_41793_();
+ return true; //Allow all items in the slot, not just repairable
}
});
this.m_38897_(new Slot(this.f_39559_, 2, 129, 34) {
@@ -59,6 +_,7 @@
}
public void m_142406_(Player p_150574_, ItemStack p_150575_) {
+ if (net.minecraftforge.common.ForgeHooks.onGrindstoneTake(GrindstoneMenu.this.f_39560_, p_39568_, this::m_39631_)) return;
p_39568_.m_39292_((p_39634_, p_39635_) -> {
if (p_39634_ instanceof ServerLevel) {
ExperienceOrb.m_147082_((ServerLevel)p_39634_, Vec3.m_82512_(p_39635_), this.m_39631_(p_39634_));
@@ -71,6 +_,7 @@
}
private int m_39631_(Level p_39632_) {
+ if (xp > -1) return xp;
int l = 0;
l += this.m_39636_(GrindstoneMenu.this.f_39560_.m_8020_(0));
l += this.m_39636_(GrindstoneMenu.this.f_39560_.m_8020_(1));
@@ -123,6 +_,8 @@
ItemStack itemstack1 = this.f_39560_.m_8020_(1);
boolean flag = !itemstack.m_41619_() || !itemstack1.m_41619_();
boolean flag1 = !itemstack.m_41619_() && !itemstack1.m_41619_();
+ this.xp = net.minecraftforge.common.ForgeHooks.onGrindstoneChange(itemstack, itemstack1, this.f_39559_, -1);
+ if (this.xp == Integer.MIN_VALUE)
if (!flag) {
this.f_39559_.m_6836_(0, ItemStack.f_41583_);
} else {
@@ -144,12 +_,13 @@
}
@@ -19,3 +67,14 @@
if (!ItemStack.m_41728_(itemstack, itemstack1)) {
this.f_39559_.m_6836_(0, ItemStack.f_41583_);
this.m_38946_();
@@ -164,6 +_,10 @@
itemstack2 = flag3 ? itemstack : itemstack1;
}
+ // Forge: Skip the repair if the result would give an item stack with a count not normally obtainable
+ if (j > itemstack2.m_41741_())
+ this.f_39559_.m_6836_(0, ItemStack.f_41583_);
+ else
this.f_39559_.m_6836_(0, this.m_39579_(itemstack2, i, j));
}
@@ -38,15 +38,44 @@
p_40704_.m_5496_(p_150709_, 1.0F, 1.0F);
});
p_40703_.m_142346_(p_40704_, GameEvent.f_157816_, blockpos);
@@ -72,7 +_,7 @@
@@ -72,8 +_,8 @@
return InteractionResultHolder.m_19100_(itemstack);
} else {
BlockState blockstate = p_40703_.m_8055_(blockpos);
- BlockPos blockpos2 = blockstate.m_60734_() instanceof LiquidBlockContainer && this.f_40687_ == Fluids.f_76193_ ? blockpos : blockpos1;
- if (this.m_142073_(p_40704_, p_40703_, blockpos2, blockhitresult)) {
+ BlockPos blockpos2 = canBlockContainFluid(p_40703_, blockpos, blockstate) ? blockpos : blockpos1;
if (this.m_142073_(p_40704_, p_40703_, blockpos2, blockhitresult)) {
+ if (this.emptyContents(p_40704_, p_40703_, blockpos2, blockhitresult, itemstack)) {
this.m_142131_(p_40704_, p_40703_, itemstack, blockpos2);
if (p_40704_ instanceof ServerPlayer) {
CriteriaTriggers.f_10591_.m_59469_((ServerPlayer)p_40704_, blockpos2, itemstack);
@@ -98,7 +_,12 @@
public void m_142131_(@Nullable Player p_150711_, Level p_150712_, ItemStack p_150713_, BlockPos p_150714_) {
}
+ @Deprecated //Forge: use the ItemStack sensitive version
public boolean m_142073_(@Nullable Player p_150716_, Level p_150717_, BlockPos p_150718_, @Nullable BlockHitResult p_150719_) {
+ return this.emptyContents(p_150716_, p_150717_, p_150718_, p_150719_, null);
+ }
+
+ public boolean emptyContents(@Nullable Player p_150716_, Level p_150717_, BlockPos p_150718_, @Nullable BlockHitResult p_150719_, @Nullable ItemStack container) {
if (!(this.f_40687_ instanceof FlowingFluid)) {
return false;
} else {
@@ -107,8 +_,12 @@
Material material = blockstate.m_60767_();
boolean flag = blockstate.m_60722_(this.f_40687_);
boolean flag1 = blockstate.m_60795_() || flag || block instanceof LiquidBlockContainer && ((LiquidBlockContainer)block).m_6044_(p_150717_, p_150718_, blockstate, this.f_40687_);
+ var containedFluidStack = java.util.Optional.ofNullable(container).flatMap(net.minecraftforge.fluids.FluidUtil::getFluidContained);
if (!flag1) {
- return p_150719_ != null && this.m_142073_(p_150716_, p_150717_, p_150719_.m_82425_().m_142300_(p_150719_.m_82434_()), (BlockHitResult)null);
+ return p_150719_ != null && this.emptyContents(p_150716_, p_150717_, p_150719_.m_82425_().m_142300_(p_150719_.m_82434_()), (BlockHitResult)null, container);
+ } else if (p_150717_.m_6042_().m_63951_() && containedFluidStack.isPresent() && this.f_40687_.getAttributes().doesVaporize(p_150717_, p_150718_, containedFluidStack.get())) {
+ this.f_40687_.getAttributes().vaporize(p_150716_, p_150717_, p_150718_, containedFluidStack.get());
+ return true;
} else if (p_150717_.m_6042_().m_63951_() && this.f_40687_.m_205067_(FluidTags.f_13131_)) {
int i = p_150718_.m_123341_();
int j = p_150718_.m_123342_();
@@ -120,7 +_,7 @@
}
@@ -0,0 +1,19 @@
--- a/net/minecraft/world/item/BundleItem.java
+++ b/net/minecraft/world/item/BundleItem.java
@@ -41,7 +_,7 @@
}
public boolean m_142207_(ItemStack p_150733_, Slot p_150734_, ClickAction p_150735_, Player p_150736_) {
- if (p_150735_ != ClickAction.SECONDARY) {
+ if (p_150733_.m_41613_() != 1 || p_150735_ != ClickAction.SECONDARY) {
return false;
} else {
ItemStack itemstack = p_150734_.m_7993_();
@@ -63,6 +_,7 @@
}
public boolean m_142305_(ItemStack p_150742_, ItemStack p_150743_, Slot p_150744_, ClickAction p_150745_, Player p_150746_, SlotAccess p_150747_) {
+ if (p_150742_.m_41613_() != 1) return false;
if (p_150745_ == ClickAction.SECONDARY && p_150744_.m_150651_(p_150746_)) {
if (p_150743_.m_41619_()) {
m_150780_(p_150742_).ifPresent((p_186347_) -> {
@@ -0,0 +1,10 @@
--- a/net/minecraft/world/item/CrossbowItem.java
+++ b/net/minecraft/world/item/CrossbowItem.java
@@ -243,6 +_,7 @@
}
public static void m_40887_(Level p_40888_, LivingEntity p_40889_, InteractionHand p_40890_, ItemStack p_40891_, float p_40892_, float p_40893_) {
+ if (p_40889_ instanceof Player player && net.minecraftforge.event.ForgeEventFactory.onArrowLoose(p_40891_, p_40888_, player, 1, true) < 0) return;
List<ItemStack> list = m_40941_(p_40891_);
float[] afloat = m_40923_(p_40889_.m_21187_());
@@ -0,0 +1,14 @@
--- a/net/minecraft/world/item/DispensibleContainerItem.java
+++ b/net/minecraft/world/item/DispensibleContainerItem.java
@@ -6,9 +_,10 @@
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.BlockHitResult;
-public interface DispensibleContainerItem {
+public interface DispensibleContainerItem extends net.minecraftforge.common.extensions.IForgeDispensibleContainerItem {
default void m_142131_(@Nullable Player p_150817_, Level p_150818_, ItemStack p_150819_, BlockPos p_150820_) {
}
+ @Deprecated //Forge: use the ItemStack sensitive version
boolean m_142073_(@Nullable Player p_150821_, Level p_150822_, BlockPos p_150823_, @Nullable BlockHitResult p_150824_);
}
@@ -71,9 +71,11 @@
if (blockstate1 == p_46606_) {
if (blockstate != blockstate1) {
this.m_6550_(p_46605_, blockstate, blockstate1);
@@ -216,8 +_,6 @@
@@ -215,9 +_,8 @@
}
this.m_6559_(p_46605_, blockstate, blockstate1);
+ p_46606_.onBlockStateChange(this, p_46605_, blockstate);
}
-
- return true;
@@ -205,7 +207,7 @@
if (p_151539_ instanceof EnderDragon) {
EnderDragon enderdragon = (EnderDragon)p_151539_;
@@ -570,6 +_,12 @@
@@ -570,17 +_,29 @@
}
});
@@ -218,10 +220,20 @@
return list;
}
@@ -581,6 +_,7 @@
@Nullable
public abstract Entity m_6815_(int p_46492_);
- public void m_151543_(BlockPos p_151544_) {
+ public void blockEntityChangedWithoutNeighborUpdates(BlockPos p_151544_) {
if (this.m_46805_(p_151544_)) {
this.m_46745_(p_151544_).m_8092_(true);
}
+ }
+ /** @deprecated Call {@link #blockEntityChangedWithoutNeighborUpdates(BlockPos)} instead. See <a href="https://github.com/MinecraftForge/MinecraftForge/pull/9235">#9235</a> for details. */
+ @Deprecated
+ public void m_151543_(BlockPos p_151544_) {
+ this.blockEntityChangedWithoutNeighborUpdates(p_151544_);
+ this.m_46717_(p_151544_, m_8055_(p_151544_).m_60734_()); //Notify neighbors of changes
}
@@ -4,7 +4,7 @@
import net.minecraft.world.phys.Vec3;
import org.slf4j.Logger;
+// TODO: ForgeEventFactory.getPotentialSpawns, ForgeHooks.canEntitySpawn
+// TODO: ForgeHooks.canEntitySpawn
public final class NaturalSpawner {
private static final Logger f_46977_ = LogUtils.getLogger();
private static final int f_151589_ = 24;
@@ -35,6 +35,15 @@
return;
}
@@ -265,7 +_,7 @@
}
private static WeightedRandomList<MobSpawnSettings.SpawnerData> m_204168_(ServerLevel p_204169_, StructureFeatureManager p_204170_, ChunkGenerator p_204171_, MobCategory p_204172_, BlockPos p_204173_, @Nullable Holder<Biome> p_204174_) {
- return m_186529_(p_204173_, p_204169_, p_204172_, p_204170_) ? NetherFortressFeature.f_66381_ : p_204171_.m_203315_(p_204174_ != null ? p_204174_ : p_204169_.m_204166_(p_204173_), p_204170_, p_204172_, p_204173_);
+ return net.minecraftforge.event.ForgeEventFactory.getPotentialSpawns(p_204169_, p_204172_, p_204173_, m_186529_(p_204173_, p_204169_, p_204172_, p_204170_) ? NetherFortressFeature.f_66381_ : p_204171_.m_203315_(p_204174_ != null ? p_204174_ : p_204169_.m_204166_(p_204173_), p_204170_, p_204172_, p_204173_));
}
public static boolean m_186529_(BlockPos p_186530_, ServerLevel p_186531_, MobCategory p_186532_, StructureFeatureManager p_186533_) {
@@ -304,6 +_,13 @@
if (p_47052_ == SpawnPlacements.Type.NO_RESTRICTIONS) {
return true;
@@ -35,30 +35,30 @@
p_49385_.m_46672_(p_49386_.m_7494_(), this);
}
@@ -139,6 +_,7 @@
@@ -139,6 +_,11 @@
return blockstate.m_61124_(this.m_7978_(), flag1 ? RailShape.EAST_WEST : RailShape.NORTH_SOUTH).m_61124_(f_152149_, Boolean.valueOf(flag));
}
+ @Deprecated //Forge: Use getRailDirection(IBlockAccess, BlockPos, IBlockState, EntityMinecart) for enhanced ability
+ /**
+ * @deprecated Forge: Use {@link BaseRailBlock#getRailDirection(BlockState, BlockGetter, BlockPos, net.minecraft.world.entity.vehicle.AbstractMinecart)} for enhanced ability
+ * If you do change this property be aware that other functions in this/subclasses may break as they can make assumptions about this property
+ */
+ @Deprecated
public abstract Property<RailShape> m_7978_();
public BlockState m_7417_(BlockState p_152151_, Direction p_152152_, BlockState p_152153_, LevelAccessor p_152154_, BlockPos p_152155_, BlockPos p_152156_) {
@@ -152,4 +_,18 @@
@@ -152,4 +_,14 @@
public FluidState m_5888_(BlockState p_152158_) {
return p_152158_.m_61143_(f_152149_) ? Fluids.f_76193_.m_76068_(false) : super.m_5888_(p_152158_);
}
+
+ /* ======================================== FORGE START =====================================*/
+
+ @Override
+ public boolean isFlexibleRail(BlockState state, BlockGetter world, BlockPos pos)
+ {
+ return !this.f_49357_;
+ public boolean isFlexibleRail(BlockState state, BlockGetter world, BlockPos pos) {
+ return !this.f_49357_;
+ }
+
+ @Override
+ public RailShape getRailDirection(BlockState state, BlockGetter world, BlockPos pos, @javax.annotation.Nullable net.minecraft.world.entity.vehicle.AbstractMinecart cart) {
+ return state.m_61143_(m_7978_());
+ }
+ /* ========================================= FORGE END ======================================*/
}
@@ -1,5 +1,16 @@
--- a/net/minecraft/world/level/block/DetectorRailBlock.java
+++ b/net/minecraft/world/level/block/DetectorRailBlock.java
@@ -31,6 +_,10 @@
public DetectorRailBlock(BlockBehaviour.Properties p_52431_) {
super(true, p_52431_);
+ this.registerDefaultState();
+ }
+
+ protected void registerDefaultState() {
this.m_49959_(this.f_49792_.m_61090_().m_61124_(f_52428_, Boolean.valueOf(false)).m_61124_(f_52427_, RailShape.NORTH_SOUTH).m_61124_(f_152149_, Boolean.valueOf(false)));
}
@@ -135,7 +_,9 @@
return list.get(0).m_38534_().m_45436_();
}
@@ -11,3 +22,11 @@
if (!list1.isEmpty()) {
return AbstractContainerMenu.m_38938_((Container)list1.get(0));
}
@@ -270,6 +_,6 @@
}
protected void m_7926_(StateDefinition.Builder<Block, BlockState> p_52469_) {
- p_52469_.m_61104_(f_52427_, f_52428_, f_152149_);
+ p_52469_.m_61104_(m_7978_(), f_52428_, f_152149_);
}
}
@@ -0,0 +1,41 @@
--- a/net/minecraft/world/level/block/FlowerBlock.java
+++ b/net/minecraft/world/level/block/FlowerBlock.java
@@ -15,6 +_,17 @@
private final MobEffect f_53508_;
private final int f_53509_;
+ private final java.util.function.Supplier<MobEffect> suspiciousStewEffectSupplier;
+
+ public FlowerBlock(java.util.function.Supplier<MobEffect> effectSupplier, int p_53513_, BlockBehaviour.Properties p_53514_) {
+ super(p_53514_);
+ this.f_53508_ = null;
+ this.suspiciousStewEffectSupplier = effectSupplier;
+ this.f_53509_ = p_53513_;
+ }
+
+ /** @deprecated FORGE: Use supplier version instead */
+ @Deprecated
public FlowerBlock(MobEffect p_53512_, int p_53513_, BlockBehaviour.Properties p_53514_) {
super(p_53514_);
this.f_53508_ = p_53512_;
@@ -23,6 +_,7 @@
} else {
this.f_53509_ = p_53513_ * 20;
}
+ this.suspiciousStewEffectSupplier = net.minecraftforge.registries.RegistryObject.create(p_53512_.getRegistryName(), net.minecraftforge.registries.ForgeRegistries.MOB_EFFECTS);
}
@@ -36,10 +_,12 @@
}
public MobEffect m_53521_() {
+ if (true) return this.suspiciousStewEffectSupplier.get();
return this.f_53508_;
}
public int m_53522_() {
+ if (!this.suspiciousStewEffectSupplier.get().m_8093_()) return this.f_53509_ * 20;
return this.f_53509_;
}
}
@@ -0,0 +1,12 @@
--- a/net/minecraft/world/level/block/FungusBlock.java
+++ b/net/minecraft/world/level/block/FungusBlock.java
@@ -44,6 +_,8 @@
}
public void m_7719_(ServerLevel p_53603_, Random p_53604_, BlockPos p_53605_, BlockState p_53606_) {
- this.f_53597_.get().m_203334_().m_65385_(p_53603_, p_53603_.m_7726_().m_8481_(), p_53604_, p_53605_);
+ net.minecraftforge.event.world.SaplingGrowTreeEvent event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_53603_, p_53604_, p_53605_, this.f_53597_.get());
+ if (event.getResult().equals(net.minecraftforge.eventbus.api.Event.Result.DENY)) return;
+ event.getFeature().m_203334_().m_65385_(p_53603_, p_53603_.m_7726_().m_8481_(), p_53604_, p_53605_);
}
}
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff. Show more