mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa170facb7 | ||
|
|
9919e5c1d5 | ||
|
|
84a8997263 | ||
|
|
f7c4320b7f | ||
|
|
84b1a40fe2 | ||
|
|
03b75632d8 | ||
|
|
2c3cb2025e | ||
|
|
0ca10b5569 | ||
|
|
1087e265aa | ||
|
|
d3ca95aee2 | ||
|
|
c5f3f34c5a | ||
|
|
9ef5d545f8 | ||
|
|
1b0ffa2633 | ||
|
|
763523d1c0 | ||
|
|
24d3705e19 | ||
|
|
40bce4b498 | ||
|
|
845c5958e0 | ||
|
|
6faa5667d6 | ||
|
|
8ab5cbca26 | ||
|
|
6ff02a6ba9 | ||
|
|
4db9bc68d9 | ||
|
|
84d354a7dd | ||
|
|
4df024ef43 | ||
|
|
49c927a505 | ||
|
|
73a937e088 | ||
|
|
65e8de880d | ||
|
|
1dacb882e0 | ||
|
|
22a51250b8 | ||
|
|
d264674a85 | ||
|
|
af9b4c1fe5 | ||
|
|
5a6baccee5 | ||
|
|
6443adcb6f | ||
|
|
fc649f4e06 | ||
|
|
2886f3ca34 | ||
|
|
1af16d70cc | ||
|
|
87abbdb8ef | ||
|
|
1b25285462 | ||
|
|
0f4e3146e0 | ||
|
|
b46f7c0d85 | ||
|
|
28d71761cd | ||
|
|
dbf1c8f10f | ||
|
|
f7dd6396e4 | ||
|
|
1a46f205d6 | ||
|
|
be0facc2aa | ||
|
|
67d4a0163b | ||
|
|
0cc85bc314 |
No files matched your search
Vendored
+73
-1
@@ -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
@@ -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}'),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
-39
@@ -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()
|
||||
}
|
||||
+57
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -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) {}
|
||||
|
||||
}
|
||||
+50
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-23
@@ -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();
|
||||
}
|
||||
+30
@@ -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();
|
||||
}
|
||||
+46
-22
@@ -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() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-23
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
@@ -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) {}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+106
-26
@@ -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");
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -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);
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
net.minecraftforge.fml.loading.ModDirTransformerDiscoverer
|
||||
net.minecraftforge.fml.loading.ModDirTransformerDiscoverer
|
||||
net.minecraftforge.fml.loading.ClasspathTransformerDiscoverer
|
||||
+1
@@ -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
@@ -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;
|
||||
|
||||
+6
-6
@@ -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_);
|
||||
}
|
||||
|
||||
+15
-6
@@ -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,
|
||||
+16
@@ -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);
|
||||
+16
@@ -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)} */
|
||||
|
||||
+2
-2
@@ -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);
|
||||
+ }
|
||||
|
||||
-12
@@ -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_) {
|
||||
@@ -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
Reference in new issue
Block a user