Compare commits

...
46 Commits
Author SHA1 Message Date
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 <[email protected]>

* 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 <[email protected]>

Co-authored-by: ferriarnus <[email protected]>
Co-authored-by: matyrobbrt <[email protected]>
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
139 changed files with 4260 additions and 1220 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()
}
})
+77 -54
View File
@@ -10,6 +10,12 @@ buildscript {
}
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
@@ -30,7 +36,7 @@ plugins {
id 'com.github.ben-manes.versions' version '0.36.0'
id 'net.minecraftforge.gradleutils' version '2.+'
id 'eclipse'
id 'de.undercouch.download' version '4.1.2'
id 'de.undercouch.download' version '5.1.3'
}
Util.init() //Init all our extension methods!
@@ -48,7 +54,7 @@ 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'
EVENTBUS_VERSION = '5.0.3'
@@ -58,6 +64,7 @@ ext {
ASM_VERSION = '9.2'
INSTALLER_VERSION = '2.1.+'
MIXIN_VERSION = '0.8.5'
JARJAR_VERSION = '0.3.0'
GIT_INFO = gradleutils.gitInfo
VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, '[0-9]', MC_VERSION)
@@ -274,6 +281,7 @@ def sharedDeps = {
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 "net.minecraftforge:JarJar:${JARJAR_VERSION}"
/*
installer 'org.lwjgl:lwjgl:3.2.2'
@@ -420,6 +428,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 {
@@ -1146,86 +1156,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 { getLatestForgeVersion(MC_VERSION) }.map { MC_VERSION + '-' + it })
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,7 +1269,8 @@ 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}'),
+2
View File
@@ -6,4 +6,6 @@ dependencies {
implementation 'org.ow2.asm:asm:9.1'
implementation 'org.ow2.asm:asm-tree:9.1'
implementation 'net.minecraftforge:srgutils:0.4.+'
implementation 'commons-io:commons-io:2.8.0'
implementation 'com.google.code.gson:gson:2.10'
}
@@ -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()
}
@@ -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,9 @@ public class Util {
throw e
}
}
public static String getLatestForgeVersion(mcVersion) {
def json = new JsonSlurper().parseText(new URL("https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json").getText("UTF-8"))
return json.promos["$mcVersion-latest"]
}
}
@@ -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 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") }
}
}
@@ -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();
@@ -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);
+2 -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,7 @@ 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:JarJar:${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;
}
@@ -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,8 +24,9 @@ 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";
@@ -40,12 +41,15 @@ public abstract class AbstractModLocator implements IModLocator {
IModFile mod;
var type = sj.getManifest().getMainAttributes().getValue(ModFile.TYPE);
if (type == null) {
type = getDefaultJarModType();
}
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 +72,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,113 @@
/*
* 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.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.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
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();
}
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(entry -> new EarlyLoadingException.ExceptionData(
getErrorTranslationKey(entry),
entry.identifier().group() + ":" + entry.identifier().artifact(),
entry.sources()
.stream()
.flatMap(this::getModWithVersionRangeStream)
.map(this::formatError)
.collect(Collectors.joining(", ")))).toList();
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
}
@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";
}
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");
}
}
@@ -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
+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-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -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_;
}
}
}
@@ -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);
@@ -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(); }
}
@@ -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) {
@@ -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;
@@ -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,16 +39,16 @@
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_());
}
@@ -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 @@
}
@@ -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 @@
}
@@ -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_());
@@ -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;
@@ -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_);
}
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/world/level/block/MushroomBlock.java
+++ b/net/minecraft/world/level/block/MushroomBlock.java
@@ -70,7 +_,7 @@
@@ -70,13 +_,15 @@
if (blockstate.m_204336_(BlockTags.f_13057_)) {
return true;
} else {
@@ -9,3 +9,12 @@
}
}
public boolean m_54859_(ServerLevel p_54860_, BlockPos p_54861_, BlockState p_54862_, Random p_54863_) {
+ net.minecraftforge.event.world.SaplingGrowTreeEvent event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_54860_, p_54863_, p_54861_, this.f_153981_.get());
+ if (event.getResult().equals(net.minecraftforge.eventbus.api.Event.Result.DENY)) return false;
p_54860_.m_7471_(p_54861_, false);
- if (this.f_153981_.get().m_203334_().m_65385_(p_54860_, p_54860_.m_7726_().m_8481_(), p_54863_, p_54861_)) {
+ if (event.getFeature().m_203334_().m_65385_(p_54860_, p_54860_.m_7726_().m_8481_(), p_54863_, p_54861_)) {
return true;
} else {
p_54860_.m_7731_(p_54861_, p_54862_, 3);
@@ -1,40 +1,62 @@
--- a/net/minecraft/world/level/block/PoweredRailBlock.java
+++ b/net/minecraft/world/level/block/PoweredRailBlock.java
@@ -14,10 +_,16 @@
@@ -14,9 +_,19 @@
public class PoweredRailBlock extends BaseRailBlock {
public static final EnumProperty<RailShape> f_55214_ = BlockStateProperties.f_61404_;
public static final BooleanProperty f_55215_ = BlockStateProperties.f_61448_;
+ private final boolean isActivator; // TRUE for an Activator Rail, FALSE for Powered Rail
public PoweredRailBlock(BlockBehaviour.Properties p_55218_) {
- super(true, p_55218_);
+ this(p_55218_, false);
+ }
+
+ protected PoweredRailBlock(BlockBehaviour.Properties builder, boolean isPoweredRail) {
+ super(true, builder);
this.m_49959_(this.f_49792_.m_61090_().m_61124_(f_55214_, RailShape.NORTH_SOUTH).m_61124_(f_55215_, Boolean.valueOf(false)).m_61124_(f_152149_, Boolean.valueOf(false)));
+ protected PoweredRailBlock(BlockBehaviour.Properties p_55218_, boolean isPoweredRail) {
super(true, p_55218_);
+ this.isActivator = !isPoweredRail;
+ this.registerDefaultState();
+ }
+
+ protected void registerDefaultState() {
this.m_49959_(this.f_49792_.m_61090_().m_61124_(f_55214_, RailShape.NORTH_SOUTH).m_61124_(f_55215_, Boolean.valueOf(false)).m_61124_(f_152149_, Boolean.valueOf(false)));
}
protected boolean m_55219_(Level p_55220_, BlockPos p_55221_, BlockState p_55222_, boolean p_55223_, int p_55224_) {
@@ -99,13 +_,13 @@
@@ -28,7 +_,7 @@
int j = p_55221_.m_123342_();
int k = p_55221_.m_123343_();
boolean flag = true;
- RailShape railshape = p_55222_.m_61143_(f_55214_);
+ RailShape railshape = p_55222_.m_61143_(m_7978_());
switch(railshape) {
case NORTH_SOUTH:
if (p_55223_) {
@@ -99,14 +_,14 @@
protected boolean m_55225_(Level p_55226_, BlockPos p_55227_, boolean p_55228_, int p_55229_, RailShape p_55230_) {
BlockState blockstate = p_55226_.m_8055_(p_55227_);
- if (!blockstate.m_60713_(this)) {
+ if (!(blockstate.m_60734_() instanceof PoweredRailBlock)) {
+ if (!(blockstate.m_60734_() instanceof PoweredRailBlock other)) {
return false;
} else {
- RailShape railshape = blockstate.m_61143_(f_55214_);
+ RailShape railshape = getRailDirection(blockstate, p_55226_, p_55227_, null);
+ RailShape railshape = other.getRailDirection(blockstate, p_55226_, p_55227_, null);
if (p_55230_ != RailShape.EAST_WEST || railshape != RailShape.NORTH_SOUTH && railshape != RailShape.ASCENDING_NORTH && railshape != RailShape.ASCENDING_SOUTH) {
if (p_55230_ != RailShape.NORTH_SOUTH || railshape != RailShape.EAST_WEST && railshape != RailShape.ASCENDING_EAST && railshape != RailShape.ASCENDING_WEST) {
- if (blockstate.m_61143_(f_55215_)) {
+ if (isActivator == (((PoweredRailBlock) blockstate.m_60734_()).isActivator)) {
return p_55226_.m_46753_(p_55227_) ? true : this.m_55219_(p_55226_, p_55227_, blockstate, p_55228_, p_55229_ + 1);
- return p_55226_.m_46753_(p_55227_) ? true : this.m_55219_(p_55226_, p_55227_, blockstate, p_55228_, p_55229_ + 1);
+ if (isActivatorRail() == other.isActivatorRail()) {
+ return p_55226_.m_46753_(p_55227_) ? true : other.m_55219_(p_55226_, p_55227_, blockstate, p_55228_, p_55229_ + 1);
} else {
return false;
}
@@ -125,7 +_,7 @@
if (flag1 != flag) {
p_55233_.m_7731_(p_55234_, p_55232_.m_61124_(f_55215_, Boolean.valueOf(flag1)), 3);
p_55233_.m_46672_(p_55234_.m_7495_(), this);
- if (p_55232_.m_61143_(f_55214_).m_61745_()) {
+ if (p_55232_.m_61143_(m_7978_()).m_61745_()) {
p_55233_.m_46672_(p_55234_.m_7494_(), this);
}
}
@@ -156,6 +_,9 @@
return p_55240_.m_61124_(f_55214_, RailShape.SOUTH_EAST);
case NORTH_EAST:
@@ -45,10 +67,12 @@
}
case COUNTERCLOCKWISE_90:
switch((RailShape)p_55240_.m_61143_(f_55214_)) {
@@ -254,5 +_,9 @@
@@ -253,6 +_,10 @@
}
protected void m_7926_(StateDefinition.Builder<Block, BlockState> p_55243_) {
p_55243_.m_61104_(f_55214_, f_55215_, f_152149_);
- p_55243_.m_61104_(f_55214_, f_55215_, f_152149_);
+ p_55243_.m_61104_(m_7978_(), f_55215_, f_152149_);
+ }
+
+ public boolean isActivatorRail() {
@@ -8,11 +8,3 @@
this.m_55980_(p_56004_, p_56005_, p_56003_, p_56006_);
}
@@ -41,6 +_,7 @@
if (p_55983_.m_61143_(f_55973_) == 0) {
p_55981_.m_7731_(p_55982_, p_55983_.m_61122_(f_55973_), 4);
} else {
+ if (!net.minecraftforge.event.ForgeEventFactory.saplingGrowTree(p_55981_, p_55984_, p_55982_)) return;
this.f_55975_.m_6334_(p_55981_, p_55981_.m_7726_().m_8481_(), p_55982_, p_55983_, p_55984_);
}
@@ -1,14 +1,23 @@
--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
@@ -236,6 +_,11 @@
return this.f_59647_ == ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
@@ -212,7 +_,7 @@
}
public boolean m_7155_(int p_59663_, ItemStack p_59664_, @Nullable Direction p_59665_) {
- return !(Block.m_49814_(p_59664_.m_41720_()) instanceof ShulkerBoxBlock);
+ return !(Block.m_49814_(p_59664_.m_41720_()) instanceof ShulkerBoxBlock) && p_59664_.m_41720_().m_142095_(); // FORGE: Make shulker boxes respect Item#canFitInsideContainerItems
}
public boolean m_7157_(int p_59682_, ItemStack p_59683_, Direction p_59684_) {
@@ -234,6 +_,11 @@
public boolean m_59702_() {
return this.f_59647_ == ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
+ }
+
+ @Override
+ protected net.minecraftforge.items.IItemHandler createUnSidedHandler() {
+ return new net.minecraftforge.items.wrapper.SidedInvWrapper(this, Direction.UP);
+ }
+
}
public static enum AnimationStatus {
CLOSED,
OPENING,
@@ -0,0 +1,16 @@
--- a/net/minecraft/world/level/block/grower/AbstractMegaTreeGrower.java
+++ b/net/minecraft/world/level/block/grower/AbstractMegaTreeGrower.java
@@ -30,10 +_,11 @@
public boolean m_59990_(ServerLevel p_59991_, ChunkGenerator p_59992_, BlockPos p_59993_, BlockState p_59994_, Random p_59995_, int p_59996_, int p_59997_) {
Holder<? extends ConfiguredFeature<?, ?>> holder = this.m_203620_(p_59995_);
- if (holder == null) {
+ net.minecraftforge.event.world.SaplingGrowTreeEvent event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_59991_, p_59995_, p_59993_, holder);
+ if (event.getResult().equals(net.minecraftforge.eventbus.api.Event.Result.DENY) || holder == null) {
return false;
} else {
- ConfiguredFeature<?, ?> configuredfeature = holder.m_203334_();
+ ConfiguredFeature<?, ?> configuredfeature = event.getFeature().m_203334_();
BlockState blockstate = Blocks.f_50016_.m_49966_();
p_59991_.m_7731_(p_59993_.m_142082_(p_59996_, 0, p_59997_), blockstate, 4);
p_59991_.m_7731_(p_59993_.m_142082_(p_59996_ + 1, 0, p_59997_), blockstate, 4);
@@ -0,0 +1,16 @@
--- a/net/minecraft/world/level/block/grower/AbstractTreeGrower.java
+++ b/net/minecraft/world/level/block/grower/AbstractTreeGrower.java
@@ -18,10 +_,11 @@
public boolean m_6334_(ServerLevel p_60006_, ChunkGenerator p_60007_, BlockPos p_60008_, BlockState p_60009_, Random p_60010_) {
Holder<? extends ConfiguredFeature<?, ?>> holder = this.m_203525_(p_60010_, this.m_60011_(p_60006_, p_60008_));
- if (holder == null) {
+ net.minecraftforge.event.world.SaplingGrowTreeEvent event = net.minecraftforge.event.ForgeEventFactory.blockGrowFeature(p_60006_, p_60010_, p_60008_, holder);
+ if (event.getResult().equals(net.minecraftforge.eventbus.api.Event.Result.DENY) || holder == null) {
return false;
} else {
- ConfiguredFeature<?, ?> configuredfeature = holder.m_203334_();
+ ConfiguredFeature<?, ?> configuredfeature = event.getFeature().m_203334_();
p_60006_.m_7731_(p_60008_, Blocks.f_50016_.m_49966_(), 4);
if (configuredfeature.m_65385_(p_60006_, p_60007_, p_60010_, p_60008_)) {
return true;
@@ -89,7 +89,8 @@
}
public MaterialColor m_60780_(BlockGetter p_60781_, BlockPos p_60782_) {
return this.f_60598_;
- return this.f_60598_;
+ return m_60734_().getMapColor(this.m_7160_(), p_60781_, p_60782_, this.f_60598_);
}
+ /** @deprecated use {@link BlockState#rotate(LevelAccessor, BlockPos, Rotation)} */
@@ -93,8 +93,8 @@
m_74543_(p_74524_, compoundtag).ifPresent((p_205061_) -> {
- float f = p_205061_.m_7890_(p_74527_);
- f += p_205061_.m_6961_(p_74526_) - p_205061_.m_146908_();
+ float f = p_205061_.m_6961_(placementIn.m_74401_());
+ f = f + (p_205061_.m_146908_() - p_205061_.m_7890_(placementIn.m_74404_()));
+ float f = p_205061_.m_7890_(placementIn.m_74404_());
+ f += p_205061_.m_6961_(placementIn.m_74401_()) - p_205061_.m_146908_();
p_205061_.m_7678_(vec31.f_82479_, vec31.f_82480_, vec31.f_82481_, f, p_205061_.m_146909_());
- if (p_74530_ && p_205061_ instanceof Mob) {
+ if (placementIn.m_74414_() && p_205061_ instanceof Mob) {
@@ -9,7 +9,7 @@
+ LevelStorageSource.this.m_78229_(this.f_78271_.toFile(), (file, dataFixer) -> {
+ try {
+ CompoundTag compoundTag = NbtIo.m_128937_(file);
+ net.minecraftforge.common.ForgeHooks.readAdditionalLevelSaveData(compoundTag);
+ net.minecraftforge.common.ForgeHooks.readAdditionalLevelSaveData(compoundTag, this.f_78271_);
+ } catch (Exception e) {
+ f_78191_.error("Exception reading {}", file, e);
+ }
@@ -1,12 +0,0 @@
--- a/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceCondition.java
+++ b/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceCondition.java
@@ -18,7 +_,8 @@
}
public boolean test(LootContext p_81930_) {
- return p_81930_.m_78933_().nextFloat() < this.f_81921_;
+ int i = p_81930_.getLootingModifier();
+ return p_81930_.m_78933_().nextFloat() < this.f_81921_ + (float) i;
}
public static LootItemCondition.Builder m_81927_(float p_81928_) {
+1
View File
@@ -3,6 +3,7 @@
--add-opens java.base/java.util.jar=cpw.mods.securejarhandler
--add-exports java.base/sun.security.util=cpw.mods.securejarhandler
--add-exports jdk.naming.dns/com.sun.jndi.dns=java.naming
-Djava.net.preferIPv6Addresses=system
-DignoreList=@IGNORE_LIST@
-DlibraryDirectory=libraries
-DlegacyClassPath=@CLASS_PATH@
@@ -54,6 +54,7 @@ import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.renderer.texture.TextureAtlas;
@@ -224,11 +225,27 @@ public class ForgeHooksClient
}
}
@Deprecated(forRemoval = true, since = "1.18.2")
public static void dispatchRenderLast(LevelRenderer context, PoseStack poseStack, float partialTick, Matrix4f projectionMatrix, long finishTimeNano)
{
MinecraftForge.EVENT_BUS.post(new RenderLevelLastEvent(context, poseStack, partialTick, projectionMatrix, finishTimeNano));
}
public static void dispatchRenderStage(RenderLevelStageEvent.Stage stage, LevelRenderer levelRenderer, PoseStack poseStack, Matrix4f projectionMatrix, int renderTick, Camera camera, Frustum frustum)
{
var profiler = Minecraft.getInstance().getProfiler();
profiler.push(stage.toString());
MinecraftForge.EVENT_BUS.post(new RenderLevelStageEvent(stage, levelRenderer, poseStack, projectionMatrix, renderTick, MinecraftForgeClient.getPartialTick(), camera, frustum));
profiler.pop();
}
public static void dispatchRenderStage(RenderType renderType, LevelRenderer levelRenderer, PoseStack poseStack, Matrix4f projectionMatrix, int renderTick, Camera camera, Frustum frustum)
{
RenderLevelStageEvent.Stage stage = RenderLevelStageEvent.Stage.fromRenderType(renderType);
if (stage != null)
dispatchRenderStage(stage, levelRenderer, poseStack, projectionMatrix, renderTick, camera, frustum);
}
public static boolean renderSpecificFirstPersonHand(InteractionHand hand, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, float partialTick, float interpPitch, float swingProgress, float equipProgress, ItemStack stack)
{
return MinecraftForge.EVENT_BUS.post(new RenderHandEvent(hand, poseStack, bufferSource, packedLight, partialTick, interpPitch, swingProgress, equipProgress, stack));
@@ -10,6 +10,7 @@ import com.mojang.datafixers.util.Pair;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderStateShard.OutputStateShard;
import net.minecraft.client.renderer.RenderStateShard.TextureStateShard;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance;
@@ -20,6 +21,7 @@ import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraftforge.client.event.RegisterShadersEvent;
import net.minecraftforge.client.event.RenderLevelStageEvent;
import net.minecraftforge.common.util.NonNullLazy;
import net.minecraftforge.common.util.NonNullSupplier;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -42,7 +44,8 @@ public enum ForgeRenderTypes
ITEM_LAYERED_TRANSLUCENT(()-> getItemLayeredTranslucent(TextureAtlas.LOCATION_BLOCKS)),
ITEM_UNSORTED_TRANSLUCENT(()-> getUnsortedTranslucent(TextureAtlas.LOCATION_BLOCKS)),
ITEM_UNLIT_TRANSLUCENT(()-> getUnlitTranslucent(TextureAtlas.LOCATION_BLOCKS)),
ITEM_UNSORTED_UNLIT_TRANSLUCENT(()-> getUnlitTranslucent(TextureAtlas.LOCATION_BLOCKS, false));
ITEM_UNSORTED_UNLIT_TRANSLUCENT(()-> getUnlitTranslucent(TextureAtlas.LOCATION_BLOCKS, false)),
TRANSLUCENT_ON_PARTICLES_TARGET(() -> getTranslucentParticlesTarget(TextureAtlas.LOCATION_BLOCKS));
public static boolean enableTextTextureLinearFiltering = false;
@@ -161,6 +164,14 @@ public enum ForgeRenderTypes
return Internal.TEXT_INTENSITY_SEETHROUGH.apply(locationIn);
}
/**
* @return A variation of {@link RenderType#translucent()} that uses {@link OutputStateShard#PARTICLES_TARGET} to allow fabulous transparency sorting when using {@link RenderLevelStageEvent}
*/
public static RenderType getTranslucentParticlesTarget(ResourceLocation locationIn)
{
return Internal.TRANSLUCENT_PARTICLES_TARGET.apply(locationIn);
}
// ----------------------------------------
// Implementation details below this line
// ----------------------------------------
@@ -350,6 +361,18 @@ public enum ForgeRenderTypes
.createCompositeState(false);
return create("forge_text_see_through", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state);
}
public static Function<ResourceLocation, RenderType> TRANSLUCENT_PARTICLES_TARGET = Util.memoize(Internal::getTranslucentParticlesTarget);
private static RenderType getTranslucentParticlesTarget(ResourceLocation locationIn) {
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_TRANSLUCENT_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, false, true))
.setTransparencyState(TRANSLUCENT_TRANSPARENCY)
.setLightmapState(LIGHTMAP)
.setOutputState(PARTICLES_TARGET)
.createCompositeState(true);
return create("forge_translucent_particles_target", DefaultVertexFormat.BLOCK, VertexFormat.Mode.QUADS, 2097152, true, true, rendertype$state);
}
}
private static class CustomizableTextureState extends TextureStateShard
@@ -11,6 +11,7 @@ import net.minecraft.client.RecipeBookCategories;
import net.minecraft.world.inventory.RecipeBookType;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeType;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import java.util.*;
@@ -20,8 +21,7 @@ import static net.minecraft.client.RecipeBookCategories.*;
public class RecipeBookRegistry
{
// The ImmutableMap is the patched out value of AGGREGATE_CATEGORIES
private static final Map<RecipeBookCategories, List<RecipeBookCategories>> MUTABLE_AGGREGATE_CATEGORIES = new HashMap<>(ImmutableMap.of(CRAFTING_SEARCH, ImmutableList.of(CRAFTING_EQUIPMENT, CRAFTING_BUILDING_BLOCKS, CRAFTING_MISC, CRAFTING_REDSTONE), FURNACE_SEARCH, ImmutableList.of(FURNACE_FOOD, FURNACE_BLOCKS, FURNACE_MISC), BLAST_FURNACE_SEARCH, ImmutableList.of(BLAST_FURNACE_BLOCKS, BLAST_FURNACE_MISC), SMOKER_SEARCH, ImmutableList.of(SMOKER_FOOD)));
private static final Map<RecipeBookCategories, List<RecipeBookCategories>> MUTABLE_AGGREGATE_CATEGORIES = new HashMap<>();
private static final Map<RecipeBookType, List<RecipeBookCategories>> TYPE_TO_CATEGORIES = new HashMap<>();
private static final Map<RecipeType<?>, Function<Recipe<?>, RecipeBookCategories>> FIND_CATEGORIES_FOR_TYPE = new HashMap<>();
@@ -50,4 +50,16 @@ public class RecipeBookRegistry
{
return Optional.ofNullable(FIND_CATEGORIES_FOR_TYPE.get(type)).map(f -> f.apply(recipe)).orElse(null);
}
@ApiStatus.Internal
public static void initDefaultValues()
{
// The ImmutableMap is the patched out value of AGGREGATE_CATEGORIES
MUTABLE_AGGREGATE_CATEGORIES.putAll(ImmutableMap.of(
CRAFTING_SEARCH, ImmutableList.of(CRAFTING_EQUIPMENT, CRAFTING_BUILDING_BLOCKS, CRAFTING_MISC, CRAFTING_REDSTONE),
FURNACE_SEARCH, ImmutableList.of(FURNACE_FOOD, FURNACE_BLOCKS, FURNACE_MISC),
BLAST_FURNACE_SEARCH, ImmutableList.of(BLAST_FURNACE_BLOCKS, BLAST_FURNACE_MISC),
SMOKER_SEARCH, ImmutableList.of(SMOKER_FOOD)
));
}
}
@@ -10,6 +10,11 @@ import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.LevelRenderer;
import com.mojang.math.Matrix4f;
/**
* @deprecated Use {@link RenderLevelStageEvent} instead for more flexibility and improved compatibility with translucent objects.
* There is no {@link RenderLevelStageEvent.Stage} that directly replaces this event, instead you must decide which Stage best fits your use case.
*/
@Deprecated(forRemoval = true, since = "1.18.2")
public class RenderLevelLastEvent extends net.minecraftforge.eventbus.api.Event
{
private final LevelRenderer levelRenderer;
@@ -0,0 +1,209 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.event;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.ForgeRenderTypes;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
/**
* Fires at various times during LevelRenderer.renderLevel.
* Check {@link #getStage} to render during the appropriate time for your use case.
*/
public class RenderLevelStageEvent extends Event
{
private final Stage stage;
private final LevelRenderer levelRenderer;
private final PoseStack poseStack;
private final Matrix4f projectionMatrix;
private final int renderTick;
private final float partialTick;
private final Camera camera;
private final Frustum frustum;
public RenderLevelStageEvent(Stage stage, LevelRenderer levelRenderer, PoseStack poseStack, Matrix4f projectionMatrix, int renderTick, float partialTick, Camera camera, Frustum frustum)
{
this.stage = stage;
this.levelRenderer = levelRenderer;
this.poseStack = poseStack;
this.projectionMatrix = projectionMatrix;
this.renderTick = renderTick;
this.partialTick = partialTick;
this.camera = camera;
this.frustum = frustum;
}
/**
* {@return the current {@link Stage} that is being rendered. Check this before doing rendering to ensure
* that rendering happens at the appropriate time.}
*/
public Stage getStage()
{
return stage;
}
public LevelRenderer getLevelRenderer()
{
return levelRenderer;
}
public PoseStack getPoseStack()
{
return poseStack;
}
public Matrix4f getProjectionMatrix()
{
return projectionMatrix;
}
/**
* {@return the current "ticks" value in {@link LevelRenderer}}
*/
public int getRenderTick()
{
return renderTick;
}
/**
* {@return the current partialTick value at the start of LevelRenderer.renderChunkLayer}
*/
public float getPartialTick()
{
return partialTick;
}
public Camera getCamera()
{
return camera;
}
public Frustum getFrustum()
{
return frustum;
}
/**
* Use to create a custom {@link RenderLevelStageEvent.Stage}s.
* Fired after the LevelRenderer has been created.
*
* Fired on the Mod bus {@link IModBusEvent}
*/
public static class RegisterStageEvent extends Event implements IModBusEvent
{
/**
* @param name The name of your Stage.
* @param renderType
* If not null, called automatically by LevelRenderer.renderChunkLayer if the RenderType passed into it matches this one.
* If null, needs to be called manually by whoever implements it.
*
* @throws IllegalArgumentException if the RenderType passed is already mapped to a Stage.
*/
public Stage register(ResourceLocation name, @Nullable RenderType renderType) throws IllegalArgumentException
{
return Stage.register(name, renderType);
}
}
/**
* A time during level rendering for you to render custom things into the world.
* @see RegisterStageEvent
*/
public static class Stage
{
private static final Map<RenderType, Stage> RENDER_TYPE_STAGES = new HashMap<>();
/**
* Use this to render custom objects into the skybox.
* Called regardless of if they sky actually renders or not.
*/
public static final Stage AFTER_SKY = register("after_sky", null);
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_SOLID_BLOCKS = register("after_solid_blocks", RenderType.solid());
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_CUTOUT_MIPPED_BLOCKS_BLOCKS = register("after_cutout_mipped_blocks", RenderType.cutoutMipped());
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_CUTOUT_BLOCKS = register("after_cutout_blocks", RenderType.cutout());
/**
* Use this to render custom block-like geometry into the world.
* Due to how transparency sorting works, this stage may not work properly with translucency. If you intend to render translucency,
* try using {@link #AFTER_TRIPWIRE_BLOCKS} or {@link #AFTER_PARTICLES}.
* Although this is called within a fabulous graphics target, it does not function properly in many cases.
*/
public static final Stage AFTER_TRANSLUCENT_BLOCKS = register("after_translucent_blocks", RenderType.translucent());
/**
* Use this to render custom block-like geometry into the world.
*/
public static final Stage AFTER_TRIPWIRE_BLOCKS = register("after_tripwire_blocks", RenderType.tripwire());
/**
* Use this to render custom effects into the world, such as custom entity-like objects or special rendering effects.
* Called within a fabulous graphics target.
* Happens after entities render.
*
* @see ForgeRenderTypes#TRANSLUCENT_ON_PARTICLES_TARGET
*/
public static final Stage AFTER_PARTICLES = register("after_particles", null);
/**
* Use this to render custom weather effects into the world.
* Called within a fabulous graphics target.
*/
public static final Stage AFTER_WEATHER = register("after_weather", null);
private final String name;
private Stage(String name)
{
this.name = name;
}
private static Stage register(ResourceLocation name, @Nullable RenderType renderType) throws IllegalArgumentException
{
Stage stage = new Stage(name.toString());
if (renderType != null && RENDER_TYPE_STAGES.putIfAbsent(renderType, stage) != null)
throw new IllegalArgumentException("Attempted to replace an existing RenderLevelStageEvent.Stage for a RenderType: Stage = " + stage + ", RenderType = " + renderType);
return stage;
}
private static Stage register(String name, @Nullable RenderType renderType) throws IllegalArgumentException
{
return register(new ResourceLocation(name), renderType);
}
@Override
public String toString()
{
return this.name;
}
/**
* {@return the Stage bound to the RenderType, or null if no value is present}
*/
@Nullable
public static Stage fromRenderType(RenderType renderType)
{
return RENDER_TYPE_STAGES.get(renderType);
}
}
}
@@ -5,13 +5,14 @@
package net.minecraftforge.client.extensions;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.block.model.BakedQuad;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import net.minecraftforge.client.model.pipeline.LightUtil;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryStack;
import java.nio.Buffer;
@@ -26,6 +27,13 @@ import net.minecraft.core.Vec3i;
public interface IForgeVertexConsumer
{
// TODO 1.19: Make not default and not nullable
@Nullable
default VertexFormat getVertexFormat()
{
return null;
}
// Copy of putBulkData, but enables tinting and per-vertex alpha
default void putBulkData(PoseStack.Pose poseStack, BakedQuad bakedQuad, float red, float green, float blue, int packedLight, int packedOverlay, boolean readExistingColor) {
putBulkData(poseStack, bakedQuad, red, green, blue, 1.0f, packedLight, packedOverlay, readExistingColor);
@@ -96,7 +96,11 @@ public class LoadingErrorScreen extends ErrorScreen {
}
public static class LoadingEntryList extends ObjectSelectionList<LoadingEntryList.LoadingMessageEntry> {
LoadingEntryList(final LoadingErrorScreen parent, final List<ModLoadingException> errors, final List<ModLoadingWarning> warnings) {
super(parent.minecraft, parent.width, parent.height, 35, parent.height - 50, 2 * parent.minecraft.font.lineHeight + 8);
super(Objects.requireNonNull(parent.minecraft), parent.width, parent.height, 35, parent.height - 50,
Math.max(
errors.stream().mapToInt(error -> parent.font.split(new TranslatableComponent(error.getMessage() != null ? error.getMessage() : ""), parent.width - 20).size()).max().orElse(0),
warnings.stream().mapToInt(warning -> parent.font.split(new TranslatableComponent(warning.formatToString() != null ? warning.formatToString() : ""), parent.width - 20).size()).max().orElse(0)
) * parent.minecraft.font.lineHeight + 8);
boolean both = !errors.isEmpty() && !warnings.isEmpty();
if (both)
addEntry(new LoadingMessageEntry(parent.errorHeader, true));
@@ -142,13 +146,14 @@ public class LoadingErrorScreen extends ErrorScreen {
@Override
public void render(PoseStack poseStack, int entryIdx, int top, int left, final int entryWidth, final int entryHeight, final int mouseX, final int mouseY, final boolean p_194999_5_, final float partialTick) {
Font font = Minecraft.getInstance().font;
final List<FormattedCharSequence> strings = font.split(message, LoadingEntryList.this.width);
final List<FormattedCharSequence> strings = font.split(message, LoadingEntryList.this.width - 20);
int y = top + 2;
for (int i = 0; i < Math.min(strings.size(), 2); i++) {
for (FormattedCharSequence string : strings)
{
if (center)
font.draw(poseStack, strings.get(i), left + (width) - font.width(strings.get(i)) / 2F, y, 0xFFFFFF);
font.draw(poseStack, string, left + (width) - font.width(string) / 2F, y, 0xFFFFFF);
else
font.draw(poseStack, strings.get(i), left + 5, y, 0xFFFFFF);
font.draw(poseStack, string, left + 5, y, 0xFFFFFF);
y += font.lineHeight;
}
}
@@ -1,20 +1,6 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2022.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.gui;
@@ -43,6 +43,7 @@ public class ForgeBlockModelRenderer extends ModelBlockRenderer
{
VertexBufferConsumer consumer = consumerFlat.get();
consumer.setBuffer(buffer);
consumer.setPackedOverlay(packedOverlay);
VertexLighterFlat lighter = lighterFlat.get();
lighter.setParent(consumer);
@@ -63,6 +64,7 @@ public class ForgeBlockModelRenderer extends ModelBlockRenderer
{
VertexBufferConsumer consumer = consumerSmooth.get();
consumer.setBuffer(buffer);
consumer.setPackedOverlay(packedOverlay);
VertexLighterSmoothAo lighter = lighterSmooth.get();
lighter.setParent(consumer);
@@ -7,19 +7,31 @@ package net.minecraftforge.client.model.pipeline;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.blaze3d.vertex.VertexFormatElement;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.core.Direction;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Assumes VertexFormatElement is present in the BufferBuilder's vertex format.
*/
public class VertexBufferConsumer implements IVertexConsumer
{
private VertexFormat format;
private List<VertexFormatElement> elements;
private VertexConsumer renderer;
private boolean overrideOverlayCoords;
private int overlayCoordU;
private int overlayCoordV;
public VertexBufferConsumer() {}
public VertexBufferConsumer()
{
setVertexFormat(DefaultVertexFormat.BLOCK);
}
public VertexBufferConsumer(VertexConsumer buffer)
{
@@ -29,7 +41,7 @@ public class VertexBufferConsumer implements IVertexConsumer
@Override
public final VertexFormat getVertexFormat()
{
return DefaultVertexFormat.BLOCK;
return format;
}
@Override
@@ -40,24 +52,36 @@ public class VertexBufferConsumer implements IVertexConsumer
final float d2 = data.length <= 2 ? 0 : data[2];
final float d3 = data.length <= 3 ? 0 : data[3];
switch (e)
var element = elements.get(e);
switch (element.getUsage())
{
case 0: // POSITION_3F
renderer.vertex(d0, d1, d2);
case POSITION: // POSITION_3F
if (element.getIndex() == 0)
renderer.vertex(d0, d1, d2);
break;
case 4: // NORMAL_3B
renderer.normal(d0, d1, d2);
case NORMAL: // NORMAL_3B
if (element.getIndex() == 0)
renderer.normal(d0, d1, d2);
break;
case 1: // COLOR_4UB
renderer.color(d0, d1, d2, d3);
case COLOR: // COLOR_4UB
if (element.getIndex() == 0)
renderer.color(d0, d1, d2, d3);
break;
case 2: // TEX_2F
renderer.uv(d0, d1);
case UV: // TEX_2F
switch(element.getIndex())
{
case 0 -> renderer.uv(d0, d1);
case 1 -> {
if (overrideOverlayCoords)
renderer.overlayCoords(overlayCoordU, overlayCoordV);
else
renderer.overlayCoords((int) (d0 * 32767f), (int) (d1 * 32767f));
}
case 2 -> renderer.uv2((int) (d0 * 0xF0), (int) (d1 * 0xF0));
}
break;
case 3: // TEX_2SB
renderer.uv2((int) (d0 * 0xF0), (int) (d1 * 0xF0));
break;
case 5: // PADDING_1B
case PADDING:
break;
default:
throw new IllegalArgumentException("Vertex element out of bounds: " + e);
@@ -71,6 +95,13 @@ public class VertexBufferConsumer implements IVertexConsumer
public void setBuffer(VertexConsumer buffer)
{
this.renderer = buffer;
setVertexFormat(buffer.getVertexFormat());
}
public void setVertexFormat(@Nullable VertexFormat format)
{
this.format = format != null ? format : DefaultVertexFormat.BLOCK;
this.elements = this.format.getElements();
}
@Override
@@ -81,4 +112,11 @@ public class VertexBufferConsumer implements IVertexConsumer
public void setApplyDiffuseLighting(boolean diffuse) {}
@Override
public void setTexture(TextureAtlasSprite texture ) {}
public void setPackedOverlay(int packedOverlay)
{
this.overrideOverlayCoords = true;
this.overlayCoordV = (packedOverlay >> 16) & 0xFFFF;
this.overlayCoordU = (packedOverlay) & 0xFFFF;
}
}
@@ -19,8 +19,8 @@ import java.util.function.Function;
*/
public class UnitSprite extends TextureAtlasSprite
{
public static final UnitSprite INSTANCE = new UnitSprite();
public static final ResourceLocation LOCATION = new ResourceLocation("forge", "unit");
public static final UnitSprite INSTANCE = new UnitSprite();
public static final Function<Material, TextureAtlasSprite> GETTER = (x) -> INSTANCE;
private UnitSprite()
@@ -128,6 +128,8 @@ public class ForgeConfig {
public final BooleanValue forceSystemNanoTime;
public final BooleanValue compressLanIPv6Addresses;
Client(ForgeConfigSpec.Builder builder) {
builder.comment("Client only settings, mostly things related to rendering")
.push("client");
@@ -159,6 +161,11 @@ public class ForgeConfig {
.translation("forge.configgui.forceSystemNanoTime")
.define("forceSystemNanoTime", false);
compressLanIPv6Addresses = builder
.comment("When enabled, Forge will convert discovered 'Open to LAN' IPv6 addresses to their more compact, compressed representation")
.translation("forge.configgui.compressLanIPv6Addresses")
.define("compressLanIPv6Addresses", true);
builder.pop();
}
}
@@ -810,7 +810,7 @@ public class ForgeConfigSpec extends UnmodifiableConfigWrapper<UnmodifiableConfi
}
}
public static class ConfigValue<T>
public static class ConfigValue<T> implements Supplier<T>
{
private static boolean USE_CACHES = true;
@@ -835,6 +835,7 @@ public class ForgeConfigSpec extends UnmodifiableConfigWrapper<UnmodifiableConfi
return Lists.newArrayList(path);
}
@Override
public T get()
{
Preconditions.checkNotNull(spec, "Cannot get config value before spec is built");
@@ -7,6 +7,7 @@ package net.minecraftforge.common;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -49,17 +50,20 @@ import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.ResourceLocationException;
import net.minecraft.advancements.Advancement;
import net.minecraft.core.*;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.PackType;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.datafix.fixes.StructuresBecomeConfiguredFix;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.DefaultAttributes;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.inventory.ContainerLevelAccess;
import net.minecraft.world.item.*;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeType;
@@ -127,7 +131,9 @@ import net.minecraftforge.common.world.MobSpawnSettingsBuilder;
import net.minecraftforge.event.AnvilUpdateEvent;
import net.minecraftforge.event.DifficultyChangeEvent;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.GrindstoneEvent;
import net.minecraftforge.event.ItemAttributeModifierEvent;
import net.minecraftforge.event.ModMismatchEvent;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.RegisterStructureConversionsEvent;
import net.minecraftforge.event.VanillaGameEvent;
@@ -138,6 +144,8 @@ import net.minecraftforge.event.entity.EntityTravelToDimensionEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.EnderManAngerEvent;
import net.minecraftforge.event.entity.living.LivingAttackEvent;
import net.minecraftforge.event.entity.living.LivingChangeTargetEvent;
import net.minecraftforge.event.entity.living.LivingChangeTargetEvent.ILivingTargetType;
import net.minecraftforge.event.entity.living.LivingDamageEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
@@ -162,7 +170,6 @@ import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.NoteBlockEvent;
import net.minecraftforge.eventbus.api.Event.Result;
import net.minecraftforge.fluids.FluidAttributes;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.resource.ResourcePackLoader;
@@ -195,6 +202,9 @@ import net.minecraft.world.level.material.EmptyFluid;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.LavaFluid;
import net.minecraft.world.level.material.WaterFluid;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.jetbrains.annotations.ApiStatus;
public class ForgeHooks
{
@@ -287,11 +297,28 @@ public class ForgeHooks
//Optifine Helper Functions u.u, these are here specifically for Optifine
//Note: When using Optifine, these methods are invoked using reflection, which
//incurs a major performance penalty.
// TODO: Remove in 1.20
@Deprecated(since = "1.19.2", forRemoval = true)
public static void onLivingSetAttackTarget(LivingEntity entity, LivingEntity target)
{
MinecraftForge.EVENT_BUS.post(new LivingSetAttackTargetEvent(entity, target));
}
// TODO: Remove in 1.20
@Deprecated(since = "1.19.2", forRemoval = true)
public static void onLivingSetAttackTarget(LivingEntity entity, LivingEntity target, ILivingTargetType targetType)
{
MinecraftForge.EVENT_BUS.post(new LivingSetAttackTargetEvent(entity, target, targetType));
}
public static LivingChangeTargetEvent onLivingChangeTarget(LivingEntity entity, LivingEntity originalTarget, ILivingTargetType targetType)
{
LivingChangeTargetEvent event = new LivingChangeTargetEvent(entity, originalTarget, targetType);
MinecraftForge.EVENT_BUS.post(event);
return event;
}
public static boolean onLivingUpdate(LivingEntity entity)
{
return MinecraftForge.EVENT_BUS.post(new LivingUpdateEvent(entity));
@@ -428,15 +455,23 @@ public class ForgeHooks
return !MinecraftForge.EVENT_BUS.post(new VanillaGameEvent(level, cause, vanillaEvent, position));
}
@Deprecated
@Nullable
public static Component onServerChatEvent(ServerGamePacketListenerImpl net, String raw, Component comp)
{
ServerChatEvent event = new ServerChatEvent(net.player, raw, comp);
var event = onServerChatEvent(net, raw, comp, null, null);
return event == null ? null : event.getComponent();
}
@Nullable
public static ServerChatEvent onServerChatEvent(ServerGamePacketListenerImpl net, String raw, Component comp, @Nullable String filtered, @Nullable Component filteredComp)
{
ServerChatEvent event = new ServerChatEvent(net.player, raw, comp, filtered, filteredComp);
if (MinecraftForge.EVENT_BUS.post(event))
{
return null;
}
return event.getComponent();
return event;
}
@@ -679,6 +714,41 @@ public class ForgeHooks
return e.getBreakChance();
}
public static int onGrindstoneChange(@Nonnull ItemStack top, @Nonnull ItemStack bottom, Container outputSlot, int xp)
{
GrindstoneEvent.OnPlaceItem e = new GrindstoneEvent.OnPlaceItem(top, bottom, xp);
if (MinecraftForge.EVENT_BUS.post(e))
{
outputSlot.setItem(0, ItemStack.EMPTY);
return -1;
}
if (e.getOutput().isEmpty()) return Integer.MIN_VALUE;
outputSlot.setItem(0, e.getOutput());
return e.getXp();
}
public static boolean onGrindstoneTake(Container inputSlots, ContainerLevelAccess access, Function<Level, Integer> xpFunction)
{
access.execute((l,p) -> {
int xp = xpFunction.apply(l);
GrindstoneEvent.OnTakeItem e = new GrindstoneEvent.OnTakeItem(inputSlots.getItem(0), inputSlots.getItem(1), xp);
if (MinecraftForge.EVENT_BUS.post(e))
{
return;
}
if (l instanceof ServerLevel serverLevel)
{
ExperienceOrb.award(serverLevel, Vec3.atCenterOf(p), e.getXp());
}
l.levelEvent(1042, p, 0);
inputSlots.setItem(0, e.getNewTopItem());
inputSlots.setItem(1, e.getNewBottomItem());
inputSlots.setChanged();
});
return true;
}
private static ThreadLocal<Player> craftingPlayer = new ThreadLocal<Player>();
public static void setCraftingPlayer(Player player)
{
@@ -1378,13 +1448,30 @@ public class ForgeHooks
LOGGER.debug(WORLDPERSISTENCE, "ID Map collection complete {}", worldData.getLevelName());
levelTag.put("fml", fmlData);
}
/**
* @deprecated To be removed in 1.20.
* Use {@link #readAdditionalLevelSaveData(CompoundTag, Path)} instead.
*/
@Deprecated(forRemoval = true, since = "1.19.2")
public static void readAdditionalLevelSaveData(CompoundTag rootTag)
{
readAdditionalLevelSaveData(rootTag, null);
}
/**
* @param rootTag Level data file contents.
* @param levelPath Level currently being loaded. TODO 1.20 - Remove nullable annotation
*/
@ApiStatus.Internal
public static void readAdditionalLevelSaveData(CompoundTag rootTag, @Nullable Path levelPath)
{
CompoundTag tag = rootTag.getCompound("fml");
if (tag.contains("LoadingModList"))
{
ListTag modList = tag.getList("LoadingModList", net.minecraft.nbt.Tag.TAG_COMPOUND);
Map<String, ArtifactVersion> mismatchedVersions = new HashMap<>(modList.size());
Map<String, ArtifactVersion> missingVersions = new HashMap<>(modList.size());
for (int i = 0; i < modList.size(); i++)
{
CompoundTag mod = modList.getCompound(i);
@@ -1394,16 +1481,70 @@ public class ForgeHooks
continue;
}
String modVersion = mod.getString("ModVersion");
Optional<? extends ModContainer> container = ModList.get().getModContainerById(modId);
if (container.isEmpty())
final var previousVersion = new DefaultArtifactVersion(modVersion);
ModList.get().getModContainerById(modId).ifPresentOrElse(container ->
{
LOGGER.error(WORLDPERSISTENCE,"This world was saved with mod {} which appears to be missing, things may not work well", modId);
continue;
}
if (!Objects.equals(modVersion, MavenVersionStringHelper.artifactVersionToString(container.get().getModInfo().getVersion())))
final var loadingVersion = container.getModInfo().getVersion();
if (!loadingVersion.equals(previousVersion))
{
// Enqueue mismatched versions for bulk event
mismatchedVersions.put(modId, previousVersion);
}
}, () -> missingVersions.put(modId, previousVersion));
}
final var mismatchEvent = new ModMismatchEvent(levelPath, mismatchedVersions, missingVersions);
ModLoader.get().postEvent(mismatchEvent);
StringBuilder resolved = new StringBuilder("The following mods have version differences that were marked resolved:");
StringBuilder unresolved = new StringBuilder("The following mods have version differences that were not resolved:");
// For mods that were marked resolved, log the version resolution and the mod that resolved the mismatch
mismatchEvent.getResolved().forEachOrdered((res) ->
{
final var modid = res.modid();
final var diff = res.versionDifference();
if (res.wasSelfResolved())
{
LOGGER.warn(WORLDPERSISTENCE,"This world was saved with mod {} version {} and it is now at version {}, things may not work well", modId, modVersion, MavenVersionStringHelper.artifactVersionToString(container.get().getModInfo().getVersion()));
resolved.append(System.lineSeparator())
.append(diff.isMissing()
? "%s (version %s -> MISSING, self-resolved)".formatted(modid, diff.oldVersion())
: "%s (version %s -> %s, self-resolved)".formatted(modid, diff.oldVersion(), diff.newVersion())
);
}
else
{
final var resolver = res.resolver().getModId();
resolved.append(System.lineSeparator())
.append(diff.isMissing()
? "%s (version %s -> MISSING, resolved by %s)".formatted(modid, diff.oldVersion(), resolver)
: "%s (version %s -> %s, resolved by %s)".formatted(modid, diff.oldVersion(), diff.newVersion(), resolver)
);
}
});
// For mods that did not specify handling, show a warning to users that errors may occur
mismatchEvent.getUnresolved().forEachOrdered((unres) ->
{
final var modid = unres.modid();
final var diff = unres.versionDifference();
unresolved.append(System.lineSeparator())
.append(diff.isMissing()
? "%s (version %s -> MISSING)".formatted(modid, diff.oldVersion())
: "%s (version %s -> %s)".formatted(modid, diff.oldVersion(), diff.newVersion())
);
});
if (mismatchEvent.anyResolved())
{
resolved.append(System.lineSeparator()).append("Things may not work well.");
LOGGER.debug(WORLDPERSISTENCE, resolved.toString());
}
if (mismatchEvent.anyUnresolved())
{
unresolved.append(System.lineSeparator()).append("Things may not work well.");
LOGGER.warn(WORLDPERSISTENCE, unresolved.toString());
}
}
@@ -1484,7 +1625,7 @@ public class ForgeHooks
return fallback;
}
}
private static BannerPattern[] nonPatternItems;
private static int totalPatternRows;
@@ -1514,7 +1655,7 @@ public class ForgeHooks
{
return nonPatternItems[index].ordinal();
}
public static boolean shouldSuppressEnderManAnger(EnderMan enderMan, Player player, ItemStack mask)
{
return mask.isEnderMask(player, enderMan) || MinecraftForge.EVENT_BUS.post(new EnderManAngerEvent(enderMan, player));
@@ -1,5 +1,5 @@
/*
* Minecraft Forge - Forge Development LLC
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
@@ -24,7 +24,9 @@ import net.minecraft.world.entity.projectile.WitherSkull;
import net.minecraft.world.item.*;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.MaterialColor;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.tags.BlockTags;
import net.minecraft.core.Direction;
@@ -832,4 +834,30 @@ public interface IForgeBlock
}
return true;
}
/**
* Called after the {@link BlockState} at the given {@link BlockPos} was changed and neighbors were updated.
* This method is called on the server and client side.
* Modifying the level is disallowed in this method.
* Useful for calculating additional data based on the new state and the neighbor's reactions to the state change.
*
* @param level The level the state was modified in
* @param pos The blocks position in the level
* @param oldState The previous state of the block at the given position, may be a different block than this one
* @param newState The new state of the block at the given position
*/
default void onBlockStateChange(LevelReader level, BlockPos pos, BlockState oldState, BlockState newState) { }
/**
* Returns the {@link MaterialColor} shown on the map.
*
* @param state The state of this block
* @param level The level this block is in
* @param pos The blocks position in the level
* @param defaultColor The {@code MaterialColor} configured for the given {@code BlockState} in the {@link BlockBehaviour.Properties}
*/
default MaterialColor getMapColor(BlockState state, BlockGetter level, BlockPos pos, MaterialColor defaultColor)
{
return defaultColor;
}
}
@@ -696,4 +696,19 @@ public interface IForgeBlockState
{
return self().getBlock().supportsExternalFaceHiding(self());
}
/**
* Called after the {@link BlockState} at the given {@link BlockPos} was changed and neighbors were updated.
* This method is called on the server and client side.
* Modifying the level is disallowed in this method.
* Useful for calculating additional data based on the new state and the neighbor's reactions to the state change.
*
* @param level The level the state was modified in
* @param pos The blocks position in the level
* @param oldState The previous state of the block at the given position, may be a different block than this one
*/
default void onBlockStateChange(LevelReader level, BlockPos pos, BlockState oldState)
{
self().getBlock().onBlockStateChange(level, pos, oldState, self());
}
}
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff. Show more