Compare commits

..
12 Commits
Author SHA1 Message Date
LexManos 3b981f4725 1.21.11 RB 2 2026-08-05 11:13:16 -07:00
Martijn van den Brand bca7eff231 Fix IForgeBlockStateModel.getModelData not being called correct for #getParticleIcon (#10818) 2026-08-05 10:46:40 -07:00
LexManos 5df71fb5df Fix reading ping data for legacy Forge versions. Closes #10778 2026-08-05 10:07:49 -07:00
LexManos 5e6d92a099 Fix ModList screen selection getting out of sync. Closes #10855 2026-07-27 14:50:15 -07:00
LexManos a171012411 Bump Dependencies, Notably ASM for Java 27 support 2026-07-27 14:46:02 -07:00
Matt 44b30daa05 Split hotbar rendering into components, allow replacing renderers (#10868) 2026-07-26 10:20:39 -07:00
Matt 2d33dba36e Add DeltaTracker (partialTick) context to PassDefinitions (#10849) 2026-07-21 11:35:51 -07:00
Meme Man c538816698 Update equality check for RegistryObject to include RegistryObject#key. (#10858) 2026-07-21 10:51:17 -07:00
Matt 4f936fb5f7 Toml parsing errors are now visible in the latest log. (#10837) 2026-07-09 12:11:03 -07:00
LexManos 66e4dab83b Bump MDK gradle and fix missing and wrong dependency in installer 2026-05-27 09:07:31 -07:00
LexManos 78d8d5495c Fix versioning 2026-05-27 07:09:39 -07:00
e61aa67600 ForgeDev 7 (#10747)
Major re-write of the build tools for creating Forge.
This is equivalent to the ForgeGradle 7 migration for the user side.

Co-authored-by: Jonathing <[email protected]>
Co-authored-by: Paint_Ninja <[email protected]>
2026-05-27 06:49:54 -07:00
1797 changed files with 21756 additions and 16857 deletions

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ name: Publish
on:
push:
branches: [ '26.2' ]
branches: [ '1.21.11' ]
permissions:
contents: read
-2
View File
@@ -38,7 +38,6 @@ src/minecraft
/projects/**/run/
/projects/**/*.launch
/repo/
/rejects-*/
src/*/generated/**/.cache/
# Generated by gradle every import
@@ -52,7 +51,6 @@ src/*/generated/**/.cache/
/*/.gitignore
/*/.factorypath
/*/.apt_generated/
/fmlcore/logs/
/lib/
/_actual/
/runs/
+210 -129
View File
@@ -10,6 +10,7 @@ plugins {
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
alias libs.plugins.renamer
alias libs.plugins.jarsigner
id 'net.minecraftforge.forgedev'
id 'net.minecraftforge.forge.build.convention'
@@ -39,11 +40,20 @@ fdtools {
javaLauncher = javaToolchains.launcherFor(java.toolchain)
}
}
renamerTools {
configure('classes') {
artifact = buildLibs.renamer.get().toString() + ':all'
}
configure('srg2source') {
version = buildLibs.srg2source.get().version
javaLauncher = javaToolchains.launcherFor(java.toolchain)
}
}
/* This is used for debugging, comparing all published artifacts against actual production
forgedev.validatePublish {
task {
baseBranch = 'upstream/26.1.2'
baseBranch = 'upstream/1.21.11'
versionPrefix = minecraftVersion
}
}
@@ -53,6 +63,8 @@ final minecraftFiles = forgedev.minecraftFiles(minecraftVersion)
final mcpBase = forgedev.mcpBase {
mcpVersion = "${minecraftVersion}-${project.ext.mcpVersion}"
mappingChannel = mappingsChannel
mappingVersion = mappingsVersion
accessTransformers.from file('src/main/resources/META-INF/accesstransformer.cfg')
sideAnnotationStrippers.from file('src/main/resources/forge.sas')
}
@@ -115,10 +127,15 @@ configurations {
extendsFrom(bootstrap)
}
register('installerextra') {
transitive = false
extendsFrom(bootstrap)
}
named('api') {
extendsFrom(installer)
}
named('implementation') {
// Inherit vanilla Minecraft's dependencies
extendsFrom(mcpBase.dependencyConfiguration)
@@ -162,7 +179,14 @@ dependencies {
installer projects.lowcodelanguage
installer projects.mclanguage
installer projects.forgeTransformers
// Remove when we bump to a version with no srg names, as this is needed to map srg->official at runtimes
installerextra("de.oceanlabs.mcp:mcp_config:${mcpBase.mcpVersion.get().toString()}:srg2off")
// Get the zip file with csv's of srg->mapepd for Runtime SRG->Mapped reflection remapping
// This is also only needed during dev time, and automatically added to the dep list by Mavenizer for userdev
// We shouldn't need this anymore now that we use runtime official mappings, but some of our code still references SRG names via reflection at dev time
runtimeOnly files(mcpBase.mappingZip)
runtimeOnly files(mcpBase.extra)
runtimeOnly libs.bootstrap
@@ -172,8 +196,8 @@ dependencies {
}
final EXTRA_TXTS = [
file('LICENSE.txt'),
changelog.task.flatMap(task -> task.outputFile)
file('LICENSE.txt'),
changelog.task.flatMap(task -> task.outputFile)
]
forgedev.runs {
configureEach {
@@ -182,7 +206,7 @@ forgedev.runs {
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders'
jvmArgs '-Djava.net.preferIPv6Addresses=system'
systemProperty 'bsl.debug', 'true'
systemProperty 'terminal.jline', 'true'
@@ -199,13 +223,13 @@ forgedev.runs {
systemProperty 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll'
args '--launchTarget', 'forge_dev_client',
'--username', 'Dev',
'--version', project.name,
'--accessToken', '0',
'--userType', 'mojang',
'--versionType', 'release',
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
'--username', 'Dev',
'--version', project.name,
'--accessToken', '0',
'--userType', 'mojang',
'--versionType', 'release',
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
}
}
@@ -225,20 +249,20 @@ forgedev.runs {
create('data') {
options {
args '--launchTarget', 'forge_dev_data',
'--all',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
'--all',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.main) {
args '--mod', 'forge',
'--output', rootProject.file('src/main/generated/')
'--output', rootProject.file('src/main/generated/')
}
with(sourceSets.test) {
args '--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
}
}
}
@@ -246,24 +270,24 @@ forgedev.runs {
create('clientData') {
options {
args '--all',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.main) {
args '--launchTarget', 'forge_dev_client_data',
'--mod', 'forge',
'--output', rootProject.file('src/main/generated/'),
'--mod', 'forge'
'--mod', 'forge',
'--output', rootProject.file('src/main/generated/'),
'--mod', 'forge'
}
with(sourceSets.test) {
args '--launchTarget', 'forge_dev',
'--launchEntry', 'minecraft/net.minecraft.client.data.Main',
'--launchData',
'--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
'--launchEntry', 'minecraft/net.minecraft.client.data.Main',
'--launchData',
'--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
}
}
}
@@ -279,6 +303,7 @@ final serverShim = forgedev.shim {
}
classpath {
libraries configurations.installer
libraries configurations.installerextra
library tasks.named('universalJar', Jar)
library forgedev.binaryPatches('server').apply, 'server'
serverBundle.fileProvider(minecraftFiles.server)
@@ -290,7 +315,7 @@ final serverShim = forgedev.shim {
}
final finalizeSpawn = forgedev.methodCallFinder('findFinalizeSpawnTargets') {
jar.fileProvider(mcpBase.classes)
jar = tasks.named('renameClientOfficial').flatMap{ it.output } //.fileProvider(mcpBase.classes)
output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json')
blacklist 'net/minecraft/world/level/BaseSpawner' // Ignore this class as we special case it.
invokeVirtual('finalizeSpawn', '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;')
@@ -302,16 +327,18 @@ forgedev.validateDeprecations(tasks.named('jar', Jar)) {
forgedev.checks() {
base = mcpBase
ats {}
ats {
mappings.fileProvider(mcpBase.map2Srg)
}
sas {}
execs {}
execs { }
patches {
dependsOn forgedev.patches.make
patchDir = forgedev.patches.patches
patchesWithS2SArtifact = [
'minecraft/net/minecraft/client/renderer/ViewArea.java.patch',
'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch',
'minecraft/net/minecraft/client/renderer/ViewArea.java.patch',
'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch',
]
}
@@ -336,23 +363,23 @@ tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge'
'Automatic-Module-Name': 'net.minecraftforge.forge'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : gitversion.info.tag,
'Implementation-Title' : project.group,
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': forgeVersion
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : gitversion.info.tag,
'Implementation-Title' : project.group,
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': forgeVersion
], 'net/minecraftforge/versions/forge/')
attributes([
'Specification-Title' : 'Minecraft',
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : minecraftVersion,
'Implementation-Title' : 'MCP',
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': mcpVersion
'Specification-Title' : 'Minecraft',
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : minecraftVersion,
'Implementation-Title' : 'MCP',
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': mcpVersion
], 'net/minecraftforge/versions/mcp/')
}
jarSigner.sign(it)
@@ -367,17 +394,39 @@ final universalJar = tasks.register('universalJar', Jar) {
jarSigner.sign(it)
}
final sourcesJarAll = tasks.register('sourcesJarAll', Jar) {
archiveClassifier = 'sources-all'
from(sourceSets.main.allJava.srcDirs)
final map2Tsrg = renamer.convert('map2Tsrg', mcpBase.map2Srg) // Remove when we bump renamer to version that supports gziped files (on MC version change)
final reobfJar = renamer.classes('srgJar', tasks.named('jar', Jar)) {
libraries.setFrom(configurations.compileClasspath)
mappings map2Tsrg
archiveClassifier = 'srg'
}
final universalJarSrg = tasks.register('universalJarSrg', Jar) {
from (zipTree(reobfJar.flatMap { it.output } )) {
exclude(forgedev.filterVanilla(mcpBase.classes))
}
manifest = universalJar.get().manifest
archiveClassifier = 'universal-srg'
jarSigner.sign(it)
}
final sourcesSrg = renamer.sources(sourceSets.main) {
apply {
archiveClassifier = 'sources-srg'
mappings mcpBase.map2Srg
excFiles.from files('src/main/resources/forge.exc')
}
extract {
mustRunAfter(forgedev.patches.apply)
}
}
// Our sources, plus the patches we do to Vanilla Minecraft
final sourcesJar = tasks.register('sourcesJar', Jar) {
archiveClassifier = 'sources'
from(sourceSets.main.allJava.srcDirs - forgedev.patches.patched.asFile.get())
mustRunAfter(forgedev.patches.make)
from(forgedev.patches.patches) {
// If we don't need to reobf we can just do this
//from(sourceSets.main.allJava.srcDirs - forgedev.patches.patched.asFile.get())
dependsOn(sourcesSrg.apply)
from(zipTree(sourcesSrg.apply.flatMap{ it.output })) {
exclude { forgedev.patches.patched.file(it.path).get().asFile.exists() }
}
from(forgedev.userDev.patches.flatMap{ it.output }) {
into 'patches/'
}
}
@@ -386,7 +435,7 @@ forgedev.userDev {
base(mcpBase)
config {
universal universalJar
universal universalJarSrg
sources sourcesJar
addLibraries(configurations.installer)
// TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config
@@ -400,7 +449,7 @@ forgedev.userDev {
parents = []
main = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders'
jvmArgs '-Djava.net.preferIPv6Addresses=system'
client = it.name.containsIgnoreCase('client')
environment 'MCP_MAPPINGS', '{mcp_mappings}'
}
@@ -436,33 +485,40 @@ forgedev.userDev {
}
}
patches {
modified.setFrom(sourcesJarAll.flatMap { it.archiveFile })
modified.setFrom(sourcesSrg.apply.flatMap{ it.output })
}
binaryPatches {
mustRunAfter(forgedev.patches.make)
patches.setFrom(forgedev.patches.patches)
dirty.setFrom(tasks.named('jar', Jar).flatMap { it.archiveFile })
patches.setFrom(forgedev.patches.apply.flatMap { it.output })
dirty.setFrom(reobfJar.flatMap { it.output })
}
}
forgedev.userdevCompatibility(minecraftVersion) {
clean = mcpBase.classes
dirty = tasks.named('jar', Jar)
clean = mcpBase.classesRaw
dirty = reobfJar
}
// region Installer tasks, Not needed when we simplify the installer/remove obf
final clientBinPatches = forgedev.binaryPatches('client') {
clean = minecraftFiles.client
// Workaround for 3rd party launcher issues, marker needed to locate MC, store needed to fix zlib-ng issues
apply {
store = true
marker = '.forge_patched_minecraft'
final renameOfficial = (side, inputProvider, mappingsProvider) -> {
renamer.classes('rename' + side + 'Official') {
additionalArgs = ['--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs', '--reverse']
input.fileProvider(inputProvider)
libraries = mcpBase.dependencyConfiguration
mappings mappingsProvider
output = layout.buildDirectory.file("libs/rename-${side.toLowerCase()}-official.jar")
logFile = layout.buildDirectory.file("libs/rename-${side.toLowerCase()}-official.log")
}
}
final serverBinPatches = forgedev.binaryPatches('server') {
clean = minecraftFiles.serverExtracted
apply { store = true }
final clientOfficial = renameOfficial('Client', minecraftFiles.client, minecraftFiles.clientMappings)
final serverOfficial = renameOfficial('Server', minecraftFiles.serverExtracted, minecraftFiles.serverMappings)
final clientBinPatches = forgedev.binaryPatches('client') {
clean = clientOfficial.flatMap { it.output }
}
final serverBinPatches = forgedev.binaryPatches('server') {
clean = serverOfficial.flatMap { it.output }
}
final sanitizedClientMappings = renamer.convert("cleanClientMappings", minecraftFiles.clientMappings)
final sanitizedServerMappings = renamer.convert("cleanServerMappings", minecraftFiles.serverMappings)
// endregion
forgedev.installer {
@@ -473,6 +529,8 @@ forgedev.installer {
// Add extra libraries
pack serverShim.jar // The Server executable jar IS needed to be packed, as the installer would need a spec bump in order to download it
pack universalJar // I don't think this is strictly needed to be packed anymore, will need to double check later
// This isn't actually needed, but its here for some reason, probably wasn't deleted when then installer process was cleaned up
library mcpBase.mcpArtifact.get()
launcherLibrary universalJar
// We configure the launcher here to keep the order for easier diff, after publishing, move this to the LauncherJson configuration below
@@ -481,11 +539,13 @@ forgedev.installer {
generated(clientBinPatches.apply, 'client')
}
launcherLibraries configurations.installerextra
launcherLibraries configurations.installer
final launcherId = "${minecraftVersion}-${project.name}-${forgeVersion}"
final installertools = tool(buildLibs.installertools)
final binarypatcher = tool(buildLibs.binarypatcher)
final renamer = tool(buildLibs.renamer)
jar {
from(EXTRA_TXTS)
@@ -501,8 +561,8 @@ forgedev.installer {
final argsFile = rootProject.file('server_files/args.txt')
final tokens = [tokens: [
SHIM_JAR_FILE: serverShim.jar.get().archiveFileName.get(),
MAVEN_PATH: forgedev.mavenArtifact.directory
SHIM_JAR_FILE: serverShim.jar.get().archiveFileName.get(),
MAVEN_PATH: forgedev.mavenArtifact.directory
]]
from(argsFile) {
filter(tokens, ReplaceTokens)
@@ -531,57 +591,74 @@ forgedev.installer {
final projectArtifact = "${project.group}:${project.name}:${project.version}"
data 'MOJMAPS', "[net.minecraft:client:${minecraftVersion}:mappings@tsrg]", "[net.minecraft:server:${minecraftVersion}:mappings@tsrg]"
data 'MOJMAPS_SHA', sanitizedClientMappings.flatMap{ it.output }, sanitizedServerMappings.flatMap{ it.output }
data 'MC_UNPACKED', "[net.minecraft:client:${minecraftVersion}]", "[net.minecraft:server:${minecraftVersion}:unpacked]"
data 'MC_UNPACKED_SHA', minecraftFiles.client, minecraftFiles.serverExtracted
data 'MC_OFF', "[net.minecraft:client:${minecraftVersion}:official]", "[net.minecraft:server:${minecraftVersion}:official]"
data 'MC_OFF_SHA', clientOfficial.flatMap { it.output }, serverOfficial.flatMap { it.output }
data 'BINPATCH', '/data/client.lzma', '/data/server.lzma'
data 'PATCHED', "[$projectArtifact:client]", "[$projectArtifact:server]"
data 'PATCHED_SHA', clientBinPatches.apply.flatMap { it.output }, serverBinPatches.apply.flatMap { it.output }
data 'PATCHED_SHA', clientBinPatches.apply.flatMap { it.output }, serverBinPatches.apply.flatMap { it.output }
def rename = (side, input) -> {
step(renamer) {
sides = [side]
args = [
'--input', input,
'--output', '{MC_OFF}',
'--names', '{MOJMAPS}',
'--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs', '--reverse'
]
cache '{MC_OFF}', '{MC_OFF_SHA}'
}
}
steps = [
extract(installertools) {
side = 'server'
archive '{INSTALLER}'
extract 'data/README.txt', '{ROOT}/README.txt'
extract 'data/run.sh', '{ROOT}/run.sh'
executable '{ROOT}/run.sh'
extract 'data/run.bat', '{ROOT}/run.bat'
optional 'data/user_jvm_args.txt', '{ROOT}/user_jvm_args.txt'
extract 'data/unix_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/unix_args.txt"
extract 'data/win_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/win_args.txt"
},
extractBundle(installertools) {
side = 'server'
libraries()
},
extractBundle(installertools) {
side = 'server'
jar '{MINECRAFT_JAR}', '{MC_UNPACKED}'
cache '{MC_UNPACKED}', '{MC_UNPACKED_SHA}'
},
step(binarypatcher) {
side = 'server'
args = [
'--clean', '{MC_UNPACKED}',
'--output', '{PATCHED}',
'--apply', '{BINPATCH}',
'--data', '--unpatched',
'--store'
]
cache '{PATCHED}', '{PATCHED_SHA}'
},
step(binarypatcher) {
side = 'client'
args = [
'--clean', '{MINECRAFT_JAR}',
'--output', '{PATCHED}',
'--apply', '{BINPATCH}',
'--data', '--unpatched',
'--store',
'--marker', '.forge_patched_minecraft'
]
cache '{PATCHED}', '{PATCHED_SHA}'
}
extract(installertools) {
side = 'server'
archive '{INSTALLER}'
extract 'data/README.txt', '{ROOT}/README.txt'
extract 'data/run.sh', '{ROOT}/run.sh'
executable '{ROOT}/run.sh'
extract 'data/run.bat', '{ROOT}/run.bat'
optional 'data/user_jvm_args.txt', '{ROOT}/user_jvm_args.txt'
extract 'data/unix_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/unix_args.txt"
extract 'data/win_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/win_args.txt"
},
extractBundle(installertools) {
side = 'server'
libraries()
},
extractBundle(installertools) {
side = 'server'
jar '{MINECRAFT_JAR}', '{MC_UNPACKED}'
cache '{MC_UNPACKED}', '{MC_UNPACKED_SHA}'
},
step(installertools) {
args = [
'--task', 'DOWNLOAD_MOJMAPS',
'--sanitize',
'--version', minecraftVersion,
'--side', '{SIDE}',
'--output', '{MOJMAPS}'
]
cache '{MOJMAPS}', '{MOJMAPS_SHA}'
},
rename.call('server', '{MC_UNPACKED}'),
rename.call('client', '{MINECRAFT_JAR}'),
step(binarypatcher) {
args = [
'--clean', '{MC_OFF}',
'--output', '{PATCHED}',
'--apply', '{BINPATCH}',
'--data', '--unpatched'
]
cache '{PATCHED}', '{PATCHED_SHA}'
}
]
}
@@ -595,7 +672,7 @@ forgedev.installer {
inheritsFrom = minecraftVersion
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
gameArgs = [ '--launchTarget', 'forge_client' ]
jvmArgs = [ '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders' ]
jvmArgs = [ '-Djava.net.preferIPv6Addresses=system' ]
}
}
@@ -626,13 +703,15 @@ final mdkZip = tasks.register('mdkZip', Zip) {
exclude it
}
filter(ReplaceTokens, tokens: [
FORGE_VERSION: forgeVersion,
FORGE_GROUP: project.group,
FORGE_NAME: project.name,
MC_VERSION: minecraftVersion,
FORGE_SPEC_VERSION: gitversion.info.tag.split("\\.")[0],
MC_NEXT_VERSION: minecraftNextVersion,
EVENTBUS_VERSION: libs.versions.eventbus.get()
FORGE_VERSION: forgeVersion,
FORGE_GROUP: project.group,
FORGE_NAME: project.name,
MC_VERSION: minecraftVersion,
MAPPING_CHANNEL: mappingsChannel,
MAPPING_VERSION: mappingsVersion,
FORGE_SPEC_VERSION: gitversion.info.tag.split("\\.")[0],
MC_NEXT_VERSION: minecraftNextVersion,
EVENTBUS_VERSION: libs.versions.eventbus.get()
])
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
@@ -734,6 +813,7 @@ publishing {
gradleutils.promote(it)
artifact universalJar
artifact universalJarSrg
artifact forgedev.installer.jar
artifact mdkZip
artifact forgedev.userDev.jar
@@ -779,8 +859,8 @@ tasks.withType(AbstractTestTask).configureEach {
}
// This shouldn't be needed according to the Gradle devs, as we never run these commands together.
// But in another case of Gradle being gradle, here we are
[ 'makePatches', 'compileJava', 'checkLicenseMain', 'updateLicenseMain', 'sourcesJar', 'sourcesJarAll' ]
.each{ tasks.named(it).configure{ mustRunAfter forgedev.patches.apply }}
[ 'makePatches', 'compileJava', 'checkLicenseMain', 'updateLicenseMain', 'sourcesJar' ]
.each{ tasks.named(it).configure{ mustRunAfter forgedev.patches.apply }}
forgedev.runs.configureEach {
run.configure { dependsOn finalizeSpawn}
runTest.configure { dependsOn finalizeSpawn}
@@ -791,5 +871,6 @@ tasks.named('dependencyUpdates', com.github.benmanes.gradle.versions.updates.Dep
resolutionStrategy {
mcpBase.dependencies.get().forEach {dep -> force(dep) } // Force all vanilla dependencies to their correct versions
force(earlyDisplayLibs.slf4j.jdk14) // Force slf4j implementation, its tied to the api from the manifest
force('de.oceanlabs.mcp:mcp_config:' + bootLibs.versions.mcp) // Don't warn abou MCPConfig updates
}
}
+8 -10
View File
@@ -2,8 +2,8 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=26.2-&query=$.promos["26.2-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=26.2-&query=$.promos["26.2-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.11-&query=$.promos["1.21.11-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.11-&query=$.promos["1.21.11-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
Forge is a free, open-source modding API all of your favourite mods use!
@@ -34,13 +34,12 @@ For support and questions, visit [the Support Forum][Support-Forum] or [the Forg
We require the version of Java that Minecraft requires, which is currently:
| Minecraft | Java |
|------------------|-------------------|
| 26.1+ | [25][Adoptium-25] |
| 1.20.6 - 1.21.11 | [21][Adoptium-21] |
| 1.18 - 1.20.4 | [17][Adoptium-17] |
| 1.17.1 | [16][Adoptium-16] |
| 1.16.5- | [8][Adoptium-8] |
| Minecraft | Java |
|---------------|-------------------|
| 1.20.6+ | [21][Adoptium-21] |
| 1.18 - 1.20.4 | [17][Adoptium-17] |
| 1.17.1 | [16][Adoptium-16] |
| 1.16.5- | [8][Adoptium-8] |
**If you are on Windows and have difficulty using Java to launch JAR files, consider using [Jarfix by Johann Löfflmann][Jarfix].**
@@ -81,7 +80,6 @@ YourKit for providing us accesss to their [YourKit Java Profiler] which helps id
[Download]: https://files.minecraftforge.net/
[Adoptium-25]: https://adoptium.net/temurin/releases/?version=25
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env bash
for file in $(find repo | grep --invert-match -e md5 -e sha | grep -e .jar -e .zip); do
file_path=$(realpath $file)
file_name=$(basename $file)
echo "$file_name"
files=$(unzip -l $file | awk '{print $4}')
for f in $files; do
out="_actual/$file_name/$f"
parent=$(dirname "$out")
echo $out
if [ ! -d "$parent" ]; then
mkdir -p "$parent"
fi
if [[ $f == *.class ]]; then
javap -v -p jar:file://$file_path!/$f | tail -n +4 >$out.txt
elif [[ $f != */ && $f != "Name" && $f != "----" ]]; then
unzip -o -q $file $f -d _actual/$file_name
fi
done
done
+8 -8
View File
@@ -40,16 +40,16 @@ tasks.withType(JavaCompile).configureEach {
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType' : 'LIBRARY'
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType' : 'LIBRARY'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Version': '1.0',
'Implementation-Vendor' : vendor
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Version': '1.0',
'Implementation-Vendor' : vendor
], 'net/minecraftforge/fml/')
}
}
@@ -14,14 +14,16 @@ import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
public class CrashReportCallables {
public class CrashReportCallables
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final List<ISystemReportExtender> crashCallables = Collections.synchronizedList(new ArrayList<>());
/**
* Register a custom {@link ISystemReportExtender}
*/
public static void registerCrashCallable(ISystemReportExtender callable) {
public static void registerCrashCallable(ISystemReportExtender callable)
{
crashCallables.add(callable);
}
@@ -31,15 +33,19 @@ public class CrashReportCallables {
* @param headerName The name of the system report entry
* @param reportGenerator The report generator to be called when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator) {
registerCrashCallable(new ISystemReportExtender() {
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator)
{
registerCrashCallable(new ISystemReportExtender()
{
@Override
public String getLabel() {
public String getLabel()
{
return headerName;
}
@Override
public String get() {
public String get()
{
return reportGenerator.get();
}
});
@@ -52,23 +58,31 @@ public class CrashReportCallables {
* @param reportGenerator The report generator to be called when a crash report is built
* @param active The supplier of the flag to be checked when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator, BooleanSupplier active) {
registerCrashCallable(new ISystemReportExtender() {
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator, BooleanSupplier active)
{
registerCrashCallable(new ISystemReportExtender()
{
@Override
public String getLabel() {
public String getLabel()
{
return headerName;
}
@Override
public String get() {
public String get()
{
return reportGenerator.get();
}
@Override
public boolean isActive() {
try {
public boolean isActive()
{
try
{
return active.getAsBoolean();
} catch (Throwable t) {
}
catch (Throwable t)
{
LOGGER.warn("CrashCallable '{}' threw an exception while checking the active flag, disabling", headerName, t);
return false;
}
@@ -76,7 +90,8 @@ public class CrashReportCallables {
});
}
public static List<ISystemReportExtender> allCrashCallables() {
public static List<ISystemReportExtender> allCrashCallables()
{
return List.copyOf(crashCallables);
}
}
@@ -0,0 +1,273 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.FMLEnvironment;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
/**
* Use to execute code conditionally based on sidedness.
* <ul>
* <li>When you want to call something on one side and return a result {@link #safeCallWhenOn(Dist, Supplier)}</li>
* <li>When you want to call one thing on one side, another thing on the other and return a result {@link #safeRunForDist(Supplier, Supplier)}</li>
* <li>When you want to run something on one side {@link #safeRunWhenOn(Dist, Supplier)}</li>
* </ul>
*
* @deprecated Historically, this allowed for safe execution of code on the correct side by exploiting a low-level
* detail in Java 8, but in newer versions a simple if check is enough.
* <br>
* For example:
* <pre>{@code
* if (FMLEnvironment.dist.isClient()) {
* // client code here
* }
* }</pre>
*/
@Deprecated(since = "1.21.5", forRemoval = true)
public final class DistExecutor
{
private static final Logger LOGGER = LogManager.getLogger();
private DistExecutor() {}
/**
* Run the callable in the supplier only on the specified {@link Dist}.
* This method is NOT sided-safe and special care needs to be taken in code using this method that implicit class
* loading is not triggered by the Callable.
*
* This method can cause unexpected ClassNotFound exceptions.
*
* Use {@link #safeCallWhenOn(Dist, Supplier)} where possible.
*
* @param dist The dist to run on
* @param toRun A supplier of the callable to run (Supplier wrapper to ensure classloading only on the appropriate dist)
* @param <T> The return type from the callable
* @return The callable's result
* @deprecated use {@link #safeCallWhenOn(Dist, Supplier)} instead. This remains for advanced use cases.
*/
@Deprecated
public static <T> T callWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
return unsafeCallWhenOn(dist, toRun);
}
public static <T> T unsafeCallWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
if (dist == FMLEnvironment.dist) {
try
{
return toRun.get().call();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
return null;
}
/**
* Call the SafeCallable when on the correct {@link Dist}.
*
* <strong>The lambda supplied here is required to be a method reference to a method defined in
* another class, otherwise an invalid SafeReferent error will be thrown</strong>
* @param dist the dist which this will run on
* @param toRun the SafeCallable to run and return the result from
* @param <T> The type of the SafeCallable
* @return the result of the SafeCallable or null if on the wrong side
*/
public static <T> T safeCallWhenOn(Dist dist, Supplier<SafeCallable<T>> toRun) {
validateSafeReferent(toRun);
return callWhenOn(dist, toRun::get);
}
/**
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #callWhenOn(Dist, Supplier)}.
*
* This method can cause unexpected ClassNotFound exceptions.
*
* @see #callWhenOn(Dist, Supplier)
* @param dist Dist to run this code on
* @param toRun The code to run
* @deprecated use {@link #safeRunWhenOn(Dist, Supplier)} where possible. Advanced uses only.
*/
@Deprecated
public static void runWhenOn(Dist dist, Supplier<Runnable> toRun) {
unsafeRunWhenOn(dist, toRun);
}
/**
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #unsafeCallWhenOn(Dist, Supplier)}.
*
* This method can cause unexpected ClassNotFoundException problems in common scenarios. Understand the pitfalls of
* the way the class verifier works to load classes before using this.
*
* Use {@link #safeRunWhenOn(Dist, Supplier)} if you can.
*
* @see #unsafeCallWhenOn(Dist, Supplier)
* @param dist Dist to run this code on
* @param toRun The code to run
*/
public static void unsafeRunWhenOn(Dist dist, Supplier<Runnable> toRun) {
if (dist == FMLEnvironment.dist) {
toRun.get().run();
}
}
/**
* Call the supplied SafeRunnable when on the correct Dist.
* @param dist The dist to run on
* @param toRun The code to run
*/
public static void safeRunWhenOn(Dist dist, Supplier<SafeRunnable> toRun) {
validateSafeReferent(toRun);
if (dist == FMLEnvironment.dist) {
toRun.get().run();
}
}
/**
* Executes one of the two suppliers, based on which side is active.
*
* <p>
* Example (replacement for old SidedProxy):<br/>
* {@code Proxy p = DistExecutor.runForDist(()->ClientProxy::new, ()->ServerProxy::new);}
*
* NOTE: the double supplier is required to avoid classloading the secondary target.
*
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
* @param <T> The common type to return
* @return The returned instance
* @deprecated Use {@link #safeRunForDist(Supplier, Supplier)}
*/
@Deprecated
public static <T> T runForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
return unsafeRunForDist(clientTarget, serverTarget);
}
/**
* Unsafe version of {@link #safeRunForDist(Supplier, Supplier)}. Use only when you know what you're doing
* and understand why the verifier can cause unexpected ClassNotFoundException crashes even when code is apparently
* not sided. Ensure you test both sides fully to be confident in using this.
*
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
* @param <T> The common type to return
* @return The returned instance
*/
public static <T> T unsafeRunForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
switch (FMLEnvironment.dist)
{
case CLIENT:
return clientTarget.get().get();
case DEDICATED_SERVER:
return serverTarget.get().get();
default:
throw new IllegalArgumentException("UNSIDED?");
}
}
/**
* Executes one of the two suppliers, based on which side is active.
*
* <p>
* Example (replacement for old SidedProxy):<br/>
* {@code Proxy p = DistExecutor.safeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);}
*
* NOTE: the double supplier is required to avoid classloading the secondary target.
*
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
* @param <T> The common type to return
* @return The returned instance
*/
public static <T> T safeRunForDist(Supplier<SafeSupplier<T>> clientTarget, Supplier<SafeSupplier<T>> serverTarget) {
validateSafeReferent(clientTarget);
validateSafeReferent(serverTarget);
switch (FMLEnvironment.dist)
{
case CLIENT:
return clientTarget.get().get();
case DEDICATED_SERVER:
return serverTarget.get().get();
default:
throw new IllegalArgumentException("UNSIDED?");
}
}
/**
* A safe referent. This will assert that it is being called via a separated class method reference. This will
* avoid the common pitfalls of {@link #callWhenOn(Dist, Supplier)} above.
*
* SafeReferents assert that they are defined as a separate method outside the scope of the calling class.
*
* <strong>Implementations need to be defined in a separate class to the referring site, with appropriate
* visibility to be accessible at the callsite (generally, avoid private methods).</strong>
*
* <p>
* Valid:<br/>
*
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->AnotherClass::clientOnlyMethod);}
*
* <p>
* Invalid:<br/>
*
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->()->Minecraft.getInstance().world);}
*/
public interface SafeReferent {}
/**
* SafeCallable version of {@link SafeReferent}.
* @see SafeReferent
* @param <T> The return type of the Callable
*/
public interface SafeCallable<T> extends SafeReferent, Callable<T>, Serializable {}
/**
* SafeSupplier version of {@link SafeReferent}
* @param <T> The return type of the Supplier
*/
public interface SafeSupplier<T> extends SafeReferent, Supplier<T>, Serializable {}
/**
* SafeRunnable version of {@link SafeReferent}
*/
public interface SafeRunnable extends SafeReferent, Runnable, Serializable {}
private static final void validateSafeReferent(Supplier<? extends SafeReferent> safeReferentSupplier) {
if (FMLEnvironment.production) return;
final SafeReferent setter;
try {
setter = safeReferentSupplier.get();
} catch (Exception e) {
// Typically a class cast exception, just return out, expected.
return;
}
for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(setter);
if (!(replacement instanceof SerializedLambda))
break;// custom interface implementation
SerializedLambda l = (SerializedLambda) replacement;
if (Objects.equals(l.getCapturingClass(), l.getImplClass())) {
LOGGER.fatal("Detected unsafe referent usage, please view the code at {}",Thread.currentThread().getStackTrace()[3]);
throw new RuntimeException("Unsafe Referent usage found in safe referent method");
}
} catch (NoSuchMethodException e) {
} catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}
}
}
@@ -6,14 +6,13 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
/**
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
@@ -21,7 +20,6 @@ import java.util.function.Supplier;
*
* @see IModStateProvider
*/
@NullMarked
public interface IModLoadingState {
/**
* {@return the name of this state}
@@ -44,21 +42,19 @@ public interface IModLoadingState {
/**
* {@return a function returning a human-friendly message for this state}
*/
Supplier<String> message();
Function<ModList, String> message();
/**
* @return a function that computes the size of this transition based on the size of the modlist.
* Used to compute progress.
*/
default IntSupplier size() {
return ModList::size;
}
ToIntFunction<ModList> size();
/**
* {@return an optional runnable, which runs before starting the transition from this state to the next}
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
@Nullable Runnable inlineRunnable();
Optional<Consumer<ModList>> inlineRunnable();
/**
* Builds the transition task for this state with a blank pre-sync and post-sync task.
@@ -69,14 +65,14 @@ public interface IModLoadingState {
* @return a transition task for this state
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
default @Nullable CompletableFuture<Void> buildTransition(
default Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar
) {
return buildTransition(syncExecutor, parallelExecutor, progressBar,
_ -> CompletableFuture.completedFuture(null),
_ -> CompletableFuture.completedFuture(null)
e -> CompletableFuture.completedFuture(null),
e -> CompletableFuture.completedFuture(null)
);
}
@@ -91,7 +87,7 @@ public interface IModLoadingState {
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
* @return a transition task for this state
*/
@Nullable CompletableFuture<Void> buildTransition(final Executor syncExecutor,
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
@@ -6,16 +6,21 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jetbrains.annotations.Nullable;
public interface IModStateTransition {
/** Magic value to allow me to optimize the futures list by ignoring the default value without making old methods nullable. */
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
static IModStateTransition buildNoopTransition() {
return ModStateTransitionHelper.NOOP;
}
@@ -35,6 +40,15 @@ public interface IModStateTransition {
return ModLoadingStage::nextState;
}
/**
* This used to allow you to fire multiple events during the transition. However, in doing so it would cause issues with the default
* ModContainer's event handlers causing issues such as mod classes being constructed multiple times.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
return () -> Stream.ofNullable(eventFunction());
}
@Nullable
default <T extends IModBusEvent> EventGenerator<T> eventFunction() {
return null;
@@ -43,9 +57,25 @@ public interface IModStateTransition {
ThreadSelector threadSelector();
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
/**
* I think this was meant as a way to do some things for each mod container beforge/after the transition had been sent to the container.
* However, the Future returned by this was never linked to the main transition future in any way. Which means that it was run
* in parallel and couldn't guarantee the state of the ModContainer.
* <p>
* Plus all existing code that I could find returned a completedFuture, so I don't think anyone ever used this.
* <p>
* If I were to add this back it would be a CompletableFuture wrap(ModContainer, CompletableFuture)
* <p>
* Added magic value NULL_HOOK to allow me to optimize the futures list by ignoring the default value without making this method nullable.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() { return NULL_HOOK; }
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() { return NULL_HOOK; }
interface EventGenerator<T extends IModBusEvent> extends Function<ModContainer, T> {
static <FN extends IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
return fn::apply;
}
}
}
}
@@ -7,10 +7,12 @@ package net.minecraftforge.fml;
import java.util.function.Supplier;
public interface ISystemReportExtender extends Supplier<String> {
public interface ISystemReportExtender extends Supplier<String>
{
String getLabel();
default boolean isActive() {
default boolean isActive()
{
return true;
}
}
@@ -5,20 +5,63 @@
package net.minecraftforge.fml;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public final class InterModComms {
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {}
public class InterModComms
{
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {
private static final ConcurrentHashMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
/**
* Deprecated: use {@link #senderModId()}
* @return The modid of the sender. This is supplied by the caller, or by the active mod container context.
* Consider it unreliable.
*/
@Deprecated
public final String getSenderModId() {
return this.senderModId;
}
/**
* Deprecated: use {@link #modId()}
* @return The modid being sent to.
*/
@Deprecated
public final String getModId() {
return this.modId;
}
/**
* Deprecated: use {@link #method()}
* @return The method being sent to.
*/
@Deprecated
public final String getMethod() {
return this.method;
}
/**
* @param <T> The type of the message.
* @return A {@link Supplier} of the message.
* Use {@link #messageSupplier()}
*/
@SuppressWarnings("unchecked")
@Deprecated
public final <T> Supplier<T> getMessageSupplier() {
return (Supplier<T>) this.messageSupplier;
}
}
private static final ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
/**
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
@@ -29,9 +72,8 @@ public final class InterModComms {
* @return true if the message was enqueued for sending (the target modid is loaded)
*/
public static boolean sendTo(final String modId, final String method, final Supplier<?> thing) {
if (!ModList.isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, _ -> new ConcurrentLinkedQueue<>())
.add(new IMCMessage(ModLoadingContext.get().getContainer().getModId(), modId, method, thing));
if (!ModList.get().isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(ModLoadingContext.get().getActiveContainer().getModId(), modId, method, thing));
return true;
}
@@ -45,9 +87,8 @@ public final class InterModComms {
* @return true if the message was enqueued for sending (the target modid is loaded)
*/
public static boolean sendTo(final String senderModId, final String modId, final String method, final Supplier<?> thing) {
if (!ModList.isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, _ -> new ConcurrentLinkedQueue<>())
.add(new IMCMessage(senderModId, modId, method, thing));
if (!ModList.get().isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(senderModId, modId, method, thing));
return true;
}
@@ -71,7 +112,7 @@ public final class InterModComms {
* @return All messages
*/
public static Stream<IMCMessage> getMessages(final String modId) {
return getMessages(modId, _ -> true);
return getMessages(modId, s->Boolean.TRUE);
}
private record QueueFilteringSpliterator(
@@ -95,10 +136,13 @@ public final class InterModComms {
}
@Override
public boolean tryAdvance(final Consumer<? super IMCMessage> action) {
public boolean tryAdvance(final Consumer<? super IMCMessage> action)
{
IMCMessage next;
do {
if (!iterator.hasNext()) {
do
{
if (!iterator.hasNext())
{
return false;
}
next = this.iterator.next();
@@ -113,5 +157,6 @@ public final class InterModComms {
public Spliterator<IMCMessage> trySplit() {
return null;
}
}
}
@@ -19,6 +19,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -40,6 +41,11 @@ public abstract class ModContainer {
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
final Set<ModContainer> dependencies = new HashSet<>();
/**
* If you want to handle the event, override {@link #dispatchConfigEvent(IConfigEvent)}
*/
@Deprecated(since = "1.21.3", forRemoval = true)
protected Optional<Consumer<IConfigEvent>> configHandler = Optional.empty();
public ModContainer(IModInfo info) {
this.modId = info.getModId();
@@ -53,7 +59,7 @@ public abstract class ModContainer {
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
case "MATCH_VERSION" -> // default displaytest checks for version string match
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
(incoming, _) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
(incoming, isNetwork) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
case "IGNORE_SERVER_VERSION" -> // Ignores any version information coming from the server - use for server only mods
IExtensionPoint.DisplayTest.IGNORE_SERVER_VERSION;
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
@@ -132,7 +138,12 @@ public abstract class ModContainer {
configs.put(modConfig.getType(), modConfig);
}
public void dispatchConfigEvent(IConfigEvent event) {}
public void dispatchConfigEvent(IConfigEvent event) {
var handler = configHandler.orElse(null);
if (handler != null) {
handler.accept(event);
}
}
/**
* Does this mod match the supplied mod?
@@ -5,14 +5,14 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -30,36 +30,33 @@ import java.util.stream.Stream;
* Master list of all mods - game-side version. This is classloaded in the game scope and
* can dispatch game level events as a result.
*/
public final class ModList {
private ModList() {}
public class ModList {
private static ModList INSTANCE;
private final List<IModFileInfo> modFiles;
private final List<IModInfo> sortedList;
private final Map<String, IModFileInfo> fileById;
private List<ModContainer> mods;
private Map<String, ModContainer> indexedMods;
private List<ModFileScanData> modFileScanData;
private List<ModContainer> sortedContainers;
private static final List<IModFileInfo> MOD_FILES;
private static final List<IModInfo> SORTED_LIST;
private static final Map<String, IModFileInfo> FILE_BY_ID;
private static List<ModContainer> mods;
private static Map<String, ModContainer> indexedMods;
private static List<ModContainer> sortedContainers;
static {
var loadingModFiles = LoadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList();
var loadingSortedList = LoadingModList.getMods();
var modFileInfos = MOD_FILES = loadingModFiles.stream().map(ModFile::getModFileInfo).toList();
SORTED_LIST = loadingSortedList.stream().map(IModInfo.class::cast).toList();
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
var modFileInfos = this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
var byId = new HashMap<String, IModFileInfo>();
for (var file : modFileInfos) {
for (var mod : file.getMods())
byId.put(mod.getModId(), mod.getOwningFile());
}
FILE_BY_ID = Map.copyOf(byId);
CrashReportCallables.registerCrashCallable("Mod List", ModList::crashReport);
this.fileById = Map.copyOf(byId);
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
}
private static String getModContainerState(String modId) {
private String getModContainerState(String modId) {
return getModContainerById(modId).map(ModContainer::getCurrentState).map(Object::toString).orElse("NONE");
}
private static String fileToLine(IModFile mf) {
private String fileToLine(IModFile mf) {
var mainMod = mf.getModInfos().getFirst();
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
mainMod.getDisplayName(),
@@ -69,92 +66,92 @@ public final class ModList {
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
}
private static String crashReport() {
return "\n" + applyForEachModFile(ModList::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
private String crashReport() {
return "\n" + applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
public static void init() {}
public static List<IModFileInfo> getModFiles() {
return MOD_FILES;
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
INSTANCE = new ModList(modFiles, sortedList);
return INSTANCE;
}
public static IModFileInfo getModFileById(String modid) {
return FILE_BY_ID.get(modid);
public static ModList get() {
return INSTANCE;
}
static void setLoadedMods(final List<ModContainer> modContainers) {
mods = modContainers;
sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> SORTED_LIST.indexOf(c.getModInfo()))).toList();
indexedMods = modContainers.stream().collect(Collectors.toUnmodifiableMap(ModContainer::getModId, Function.identity()));
public List<IModFileInfo> getModFiles() {
return modFiles;
}
static void clearLoadedMods() {
mods = List.of();
indexedMods = Map.of();
sortedContainers = List.of();
public IModFileInfo getModFileById(String modid) {
return this.fileById.get(modid);
}
void setLoadedMods(final List<ModContainer> modContainers) {
this.mods = modContainers;
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> sortedList.indexOf(c.getModInfo()))).toList();
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
}
@SuppressWarnings("unchecked")
public static <T> Optional<T> getModObjectById(String modId) {
public <T> Optional<T> getModObjectById(String modId) {
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
}
public static Optional<? extends ModContainer> getModContainerById(String modId) {
return Optional.ofNullable(indexedMods.get(modId));
public Optional<? extends ModContainer> getModContainerById(String modId) {
return Optional.ofNullable(this.indexedMods.get(modId));
}
public static Optional<? extends ModContainer> getModContainerByObject(Object obj) {
public Optional<? extends ModContainer> getModContainerByObject(Object obj) {
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
}
public static List<IModInfo> getMods() {
return SORTED_LIST;
public List<IModInfo> getMods() {
return this.sortedList;
}
public static boolean isLoaded(String modTarget) {
return indexedMods.containsKey(modTarget);
public boolean isLoaded(String modTarget) {
return this.indexedMods.containsKey(modTarget);
}
public static int size() {
public int size() {
return mods.size();
}
public static List<ModFileScanData> getAllScanData() {
final class LazyInit {
private LazyInit() {}
private static final List<ModFileScanData> MOD_FILE_SCAN_DATA = SORTED_LIST.stream()
.map(IModInfo::getOwningFile)
.filter(Objects::nonNull)
.map(IModFileInfo::getFile)
.distinct()
.map(IModFile::getScanResult)
.toList();
public List<ModFileScanData> getAllScanData() {
if (modFileScanData == null) {
modFileScanData = this.sortedList.stream().
map(IModInfo::getOwningFile).
filter(Objects::nonNull).
map(IModFileInfo::getFile).
distinct().
map(IModFile::getScanResult).
toList();
}
return LazyInit.MOD_FILE_SCAN_DATA;
return modFileScanData;
}
public static void forEachModFile(Consumer<IModFile> fileConsumer) {
MOD_FILES.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
public void forEachModFile(Consumer<IModFile> fileConsumer) {
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
}
public static <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
return MOD_FILES.stream().map(IModFileInfo::getFile).map(function);
public <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
return modFiles.stream().map(IModFileInfo::getFile).map(function);
}
public static void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
public void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
indexedMods.forEach(modContainerConsumer);
}
public static void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
sortedContainers.forEach(containerConsumer);
public void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
this.sortedContainers.forEach(containerConsumer);
}
public static List<ModContainer> getLoadedMods() {
return sortedContainers;
public List<ModContainer> getLoadedMods() {
return this.sortedContainers;
}
public static <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
return indexedMods.values().stream().map(function);
}
}
@@ -5,11 +5,16 @@
package net.minecraftforge.fml;
import com.google.common.collect.ImmutableList;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.*;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModInfo;
@@ -66,18 +71,48 @@ import static net.minecraftforge.fml.Logging.LOADING;
* and completes the mod loading sequence.</dd>
* </dl>
*/
public final class ModLoader {
private ModLoader() {}
public class ModLoader {
private static final Logger LOGGER = LogManager.getLogger();
private static final ArrayList<ModLoadingException> LOADING_EXCEPTIONS = new ArrayList<>();
private static final ArrayList<ModLoadingWarning> LOADING_WARNINGS = new ArrayList<>();
private static final Consumer<String> STATUS_CONSUMER = StartupNotificationManager.modLoaderConsumer().orElse(_ -> {});
private static final HashSet<IModLoadingState> COMPLETED_STATES = new HashSet<>();
private final LoadingModList loadingModList;
private final List<ModLoadingException> loadingExceptions;
private final Set<IModInfo> erroredModInfos;
private final List<ModLoadingWarning> loadingWarnings;
private final ModStateManager stateManager;
private static boolean loadingStateValid;
private final Consumer<String> statusConsumer = StartupNotificationManager.modLoaderConsumer().orElse(msg -> {});
private final Set<IModLoadingState> completedStates = new HashSet<>();
private ModList modList;
private ModLoader() {
var loadingModList = this.loadingModList = FMLLoader.getLoadingModList();
var loadingExceptions = this.loadingExceptions = loadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.collect(Collectors.toList());
this.loadingWarnings = loadingModList.getBrokenFiles().stream()
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.collect(Collectors.toList());
if (loadingExceptions.isEmpty()) {
this.erroredModInfos = Collections.emptySet();
} else {
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
this.erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
loadingModList.getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
.forEach(this.loadingExceptions::add);
this.stateManager = new ModStateManager();
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
}
private static String computeLanguageList() {
return "\n" + LanguageLoadingProvider
return "\n" + FMLLoader.getLanguageLoadingProvider()
.applyForEach(lp -> lp.name() + '@' + lp.getClass().getPackage().getImplementationVersion())
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
@@ -91,51 +126,37 @@ public final class ModLoader {
collect(Collectors.joining("\n\t\t","\t\t",""));
}
public static ModLoader get() {
return LazyInit.INSTANCE;
}
private static final class LazyInit {
private static final ModLoader INSTANCE = new ModLoader();
private LazyInit() {}
}
/**
* Run on the primary starting thread by ClientModLoader and ServerModLoader
* @param syncExecutor An executor to run tasks on the main thread
* @param parallelExecutor An executor to run tasks on a parallel loading thread pool
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
*/
public static void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var loadingExceptions = LoadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.toList();
ModLoader.LOADING_EXCEPTIONS.addAll(loadingExceptions);
LOADING_WARNINGS.addAll(LoadingModList.getBrokenFiles().stream()
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.toList());
Set<IModInfo> erroredModInfos;
if (loadingExceptions.isEmpty()) {
erroredModInfos = Collections.emptySet();
} else {
erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
LoadingModList.getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
.forEach(LOADING_EXCEPTIONS::add);
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
ForgeFeature.registerFeature("javaVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version")));
ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion()));
loadingStateValid = true;
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
if (!LOADING_EXCEPTIONS.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", LOADING_EXCEPTIONS.getFirst());
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
ModList.clearLoadedMods();
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(LOADING_EXCEPTIONS);
throw new LoadingFailedException(loadingExceptions);
}
var failedBounds = new ArrayList<ForgeFeature.Bound>();
for (var mod : LoadingModList.getMods()) {
for (var mod : loadingModList.getMods()) {
for (var feature : mod.getForgeFeatures()) {
if (!ForgeFeature.testFeature(FMLEnvironment.dist, feature))
failedBounds.add(feature);
@@ -144,8 +165,8 @@ public final class ModLoader {
if (!failedBounds.isEmpty()) {
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
ModList.clearLoadedMods();
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(failedBounds.stream()
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb, ForgeFeature.featureValue(fb)))
@@ -153,18 +174,18 @@ public final class ModLoader {
}
final var modContainers = new HashMap<String, ModContainer>();
for (var file : LoadingModList.getModFiles()) {
var containers = buildMods(file.getFile());
for (var file : loadingModList.getModFiles()) {
var containers = this.buildMods(file.getFile());
for (var container : containers)
modContainers.put(container.getModId(), container);
}
if (!LOADING_EXCEPTIONS.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", LOADING_EXCEPTIONS.getFirst());
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
ModList.clearLoadedMods();
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(LOADING_EXCEPTIONS);
throw new LoadingFailedException(loadingExceptions);
}
// Gather all dependencies, so we can make sure they have finished their transition events before we fire ours
@@ -187,33 +208,34 @@ public final class ModLoader {
}
}
ModList.setLoadedMods(modContainers.values().stream().toList());
var stateList = ModStateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
modList.setLoadedMods(modContainers.values().stream().toList());
this.modList = modList;
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
progress.complete();
}
public static void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = ModStateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
progress.complete();
}
public static void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = ModStateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
var modBusGroups = Set.of(
ModList.getLoadedMods().stream()
modList.getLoadedMods().stream()
.map(ModContainer::getModBusGroup)
.filter(Objects::nonNull)
.toArray(BusGroup[]::new)
@@ -223,11 +245,11 @@ public final class ModLoader {
}
parallelExecutor.execute(BusGroup.DEFAULT::trim);
STATUS_CONSUMER.accept("Mod loading complete - %d mods loaded".formatted(ModList.size()));
statusConsumer.accept("Mod loading complete - %d mods loaded".formatted(this.modList.size()));
progress.complete();
}
private static void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
if (!isLoadingStateValid()) {
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
@@ -235,25 +257,25 @@ public final class ModLoader {
progressBar.label(progressBar.name() + " working");
syncExecutor.drive(ticker);
var inlineRunnable = state.inlineRunnable();
var inlineRunnable = state.inlineRunnable().orElse(null);
if (inlineRunnable != null) handleInlineTransition(inlineRunnable, state, syncExecutor, ticker);
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar);
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar).orElse(null);
if (transition != null) waitForTransition(state, syncExecutor, ticker, transition);
COMPLETED_STATES.add(state);
completedStates.add(state);
}
private static void handleInlineTransition(final Runnable transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
var pb = StartupMessageManager.addProgressBar("State transition " + state.name() + " running", 0);
syncExecutor.drive(ticker);
transition.run();
transition.accept(this.modList);
syncExecutor.drive(ticker);
pb.complete();
syncExecutor.drive(ticker);
}
private static void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
while (!transition.isDone()) {
syncExecutor.drive(ticker);
}
@@ -267,7 +289,7 @@ public final class ModLoader {
.anyMatch(obj -> !(obj instanceof ModLoadingException));
if (hasNotModLoadingEx) {
LOGGER.fatal("Encountered non-modloading exceptions!", e);
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
statusConsumer.accept("ERROR DURING MOD LOADING");
throw e;
}
@@ -276,12 +298,12 @@ public final class ModLoader {
.map(ModLoadingException.class::cast)
.toList();
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
statusConsumer.accept("ERROR DURING MOD LOADING");
throw new LoadingFailedException(modLoadingExceptions);
}
}
private static List<ModContainer> buildMods(final IModFile modFile) {
private List<ModContainer> buildMods(final IModFile modFile) {
final Map<String, IModInfo> modInfoMap = new HashMap<>();
for (var mod : modFile.getModFileInfo().getMods())
modInfoMap.put(mod.getModId(), mod);
@@ -311,14 +333,14 @@ public final class ModLoader {
missingMods.removeAll(modIds);
LOGGER.fatal(LOADING, "The following mods are missing, but have classes in the jar: {}", missingMods);
LOADING_EXCEPTIONS.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
}
// remove errored mod containers
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
}
private static ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
final String modId, final IModLanguageProvider.IModLanguageLoader languageLoader
) {
try {
@@ -330,7 +352,7 @@ public final class ModLoader {
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
} catch (ModLoadingException mle) {
// exceptions are caught and added to the error list for later handling
LOADING_EXCEPTIONS.add(mle);
loadingExceptions.add(mle);
// return an errored container instance here, because we tried and failed building a container.
return new ErroredModContainer();
}
@@ -344,8 +366,15 @@ public final class ModLoader {
return loadingStateValid;
}
public static boolean hasCompletedState(IModLoadingState state) {
return COMPLETED_STATES.contains(state);
/** @deprecated Use {@link #hasCompletedState(IModLoadingState)} instead */
@Deprecated(forRemoval = true)
public boolean hasCompletedState(final String stateName) {
IModLoadingState state = stateManager.findState(stateName);
return completedStates.contains(state);
}
public boolean hasCompletedState(IModLoadingState state) {
return completedStates.contains(state);
}
public static <T extends IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
@@ -353,7 +382,7 @@ public final class ModLoader {
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
return;
}
for (var mod : ModList.getLoadedMods())
for (var mod : ModList.get().getLoadedMods())
mod.acceptEvent(generator.apply(mod));
}
@@ -362,7 +391,7 @@ public final class ModLoader {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
for (var mod : ModList.getLoadedMods())
for (var mod : ModList.get().getLoadedMods())
mod.acceptEvent(e);
}
@@ -371,7 +400,7 @@ public final class ModLoader {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return e;
}
for (var mod : ModList.getLoadedMods())
for (var mod : ModList.get().getLoadedMods())
mod.acceptEvent(e);
return e;
}
@@ -379,8 +408,8 @@ public final class ModLoader {
@SuppressWarnings("removal")
public static <T extends IModBusEvent> void postEventWrapContainerInModOrder(T event) {
postEventWithWrapInModOrder(event,
(mc, _) -> ModLoadingContext.get().setActiveContainer(mc),
(_, _) -> ModLoadingContext.get().setActiveContainer(null)
(mc, e) -> ModLoadingContext.get().setActiveContainer(mc),
(mc, e) -> ModLoadingContext.get().setActiveContainer(null)
);
}
@@ -389,19 +418,19 @@ public final class ModLoader {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
for (var mod : ModList.getLoadedMods()) {
for (var mod : ModList.get().getLoadedMods()) {
pre.accept(mod, e);
mod.acceptEvent(e);
post.accept(mod, e);
}
}
public static List<ModLoadingWarning> getWarnings() {
return List.copyOf(LOADING_WARNINGS);
public List<ModLoadingWarning> getWarnings() {
return ImmutableList.copyOf(this.loadingWarnings);
}
public static void addWarning(ModLoadingWarning warning) {
LOADING_WARNINGS.add(warning);
public void addWarning(ModLoadingWarning warning) {
this.loadingWarnings.add(warning);
}
private static boolean runningDataGen = false;
@@ -32,11 +32,24 @@ public class ModLoadingContext {
return context.get();
}
void setActiveContainer(final ModContainer container) {
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@Deprecated(forRemoval = true, since="1.21.1")
public void setActiveContainer(final ModContainer container) {
this.activeContainer = container;
this.languageExtension = container == null ? null : container.contextExtension.get();
}
/**
* Going to be moved to ForgeHooks for Internal use.
* @deprecated Override/Use {@link ModLoadingContext#getContainer()}
*/
@Deprecated(forRemoval = true, since="1.21.1")
public ModContainer getActiveContainer() {
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
}
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@@ -45,10 +58,11 @@ public class ModLoadingContext {
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
}
/**
* @return {@link ModLoadingContext#getActiveContainer()} by default.
*/
public ModContainer getContainer() {
return activeContainer == null
? ModList.getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!"))
: activeContainer;
return getActiveContainer();
}
/**
@@ -100,7 +114,8 @@ public class ModLoadingContext {
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
if (spec.isEmpty()) {
if (spec.isEmpty())
{
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getContainer().getModId());
return;
@@ -110,7 +125,8 @@ public class ModLoadingContext {
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
if (spec.isEmpty()) {
if (spec.isEmpty())
{
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getContainer().getModId(), fileName);
return;
@@ -119,6 +135,7 @@ public class ModLoadingContext {
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
}
@SuppressWarnings("unchecked")
public <T> T extension() {
return (T)languageExtension;
@@ -6,14 +6,14 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
/**
* Implementation of the {@link IModLoadingState} interface.
@@ -25,26 +25,27 @@ import java.util.function.Supplier;
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
* @param transition optional state transition information
*/
@NullMarked
public record ModLoadingState(
String name,
String previous,
Supplier<String> message,
IntSupplier size,
Function<ModList, String> message,
ToIntFunction<ModList> size,
ModLoadingPhase phase,
@Nullable Runnable inlineRunnable,
@Nullable IModStateTransition transition
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition
) implements IModLoadingState {
@Override
public @Nullable CompletableFuture<Void> buildTransition(
public Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
if (transition == null) return null;
return transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask);
var transition = this.transition.orElse(null);
return transition == null
? Optional.empty()
: Optional.ofNullable(transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
}
/**
@@ -59,7 +60,7 @@ public record ModLoadingState(
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return new ModLoadingState(name, previous, () -> "", () -> 0, phase, null, null);
return new ModLoadingState(name, previous, ml -> "", ml -> 0, phase, Optional.empty(), Optional.empty());
}
/**
@@ -93,7 +94,7 @@ public record ModLoadingState(
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withTransition(final String name, final String previous,
final Supplier<String> message, final ModLoadingPhase phase,
final Function<ModList, String> message, final ModLoadingPhase phase,
final IModStateTransition transition) {
return of(name, phase).after(previous).message(message).withTransition(transition);
}
@@ -112,7 +113,7 @@ public record ModLoadingState(
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
final Runnable inline) {
final Consumer<ModList> inline) {
return of(name, phase).after(previous).withInline(inline);
}
@@ -125,23 +126,23 @@ public record ModLoadingState(
return o instanceof ModLoadingState that
&& this.phase == that.phase
&& this.name.equals(that.name)
&& this.previous.equals(that.previous);
&& Objects.equals(this.previous, that.previous);
}
@Override
public int hashCode() {
int result = phase.hashCode();
result = 31 * result + name.hashCode();
result = 31 * result + previous.hashCode();
result = 31 * result + Objects.hashCode(previous);
return result;
}
public static final class Builder {
public static class Builder {
private final String name;
private final ModLoadingPhase phase;
private String after = "";
private @Nullable Supplier<String> message = null;
private @Nullable IntSupplier size = null;
private String after;
private Function<ModList, String> message = null;
private ToIntFunction<ModList> size = null;
private Builder(final String name, final ModLoadingPhase phase) {
this.name = name;
@@ -154,28 +155,28 @@ public record ModLoadingState(
return this;
}
public Builder message(final String value) { return message(() -> value); }
public Builder message(final Supplier<String> value) {
public Builder message(final String value) { return message(ml -> value); }
public Builder message(final Function<ModList, String> value) {
this.message = value;
return this;
}
public Builder size(final int value) { return size(() -> value); }
public Builder size(final IntSupplier value) {
public Builder size(final int value) { return size(ml -> value); }
public Builder size(final ToIntFunction<ModList> value) {
this.size = value;
return this;
}
public ModLoadingState empty() {
return new ModLoadingState(name, after, message != null ? message : () -> "", size != null ? size : () -> 0, phase, null, null);
return new ModLoadingState(name, after, message != null ? message : ml -> "", size != null ? size : ml -> 0, phase, Optional.empty(), Optional.empty());
}
public ModLoadingState withTransition(final IModStateTransition transition) {
return new ModLoadingState(name, after, message != null ? message : () -> "Processing transition " + name, size != null ? size : ModList::size, phase, null, transition);
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing transition " + name, size != null ? size : ModList::size, phase, Optional.empty(), Optional.of(transition));
}
public ModLoadingState withInline(final Runnable inline) {
return new ModLoadingState(name, after, message != null ? message : () -> "Processing work " + name, size != null ? size : () -> 0, phase, inline, null);
public ModLoadingState withInline(final Consumer<ModList> inline) {
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing work " + name, size != null ? size : ml -> 0, phase, Optional.of(inline), Optional.empty());
}
}
}
}
@@ -5,27 +5,41 @@
package net.minecraftforge.fml;
import com.google.common.collect.Streams;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
* @param modInfo Mod info for mod with warning
* @param warningStage The stage where this warning was encountered
* @param i18nMessage I18N message to use for display
* @param context Context for message display
*/
public record ModLoadingWarning(IModInfo modInfo, ModLoadingStage warningStage, String i18nMessage, List<Object> context) {
public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) {
this(modInfo, warningStage, i18nMessage, List.of(context));
}
public class ModLoadingWarning {
/**
* Mod Info for mod with warning
*/
private final IModInfo modInfo;
/**
* The stage where this warning was encountered
*/
private final ModLoadingStage warningStage;
public ModLoadingWarning {
context = List.copyOf(context);
/**
* I18N message to use for display
*/
private final String i18nMessage;
/**
* Context for message display
*/
private final List<Object> context;
public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) {
this.modInfo = modInfo;
this.warningStage = warningStage;
this.i18nMessage = i18nMessage;
this.context = Arrays.asList(context);
}
public String formatToString() {
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Stream.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
}
}
@@ -15,19 +15,18 @@ import java.util.function.Function;
import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public final class ModStateManager {
private ModStateManager() {}
private static final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
public class ModStateManager {
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
static {
public ModStateManager() {
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, _ -> {})
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
.map(IModStateProvider::getAllStates)
.flatMap(List::stream)
.collect(Collectors.groupingBy(IModLoadingState::phase, () -> new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
}
public static List<IModLoadingState> getStates(final ModLoadingPhase phase) {
public List<IModLoadingState> getStates(final ModLoadingPhase phase) {
var nodes = stateMap.get(phase);
var lookup = nodes.stream().collect(Collectors.toMap(IModLoadingState::name, Function.identity()));
@@ -41,7 +40,7 @@ public final class ModStateManager {
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
}
public static IModLoadingState findState(final String stateName) {
public IModLoadingState findState(final String stateName) {
var result = stateMap.values()
.stream()
.flatMap(List::stream)
@@ -24,7 +24,7 @@ import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
@ApiStatus.Internal
final class ModStateTransitionHelper {
class ModStateTransitionHelper {
static final IModStateTransition NOOP = new NoopTransition();
record NoopTransition() implements IModStateTransition {
@Override
@@ -62,30 +62,7 @@ final class ModStateTransitionHelper {
}
}
/// Same as [#completableFutureFromExceptionList(List)] but specialised for a single future
static <V> CompletionStage<Void> completableFutureFromException(FutureResult<V> t) {
if (t.exception() == null) {
return CompletableFuture.completedFuture(null);
} else {
var exception = t.exception();
CompletableFuture<Void> cf = new CompletableFuture<>();
RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
} else {
accumulator.addSuppressed(exception);
}
return cf;
}
}
record FutureResult<V>(V value, Throwable exception) {}
record FutureResult<V>(V value, Throwable exception){}
static <V> CompletableFuture<List<FutureResult<V>>> gather(Collection<? extends CompletableFuture<? extends V>> futures) {
var list = new ArrayList<FutureResult<V>>(futures.size());
@@ -99,17 +76,25 @@ final class ModStateTransitionHelper {
results[i] = raw.whenComplete((result, exception) -> list.set(i, new FutureResult<>(result, exception)));
}
return CompletableFuture.allOf(results).handle((_, _)->null).thenApply(_ -> list);
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
}
private static <T extends IModBusEvent> CompletableFuture<Void> addCompletableFutureTaskForModDispatch(
private static <T extends IModBusEvent> void addCompletableFutureTaskForModDispatch(
final IModStateTransition transition,
final Executor executor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
) {
@SuppressWarnings("removal")
var preDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (preDispatchHook != null)
completableFutures.add(preDispatchHook);
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
for (var mod : ModList.getLoadedMods()) {
for (var mod : ModList.get().getLoadedMods()) {
CompletableFuture<Void> parent = null;
if (mod.dependencies.isEmpty()) {
@@ -135,7 +120,7 @@ final class ModStateTransitionHelper {
handler.run();
mod.acceptEvent(eventGenerator.apply(mod));
}, executor)
.whenComplete((_, exception) -> {
.whenComplete((mc, exception) -> {
mod.modLoadingStage = nextState.apply(mod.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
@@ -144,7 +129,20 @@ final class ModStateTransitionHelper {
modFutures.put(mod.getModId(), dispatch);
}
return gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
var dispatch = gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
completableFutures.add(dispatch);
@SuppressWarnings("removal")
var postDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (postDispatchHook != null)
completableFutures.add(postDispatchHook);
}
private static <T extends IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
if (hook == null || hook == IModStateTransition.NULL_HOOK) return null;
@SuppressWarnings("unchecked")
var hookTyped = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>)hook;
return hookTyped.apply(executor, eventGenerator);
}
static <T extends IModBusEvent> CompletableFuture<Void> build(
@@ -156,25 +154,31 @@ final class ModStateTransitionHelper {
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
CompletableFuture<Void> future;
List<CompletableFuture<Void>> futures = new ArrayList<>();
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenRunAsync(() -> progressBar.label(progressBar.name() + ": dispatching " + name), parallelExecutor);
var eventGenerator = transition.eventFunction();
if (eventGenerator != null) {
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = transition.nextModLoadingStage();
future = addCompletableFutureTaskForModDispatch(executor, progressBar, eventGenerator, state);
final CompletableFuture<Void> eventDispatchCF = future.handle(FutureResult::new)
.thenCompose(ModStateTransitionHelper::completableFutureFromException);
postEventDispatchCF = postEventDispatchCF.thenComposeAsync(_ -> eventDispatchCF, parallelExecutor);
@SuppressWarnings({ "removal", "unchecked" })
var events = transition.eventFunctionStream().get().map(f -> (EventGenerator<T>)f).toList();
for (int x = 0; x < events.size(); x++) {
var gen = events.get(x);
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = x == events.size() - 1
? transition.nextModLoadingStage()
: ModLoadingStage::currentState;
addCompletableFutureTaskForModDispatch(transition, executor, futures, progressBar, gen, state);
}
postEventDispatchCF = postEventDispatchCF.thenRun(() -> postSyncTask.apply(syncExecutor));
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = gather(futures).thenCompose(ModStateTransitionHelper::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching " + name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
}
}
@@ -14,12 +14,25 @@ import java.util.concurrent.locks.LockSupport;
import static net.minecraftforge.fml.Logging.LOADING;
public final class ModWorkManager {
public class ModWorkManager {
private static final Logger LOGGER = LogManager.getLogger();
private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(1);
public sealed interface DrivenExecutor extends Executor {
void drive(Runnable ticker);
public interface DrivenExecutor extends Executor {
boolean selfDriven();
boolean driveOne();
default void drive(Runnable ticker) {
if (!selfDriven()) {
ticker.run();
while (true) {
if (!driveOne()) break;
}
} else {
// park for a bit so other threads can schedule
LockSupport.parkNanos(PARK_TIME);
}
}
}
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
@@ -27,7 +40,8 @@ public final class ModWorkManager {
this(new ConcurrentLinkedDeque<>());
}
private boolean driveOne() {
@Override
public boolean driveOne() {
final Runnable task = tasks.pollFirst();
if (task != null) {
task.run();
@@ -36,29 +50,30 @@ public final class ModWorkManager {
}
@Override
public void execute(final Runnable command) {
tasks.addLast(command);
public boolean selfDriven() {
return false;
}
@Override
public void drive(Runnable ticker) {
ticker.run();
while (true) {
if (!driveOne()) break;
}
public void execute(final Runnable command) {
tasks.addLast(command);
}
}
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
@Override
public void execute(final Runnable command) {
wrapped.execute(command);
public boolean selfDriven() {
return true;
}
@Override
public void drive(Runnable ticker) {
// Park for a bit so other threads can schedule
LockSupport.parkNanos(PARK_TIME);
public boolean driveOne() {
return false;
}
@Override
public void execute(final Runnable command) {
wrapped.execute(command);
}
}
@@ -43,7 +43,7 @@ public final class OptionalMod<T>
private T getValue()
{
if (!searched) {
this.value = ModList.<T>getModObjectById(this.modId).orElse(null);
this.value = ModList.get().<T>getModObjectById(this.modId).orElse(null);
searched = true;
}
return this.value;
@@ -9,8 +9,8 @@ import java.util.concurrent.Executor;
import java.util.function.BinaryOperator;
public enum ThreadSelector implements BinaryOperator<Executor> {
SYNC((sync, _) -> sync),
PARALLEL((_, parallel) -> parallel);
SYNC((sync, parallel) -> sync),
PARALLEL((sync, parallel) -> parallel);
private final BinaryOperator<Executor> selector;
@@ -93,8 +93,8 @@ public class VersionChecker {
return;
}
var mc = ModList.getModContainerById("minecraft");
var forge = ModList.getModContainerById("forge");
var mc = ModList.get().getModContainerById("minecraft");
var forge = ModList.get().getModContainerById("forge");
this.mcVersion = mc.get().getModInfo().getVersion().toString();
this.agent = "Java-http-client/" + System.getProperty("java.version")
@@ -223,7 +223,7 @@ public class VersionChecker {
// Gather a list of mods that have opted in to this update system by providing a URL.
private static List<IModInfo> gatherMods() {
List<IModInfo> ret = new LinkedList<>();
for (IModInfo info : ModList.getMods()) {
for (IModInfo info : ModList.get().getMods()) {
if (info.getUpdateURL().isPresent())
ret.add(info);
}
@@ -8,7 +8,8 @@ package net.minecraftforge.fml.config;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.loading.FMLPaths;
import org.slf4j.Logger;
import org.slf4j.Marker;
@@ -18,54 +19,57 @@ import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public final class ConfigTracker {
private ConfigTracker() {}
public class ConfigTracker {
private static final Logger LOGGER = LogUtils.getLogger();
static final Marker CONFIG = MarkerFactory.getMarker("CONFIG");
private static final ConcurrentHashMap<String, ModConfig> FILE_MAP = new ConcurrentHashMap<>();
private static final Map<ModConfig.Type, Set<ModConfig>> CONFIG_SETS = Map.of(
ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()),
ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()),
ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>())
);
private static final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> CONFIGS_BY_MOD = new ConcurrentHashMap<>();
public static final ConfigTracker INSTANCE = new ConfigTracker();
private final ConcurrentHashMap<String, ModConfig> fileMap;
private final EnumMap<ModConfig.Type, Set<ModConfig>> configSets;
private final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> configsByMod;
static void trackConfig(final ModConfig config) {
if (FILE_MAP.containsKey(config.getFileName())) {
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), FILE_MAP.get(config.getFileName()).getModId(), config.getModId());
private ConfigTracker() {
this.fileMap = new ConcurrentHashMap<>();
this.configSets = new EnumMap<>(ModConfig.Type.class);
this.configsByMod = new ConcurrentHashMap<>();
this.configSets.put(ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()));
this.configSets.put(ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()));
// this.configSets.put(ModConfig.Type.PLAYER, new ConcurrentSkipListSet<>());
this.configSets.put(ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>()));
}
void trackConfig(final ModConfig config) {
if (this.fileMap.containsKey(config.getFileName())) {
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), this.fileMap.get(config.getFileName()).getModId(), config.getModId());
throw new RuntimeException("Config conflict detected!");
}
FILE_MAP.put(config.getFileName(), config);
CONFIG_SETS.get(config.getType()).add(config);
CONFIGS_BY_MOD.computeIfAbsent(config.getModId(), _ -> new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
this.fileMap.put(config.getFileName(), config);
this.configSets.get(config.getType()).add(config);
this.configsByMod.computeIfAbsent(config.getModId(), (k)->new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
LOGGER.debug(CONFIG, "Config file {} for {} tracking", config.getFileName(), config.getModId());
}
public static void loadConfigs(ModConfig.Type type, Path configBasePath) {
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Loading configs type {}", type);
for (ModConfig config : CONFIG_SETS.get(type)) {
for (ModConfig config : this.configSets.get(type)) {
openConfig(config, configBasePath);
}
}
// TODO: [FML] This is only called for the server (outside of forceUnload)
// rethink config implementation for eventual FML rewrite
public static void unloadConfigs(ModConfig.Type type, Path configBasePath) {
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
for (ModConfig config : CONFIG_SETS.get(type)) {
for (ModConfig config : this.configSets.get(type)) {
closeConfig(config, configBasePath);
}
ConfigFileTypeHandler.get(type).stopWatcher();
}
// If there is a better way to do this, please tell me. Because this is FUCKED
public static void forceUnload() {
public void forceUnload() {
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
if (FMLEnvironment.dist.isClient())
unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get());
unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get()));
this.unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
// just in case server watcher is still alive somehow...
ConfigFileTypeHandler.get(ModConfig.Type.SERVER).stopWatcher();
@@ -92,8 +96,8 @@ public final class ConfigTracker {
}
}
public static void loadDefaultServerConfigs() {
CONFIG_SETS.get(ModConfig.Type.SERVER).forEach(modConfig -> {
public void loadDefaultServerConfigs() {
configSets.get(ModConfig.Type.SERVER).forEach(modConfig -> {
final CommentedConfig commentedConfig = CommentedConfig.inMemory();
modConfig.getSpec().correct(commentedConfig);
modConfig.setConfigData(commentedConfig);
@@ -101,16 +105,16 @@ public final class ConfigTracker {
});
}
public static String getConfigFileName(String modId, ModConfig.Type type) {
return Optional.ofNullable(CONFIGS_BY_MOD.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
public String getConfigFileName(String modId, ModConfig.Type type) {
return Optional.ofNullable(configsByMod.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
map(ModConfig::getFullPath).map(Object::toString).orElse(null);
}
public static Map<ModConfig.Type, Set<ModConfig>> configSets() {
return CONFIG_SETS;
public Map<ModConfig.Type, Set<ModConfig>> configSets() {
return configSets;
}
public static ConcurrentHashMap<String, ModConfig> fileMap() {
return FILE_MAP;
public ConcurrentHashMap<String, ModConfig> fileMap() {
return fileMap;
}
}
@@ -13,7 +13,6 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
public interface IConfigEvent {
// TODO: [FML] Get rid of this indirection somehow
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading, @Nullable Function<ModConfig, IConfigEvent> unloading) {}
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
@@ -13,6 +13,8 @@ import net.minecraftforge.fml.loading.StringUtils;
import java.io.ByteArrayInputStream;
import java.nio.file.Path;
import java.util.Locale;
import java.util.concurrent.Callable;
public class ModConfig
{
@@ -29,7 +31,7 @@ public class ModConfig
this.fileName = fileName;
this.container = container;
this.configHandler = ConfigFileTypeHandler.get(type);
ConfigTracker.trackConfig(this);
ConfigTracker.INSTANCE.trackConfig(this);
}
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer activeContainer) {
@@ -38,9 +40,8 @@ public class ModConfig
private static String defaultConfigName(Type type, String modId) {
// config file name would be "forge-client.toml" and "forge-server.toml"
return modId + '-' + type.extension() + ".toml";
return String.format(Locale.ROOT, "%s-%s.toml", modId, type.extension());
}
public Type getType() {
return type;
}
+13 -13
View File
@@ -54,10 +54,10 @@ dependencies {
// See gradle/gradle#35070 (https://github.com/gradle/gradle/issues/35070)
// We are making the list of providers first BEFORE mapping them to a provider of the resolved objects so that they can be finalized after configuration.
var lwjglTestLibs = [
dependencies.variantOf(earlyDisplayTestLibs.lwjgl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.glfw) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.opengl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.stb) { classifier lwjglNatives }
dependencies.variantOf(earlyDisplayTestLibs.lwjgl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.glfw) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.opengl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.stb) { classifier lwjglNatives }
].with { list ->
provider { list.collect { it.get() } }
}
@@ -79,16 +79,16 @@ tasks.withType(JavaCompile).configureEach {
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer' : 'boot'
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': '1.0'
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': '1.0'
], 'net/minecraftforge/fml/earlydisplay/')
}
}
@@ -114,4 +114,4 @@ publishing {
}
}
}
}
}
@@ -46,6 +46,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
@@ -68,7 +70,7 @@ import static org.lwjgl.opengl.GL32C.*;
* Based on the prior ClientVisualization, with some personal touches.
*/
public class DisplayWindow implements ImmediateWindowProvider {
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}};
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}, {3,2}};
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true);
@@ -113,14 +115,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
return "fmlearlywindow";
}
@Override
public ImmediateWindowProvider selectBackend(String backend) {
// We only support opengl
if ("default".equals(backend) || "opengl".equals(backend))
return this;
return ImmediateWindowProvider.getFallbackHandler();
}
@Override
public Runnable initialize(String[] arguments) {
String mcVersion = FMLLoader.versionInfo().mcVersion();
@@ -145,7 +139,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.colourScheme = ColourScheme.BLACK;
} else {
try {
// check the options file for the color scheme
// check the options file for the colour scheme
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Path.of("options.txt")));
var keyName = "darkMojangStudiosBackground:";
for (String line : optionLines) {
@@ -320,18 +314,17 @@ public class DisplayWindow implements ImmediateWindowProvider {
LOGGER.error("ERROR DISPLAY\n"+msgBuilder);
// we show the display on a new dedicated thread
Executors.newSingleThreadExecutor().submit(()-> {
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", 0);
if (res != 0) {
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", false);
if (res) {
try {
Desktop.getDesktop().browse(URI.create(ERROR_URL));
} catch (IOException ioe) {
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", 0);
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", false);
}
}
System.exit(1);
});
}
/**
* Called to initialize the window when preparing for the Render Thread.
*
@@ -467,9 +460,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.winWidth = x[0];
this.winHeight = y[0];
// Setting the window position isn't supported on wayland, so check the error here
glfwSetWindowPos(window, (vidmode.width() - this.winWidth) / 2 + monitorX, (vidmode.height() - this.winHeight) / 2 + monitorY);
handleLastGLFWError();
// Attempt setting the icon
// int[] channels = new int[1];
@@ -492,10 +483,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
// Show the window
glfwShowWindow(window);
// Getting the window position isn't supported on wayland, so check the error here
glfwGetWindowPos(window, x, y);
handleLastGLFWError();
this.winX = x[0];
this.winY = y[0];
glfwGetFramebufferSize(window, x, y);
@@ -504,18 +492,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
glfwPollEvents();
}
private static void handleLastGLFWError() {
handleLastGLFWError((error, description) -> {
if (error == GLFW_FEATURE_UNAVAILABLE) {
// suppress window pos errors for unsupported platforms (wayland)
LOGGER.debug(String.format("Suppressing GLFW error: [0x%X]%s", error, description));
return;
}
throw new IllegalStateException(String.format("GLFW error: [0x%X]%s", error, description));
});
}
private void winResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
this.winWidth = width;
@@ -553,7 +529,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
*
* @return the Window we own.
*/
public long setupMinecraftWindow(final int width, final int height, final String title, final long monitorSupplier, final Supplier<Object> backend) {
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
// wait for the window to actually be initialized
try {
this.initializationFuture.get(30, TimeUnit.SECONDS);
@@ -573,11 +549,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
var renderlockticket = false;
do {
try {
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
if (++tries > 9) {
Thread.dumpStack();
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
}
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
if (++tries > 9) {
Thread.dumpStack();
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
}
} catch (InterruptedException e) {
Thread.interrupted();
}
@@ -587,7 +563,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
glfwMakeContextCurrent(window);
// Set the title to what the game wants
glfwSetWindowTitle(window, title);
glfwSetWindowTitle(window, title.get());
glfwSwapInterval(0);
// Clean up our hooks
glfwSetFramebufferSizeCallback(window, null).free();
@@ -673,4 +649,4 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.context.elementShader().close();
SimpleBufferBuilder.destroy();
}
}
}
@@ -116,7 +116,7 @@ public class RenderElement {
}
public static RenderElement forgeVersionOverlay(SimpleFont font, String version) {
return new RenderElement(RenderElement.initializeText(font, (bb, _, ctx)->
return new RenderElement(RenderElement.initializeText(font, (bb, fnt, ctx)->
font.generateVerticesForTexts(ctx.scaledWidth() - font.stringWidth(version) - 10,
ctx.scaledHeight() - font.lineSpacing() + font.descent() - 10, bb,
new SimpleFont.DisplayText(version, ctx.colourScheme.foreground().packedint(RenderElement.globalAlpha)))));
@@ -178,11 +178,11 @@ public class RenderElement {
var colour = (alpha << 24) | 0xFFFFFF;
Renderer bar;
if (pm.steps() == 0) {
bar = progressBar(ctx->new int[] {(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, _->colour, frame -> indeterminateBar(frame, cnt == 0));
bar = progressBar(ctx->new int[] {(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, f->colour, frame -> indeterminateBar(frame, cnt == 0));
} else {
bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, _ -> colour, _ -> new float[]{0f, pm.progress()});
bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, f -> colour, f -> new float[]{0f, pm.progress()});
}
Renderer label = (bb, ctx, _) -> renderText(font, text((ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, pm.label().getText(), colour), bb, ctx);
Renderer label = (bb, ctx, frame) -> renderText(font, text((ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, pm.label().getText(), colour), bb, ctx);
return bar.then(label);
}
private static float[] indeterminateBar(int frame, boolean isActive) {
@@ -198,9 +198,9 @@ public class RenderElement {
var y = 10 * context.scale();
PerformanceInfo pi = context.performance();
final int colour = hsvToRGB((1.0f - (float)Math.pow(pi.memory(), 1.5f)) / 3f, 1.0f, 0.5f);
var bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, BAR_WIDTH * ctx.scale()}, _ -> colour, _ -> new float[]{0f, pi.memory()});
var bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, BAR_WIDTH * ctx.scale()}, f -> colour, f -> new float[]{0f, pi.memory()});
var width = font.stringWidth(pi.text());
Renderer label = (bb, ctx, _) -> renderText(font, text(ctx.scaledWidth() / 2 - width / 2, y + 18, pi.text(), context.colourScheme.foreground().packedint(globalAlpha)), bb, ctx);
Renderer label = (bb, ctx, frame) -> renderText(font, text(ctx.scaledWidth() / 2 - width / 2, y + 18, pi.text(), context.colourScheme.foreground().packedint(globalAlpha)), bb, ctx);
bar.then(label).accept(buffer, context, frameNumber);
}
@@ -250,7 +250,7 @@ public class RenderElement {
}
private static Renderer initializeText(SimpleFont font, TextGenerator textGenerator) {
return (bb, context, _) -> renderText(font, textGenerator, bb, context);
return (bb, context, frame) -> renderText(font, textGenerator, bb, context);
}
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
@@ -262,7 +262,7 @@ public class RenderElement {
}
private static TextGenerator text(int x, int y, String text, int colour) {
return (bb, font, _) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
return (bb, font, context) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
}
private static Renderer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
+13 -14
View File
@@ -59,7 +59,6 @@ dependencies {
implementation(libs.accesstransformers ){ transitive = false }
implementation(libs.terminalconsoleappender){ transitive = false }
implementation(libs.jimfs ){ transitive = false }
implementation(libs.roimfs ){ transitive = false }
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
implementation(libs.log4j.core ){ transitive = false }
@@ -80,16 +79,16 @@ tasks.withType(JavaCompile).configureEach {
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer' : 'boot'
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
], 'net/minecraftforge/fml/loading/')
}
}
@@ -168,11 +167,11 @@ tasks.register('jarJarOptionsJson', JarJarMetadataOptions) {
tasks.named('generateResources') {
dependsOn(
tasks.named('eclipseJdt'),
tasks.named('eclipseJdtApt'),
tasks.named('eclipseFactorypath'),
tasks.named('writeForgeVersionJson', WriteForgeVersionJson),
tasks.named('jarJarOptionsJson')
tasks.named('eclipseJdt'),
tasks.named('eclipseJdtApt'),
tasks.named('eclipseFactorypath'),
tasks.named('writeForgeVersionJson', WriteForgeVersionJson),
tasks.named('jarJarOptionsJson')
)
}
@@ -49,7 +49,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
private static final class LazyInit {
private LazyInit() {}
private static final boolean ANY_MODS = LoadingModList.getMods().size() > 2; // 2: forge, minecraft
private static final boolean ANY_MODS = LoadingModList.get().getMods().size() > 2; // 2: forge, minecraft
}
@Override
@@ -109,7 +109,7 @@ public class FMLConfig
}
if (!configSpec.isCorrect(configData)) {
LOGGER.warn(CORE, "Configuration file {} is not correct. Correcting", configFile);
configSpec.correct(configData, (_, path, incorrectValue, correctedValue) ->
configSpec.correct(configData, (action, path, incorrectValue, correctedValue) ->
LOGGER.info(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
}
configData.putAllComments(configComments);
@@ -20,8 +20,8 @@ public class FMLEnvironment
public static final boolean secureJarsEnabled = FMLLoader.isSecureJarEnabled();
static void setupInteropEnvironment(IEnvironment environment) {
environment.computePropertyIfAbsent(IEnvironment.Keys.NAMING.get(), _->naming);
environment.computePropertyIfAbsent(Environment.Keys.DIST.get(), _->dist);
environment.computePropertyIfAbsent(IEnvironment.Keys.NAMING.get(), v->naming);
environment.computePropertyIfAbsent(Environment.Keys.DIST.get(), v->dist);
}
public static class Keys {
@@ -30,11 +30,13 @@ import java.util.function.BiFunction;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
public final class FMLLoader {
public class FMLLoader {
private static final Logger LOGGER = LogUtils.getLogger();
private static AccessTransformerService accessTransformer;
private static LanguageLoadingProvider languageLoadingProvider;
private static Dist dist;
private static String naming;
private static LoadingModList loadingModList;
private static RuntimeDistCleaner runtimeDistCleaner;
private static Path gamePath;
private static final VersionInfo versionInfo = VersionInfo.detect();
@@ -149,11 +151,16 @@ public final class FMLLoader {
public static List<ITransformationService.Resource> completeScan(IModuleLayerManager layerManager) {
moduleLayerManager = layerManager;
LanguageLoadingProvider.reload();
languageLoadingProvider = new LanguageLoadingProvider();
backgroundScanHandler = modValidator.stage2Validation();
loadingModList = backgroundScanHandler.getLoadingModList();
return List.of(modValidator.getModResources());
}
public static LanguageLoadingProvider getLanguageLoadingProvider() {
return languageLoadingProvider;
}
public static CommonLaunchHandler getLaunchHandler() {
return commonLaunchHandler;
}
@@ -173,6 +180,10 @@ public final class FMLLoader {
progressWindowTick.run();
}
public static LoadingModList getLoadingModList() {
return loadingModList;
}
public static Path getGamePath() {
return gamePath;
}
@@ -12,6 +12,7 @@ import net.minecraftforge.forgespi.Environment;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -41,7 +42,7 @@ public class FMLServiceProvider implements ITransformationService {
LOGGER.debug(CORE, "Loading configuration");
FMLConfig.load();
LOGGER.debug(CORE, "Preparing ModFile");
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), _->ModFile::new);
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
LOGGER.debug(CORE, "Preparing launch handler");
FMLLoader.setupLaunchHandler(environment, arguments);
FMLEnvironment.setupInteropEnvironment(environment);
@@ -10,13 +10,8 @@ import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import joptsimple.OptionParser;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
@@ -46,64 +41,18 @@ public class ImmediateWindowHandler {
if (provider == null) {
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
provider = new DummyProvider();
} else {
var backend = findBackend(arguments);
var newProvider = provider.selectBackend(backend);
if (provider != newProvider) {
if (newProvider == null)
newProvider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider {} does not support {}, switching to {}", provider.name(), backend, newProvider.name());
provider = newProvider;
}
}
}
// Only update config if the provider isn't the dummy provider
if (!Objects.equals(provider.name(), "dummyprovider"))
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER, provider.name());
FMLLoader.progressWindowTick = provider.initialize(arguments);
earlyProgress = StartupNotificationManager.addProgressBar("EARLY", 0);
earlyProgress.label("Bootstrapping Minecraft");
}
private static String findBackend(String[] arguments) {
// Try and parse from the command line arguments
var parser = new OptionParser();
var backendOption = parser.accepts("graphicsBackend").withRequiredArg();
parser.allowsUnrecognizedOptions();
var parsed = parser.parse(arguments);
if (parsed.has(backendOption))
return parsed.valueOf(backendOption).toLowerCase(Locale.ENGLISH);
// Read the options.txt if it exists.
var optionsFile = FMLPaths.GAMEDIR.get().resolve(Path.of("options.txt"));
if (!Files.exists(optionsFile)) // Default is OpenGL first
return "default";
List<String> lines = null;
try {
lines = Files.readAllLines(optionsFile);
} catch (IOException e) {
return "default"; // We failed to read for some reason, assume we're using the default.
}
final String key = "preferredGraphicsBackend:";
for (var line : lines) {
if (line.startsWith(key)) {
var backend = line.substring(key.length() + 1, line.length() - 1);
return backend.toLowerCase(Locale.ENGLISH);
}
}
return "default";
}
public static long setupMinecraftWindow(final int width, final int height, final String title, final long monitor, final Supplier<Object> backend) {
return provider.setupMinecraftWindow(width, height, title, monitor, backend);
public static long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
return provider.setupMinecraftWindow(width, height, title, monitor);
}
public static boolean positionWindow(Optional<Object> monitor,IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
@@ -134,7 +83,7 @@ public class ImmediateWindowHandler {
earlyProgress.label(message);
}
record DummyProvider() implements ImmediateWindowProvider {
private record DummyProvider() implements ImmediateWindowProvider {
private static Method NV_HANDOFF;
private static Method NV_POSITION;
private static Method NV_OVERLAY;
@@ -155,9 +104,9 @@ public class ImmediateWindowHandler {
}
@Override
public long setupMinecraftWindow(final int width, final int height, final String title, final long monitor, final Supplier<Object> backend) {
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
try {
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor, backend);
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor);
return longsupplier.getAsLong();
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
@@ -8,6 +8,8 @@ package net.minecraftforge.fml.loading;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/**
@@ -24,36 +26,11 @@ import java.util.function.Supplier;
* No doubt many more things can be said here.
*/
public interface ImmediateWindowProvider {
/**
* Returns a new instance of ImmediateWindowProvider which just bounces to the vanilla code.
* This can be useful when you want to disable your provider and defer to vanilla behavior for some reason.
*/
public static ImmediateWindowProvider getFallbackHandler() {
return new ImmediateWindowHandler.DummyProvider();
}
/**
* @return The name of this window provider. Do NOT use fmlearlywindow.
*/
String name();
/**
* This is called before initialize, but after reading the preferred graphics backend config value from the user's
* options.txt or command line.
*
* If you do not support the requested backend, you can return a new ImmediateWindowProvider that does.
* {@link #getFallbackHandler()} can be used to get an instance that falls back to Vanilla's code effectively
* disabling the early loading screen.
*
* @param backend - The backend the user has selected, known values: "default", "opengl", and "vulkan". However this
* is read from the config file, or command line arguments so could be anything.
* Default and OpenGL are treated the same, attempting to load OpenGL first, then Vulkan.
* Vulkan attempts to load Vulkan first then OpenGL
*/
default ImmediateWindowProvider selectBackend(String backend) {
return this;
}
/**
* This is called very early on to initialize ourselves. Use this to initialize the window and other GL core resources.
*
@@ -89,7 +66,7 @@ public interface ImmediateWindowProvider {
* @param monitor This is the monitor it should appear on.
* @return The window id
*/
long setupMinecraftWindow(final int width, final int height, final String title, final long monitor, final Supplier<Object> backend);
long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor);
/**
* This is called after window handoff to allow us to tell Mojang about our window's position. This might give a
@@ -18,6 +18,7 @@ import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.function.Consumer;
@@ -27,36 +28,43 @@ import java.util.stream.Stream;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public final class LanguageLoadingProvider {
public static void forEach(Consumer<IModLanguageProvider> consumer) {
public class LanguageLoadingProvider {
public void forEach(Consumer<IModLanguageProvider> consumer) {
providers.forEach(consumer);
}
public static <T> Stream<T> applyForEach(Function<IModLanguageProvider, T> function) {
public <T> Stream<T> applyForEach(Function<IModLanguageProvider, T> function) {
return providers.stream().map(function);
}
public static void reload() {
public void reload() {
loadLanguageProviders();
}
/** This doesn't actually do anything with the argument, so just call reload(). Not sure when cpw broke that. */
@Deprecated(forRemoval = true, since = "1.20.2")
public void addAdditionalLanguages(List<ModFile> modFiles) {
reload();
}
/*==========================================================================*
* INTERNAL *
* INTERNAL SHIT *
*==========================================================================*/
private static final Logger LOGGER = LogUtils.getLogger();
private static final ServiceLoader<IModLanguageProvider> serviceLoader;
private static final ArrayList<IModLanguageProvider> providers = new ArrayList<>();
private static final HashMap<String, Wrapper> providersByName = new HashMap<>();
private final ServiceLoader<IModLanguageProvider> serviceLoader;
private final List<IModLanguageProvider> providers = new ArrayList<>();
private final Map<String, Wrapper> providersByName = new HashMap<>();
private static final DefaultArtifactVersion IN_DEV = new DefaultArtifactVersion("0.0-dev");
private record Wrapper(ArtifactVersion version, IModLanguageProvider service) {}
static {
LanguageLoadingProvider() {
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm -> lm.getLayer(Layer.PLUGIN)).orElseThrow();
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
loadLanguageProviders();
}
private static void loadLanguageProviders() {
private void loadLanguageProviders() {
providers.clear();
providersByName.clear();
@@ -89,7 +97,7 @@ public final class LanguageLoadingProvider {
}
}
public static IModLanguageProvider findLanguage(ModFile mod, String language, VersionRange versionRange) {
public IModLanguageProvider findLanguage(ModFile mod, String language, VersionRange versionRange) {
var consumer = mod.getFileName();
var wrapper = providersByName.get(language);
@@ -5,46 +5,118 @@
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/// Master list of all mods in the loading context
public sealed interface LoadingModList permits LoadingModListImpl {
static ModFileInfo getModFileById(String modId) {
return get().fileById().get(modId);
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
/**
* Master list of all mods <em>in the loading context. This class cannot refer outside the
* loading package</em>
*/
public class LoadingModList {
private static final Logger LOGGER = LogUtils.getLogger();
private static LoadingModList INSTANCE;
private final List<ModFileInfo> modFiles;
private final List<ModInfo> sortedList;
private final Map<String, ModFileInfo> fileById;
private final List<EarlyLoadingException> preLoadErrors;
private List<IModFile> brokenFiles;
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
this.modFiles = modFiles.stream()
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.toList();
this.sortedList = sortedList.stream().toList();
this.fileById = this.modFiles.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
.map(ModInfo.class::cast)
.collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
this.preLoadErrors = new ArrayList<>();
}
static List<ModFileInfo> getModFiles() {
return get().getModFiles();
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException) {
INSTANCE = new LoadingModList(modFiles, sortedList);
if (earlyLoadingException != null)
INSTANCE.preLoadErrors.add(earlyLoadingException);
return INSTANCE;
}
static List<ModInfo> getMods() {
return get().getMods();
public static LoadingModList get() {
return INSTANCE;
}
static @Nullable Path findResource(String className) {
for (ModFileInfo modFileInfo : get().getModFiles()) {
Path resource = modFileInfo.getFile().findResource(className);
public void addAccessTransformers() {
var errors = new ArrayList<ExceptionData>();
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
for (var at : mod.getAccessTransformers()) {
if (!Files.exists(at)) {
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
errors.add(new ExceptionData(message));
LOGGER.error(message);
} else
FMLLoader.addAccessTransformer(at, mod);
}
}
if (!errors.isEmpty())
preLoadErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler) {
backgroundScanHandler.setLoadingModList(this);
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(backgroundScanHandler::submitForScanning);
}
public List<ModFileInfo> getModFiles() {
return modFiles;
}
public Path findResource(final String className) {
for (ModFileInfo mf : modFiles) {
final Path resource = mf.getFile().findResource(className);
if (Files.exists(resource)) return resource;
}
return null;
}
static List<EarlyLoadingException> getErrors() {
return get().getErrors();
public ModFileInfo getModFileById(String modid) {
return this.fileById.get(modid);
}
static List<IModFile> getBrokenFiles() {
return get().getBrokenFiles();
public List<ModInfo> getMods() {
return this.sortedList;
}
private static LoadingModListImpl get() {
return LoadingModListImpl.get();
public List<EarlyLoadingException> getErrors() {
return preLoadErrors;
}
public void setBrokenFiles(final List<IModFile> brokenFiles) {
this.brokenFiles = brokenFiles;
}
public List<IModFile> getBrokenFiles() {
return this.brokenFiles;
}
}
@@ -1,90 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
/**
* Master list of all mods <em>in the loading context. This class cannot refer outside the
* loading package</em>
*/
record LoadingModListImpl(
List<ModFileInfo> getModFiles,
List<ModInfo> getMods,
Map<String, ModFileInfo> fileById,
ArrayList<EarlyLoadingException> getErrors,
ArrayList<IModFile> getBrokenFiles
) implements LoadingModList {
private static final Logger LOGGER = LogUtils.getLogger();
private static ModSorter.State temp;
LoadingModListImpl(List<ModFile> modFiles, List<ModInfo> sortedList) {
var modFileInfos = modFiles.stream()
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.toList();
sortedList = sortedList.stream().toList();
var fileById = modFileInfos.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
.map(ModInfo.class::cast)
.collect(Collectors.toUnmodifiableMap(ModInfo::getModId, ModInfo::getOwningFile));
this(modFileInfos, sortedList, fileById, new ArrayList<>(), new ArrayList<>());
}
static LoadingModListImpl init(ModSorter.State modSorterState, EarlyLoadingException earlyLoadingException) {
temp = modSorterState;
var instance = get();
if (earlyLoadingException != null)
instance.getErrors.add(earlyLoadingException);
temp = null;
instance.addAccessTransformers();
return instance;
}
static LoadingModListImpl get() {
final class LazyInit {
private LazyInit() {}
private static final LoadingModListImpl INSTANCE
= new LoadingModListImpl(LoadingModListImpl.temp.files(), LoadingModListImpl.temp.mods());
}
return LazyInit.INSTANCE;
}
private void addAccessTransformers() {
var errors = new ArrayList<ExceptionData>();
for (ModFileInfo modFile : getModFiles) {
ModFile mod = modFile.getFile();
for (var at : mod.getAccessTransformers()) {
if (!Files.exists(at)) {
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
errors.add(new ExceptionData(message));
LOGGER.error(message);
} else
FMLLoader.addAccessTransformer(at, mod);
}
}
if (!errors.isEmpty())
getErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
}
}
@@ -7,6 +7,7 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.INameMappingService;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import java.io.BufferedReader;
@@ -6,6 +6,7 @@
package net.minecraftforge.fml.loading;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Convert a maven coordinate into a Path.
@@ -33,10 +33,10 @@ import java.util.stream.Stream;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public final class ModSorter {
public class ModSorter {
private static final Logger LOGGER = LogUtils.getLogger();
record State(List<ModFile> files, List<ModInfo> mods) {}
private record State(List<ModFile> files, List<ModInfo> mods) {}
private ModSorter() {}
@@ -52,7 +52,7 @@ public final class ModSorter {
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
// So all this is really doing is wasting time.
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
return LoadingModListImpl.init(systemMods, e);
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
}
var named = new HashMap<String, ModInfo>();
@@ -66,15 +66,15 @@ public final class ModSorter {
// if we miss one or the other, we abort now
if (!failedList.isEmpty()) {
return LoadingModListImpl.init(systemMods, new EarlyLoadingException("failure to validate mod list", null, failedList));
return LoadingModList.of(systemMods.files(), systemMods.mods(), new EarlyLoadingException("failure to validate mod list", null, failedList));
} else {
// Otherwise, lets try and sort the modlist and proceed
try {
var sorted = sort(modFiles, named);
return LoadingModListImpl.init(sorted, null);
return LoadingModList.of(sorted.files(), sorted.mods(), null);
} catch (EarlyLoadingException e) {
// The only exception that can happen here is a cyclic exception, but fall back to system mods so we can display the nice screen.
return LoadingModListImpl.init(systemMods, e);
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
}
}
}
@@ -33,6 +33,7 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
private static String DIST;
private static final String ONLYIN = Type.getDescriptor(OnlyIn.class);
private static final String ONLYINS = Type.getDescriptor(OnlyIns.class);
private static final String MOD = "Lnet/minecraftforge/fml/common/Mod;";
@Override
public String name() {
@@ -44,17 +45,11 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
// TODO: [FML][RuntimeDistCleaner] In 26.1, only return YAY for MC classes on mod dev server
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
// Only apply to MC classes when on a dedicated server in a production environment
if (FMLEnvironment.production)
return FMLEnvironment.dist.isDedicatedServer() && isMinecraftClass(internalName) ? YAY : NAY;
// For dev time it's a bit trickier so that we can throw exceptions when someone uses @OnlyIn in their mod code,
// so that it's more obvious that the behaviour they're expecting won't work in production anymore
if (internalName.startsWith("net/minecraftforge/"))
return NAY;
@@ -62,7 +57,7 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
if (FMLEnvironment.dist.isClient() && isMinecraftClass(internalName))
return NAY;
return YAY; // mod classes might have @OnlyIn, so parse them in dev to find out and throw if they do
return YAY;
}
@Override
@@ -71,14 +66,29 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
var annotations = unpack(classNode.visibleAnnotations);
var isNonMinecraftClass = !isMinecraftClass(classNode.name);
final class LazyInit {
private LazyInit() {}
private static final boolean CAN_EXPLODE = !FMLEnvironment.production && "21.6".equals(FMLLoader.versionInfo().mcVersion());
}
if (remove(annotations, DIST)) {
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
throw new RuntimeException("Attempted to load class " + classNode.name + " for invalid dist " + DIST);
}
if (isNonMinecraftClass && !annotations.isEmpty())
throw new UnsupportedOperationException("Mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
if (!annotations.isEmpty()) {
if (isNonMinecraftClass) {
if (LazyInit.CAN_EXPLODE)
throw new UnsupportedOperationException("Mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
else
LOGGER.warn(DISTXFORM, "Class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", classNode.name);
}
if (!FMLEnvironment.production && hasModAnnotation(classNode.visibleAnnotations)) {
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
throw new RuntimeException("Found @OnlyIn on @Mod class " + classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
}
}
if (classNode.interfaces != null && !classNode.interfaces.isEmpty()) {
for (var ann : annotations) {
@@ -111,8 +121,12 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
itr.remove();
changed = true;
if (isNonMinecraftClass)
throw new UnsupportedOperationException("Field " + field.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
if (isNonMinecraftClass) {
if (LazyInit.CAN_EXPLODE)
throw new UnsupportedOperationException("Field " + field.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
else
LOGGER.warn(DISTXFORM, "Field {} in class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", field.name, classNode.name);
}
}
}
@@ -125,8 +139,12 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
lambdaGatherer.accept(method);
changed = true;
if (isNonMinecraftClass)
throw new UnsupportedOperationException("Method " + method.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
if (isNonMinecraftClass) {
if (LazyInit.CAN_EXPLODE)
throw new UnsupportedOperationException("Method " + method.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
else
LOGGER.warn(DISTXFORM, "Method {} in class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", method.name, classNode.name);
}
}
}
@@ -207,6 +225,18 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
return false;
}
private static boolean hasModAnnotation(final List<AnnotationNode> anns) {
if (anns == null || anns.isEmpty())
return false;
for (var ann : anns) {
if (ann.desc.equals(MOD))
return true;
}
return false;
}
private static boolean isMinecraftClass(final String internalName) {
return internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/");
}
@@ -220,7 +250,7 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
};
}
private static final class LambdaGatherer extends MethodVisitor {
private static class LambdaGatherer extends MethodVisitor {
private static final Handle META_FACTORY = new Handle(Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory", "metafactory",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
@@ -12,7 +12,6 @@ import java.util.Map;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;
// TODO: [FML] Remove this
@SuppressWarnings("deprecation")
public class StringSubstitutor {
private static final Map<String, String> GLOBALS = Map.of(
@@ -26,7 +26,6 @@ public class StringUtils {
return java.util.stream.Stream.of(endings).anyMatch(lowerSearch::endsWith);
}
@SuppressWarnings("deprecation")
public static URL toURL(final String string) {
if (string == null || string.trim().isEmpty() || string.contains("myurl.me") || string.contains("example.invalid"))
return null;
@@ -115,22 +115,19 @@ public final class UniqueModListBuilder {
}
private static ArtifactVersion getVersion(final ModFile mf) {
List<IModInfo> modInfos;
if (mf.getModFileInfo() == null || (modInfos = mf.getModInfos()) == null || modInfos.isEmpty()) {
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
return mf.getJarVersion();
}
return modInfos.getFirst().getVersion();
return mf.getModInfos().getFirst().getVersion();
}
private static String getModId(ModFile modFile) {
var modFileInfo = modFile.getModFileInfo();
List<IModInfo> mods;
if (modFileInfo == null || (mods = modFileInfo.getMods()).isEmpty()) {
if (modFile.getModFileInfo() == null || modFile.getModFileInfo().getMods().isEmpty()) {
return modFile.getSecureJar().name();
}
return mods.getFirst().getModId();
return modFile.getModFileInfo().getMods().getFirst().getModId();
}
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.ApiStatus;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
@ApiStatus.Internal // since 1.21.1, will be made non-public in a later MC version
public class VersionSupportMatrix {
@@ -31,6 +32,15 @@ public class VersionSupportMatrix {
}
}
/**
* @deprecated Use {@link #testVersionSupportMatrix(VersionRange, String, String)} instead, unwrapping your BiPredicate.
*/
@Deprecated(forRemoval = true, since = "1.21.1")
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
if (standardLookup.test(lookupId, declaredRange)) return true;
return testVersionSupportMatrix(declaredRange, lookupId, type);
}
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type) {
if (OVERRIDE_VERSIONS.isEmpty()) return false;
List<ArtifactVersion> custom = OVERRIDE_VERSIONS.get(type + "." + lookupId);
@@ -7,6 +7,7 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.slf4j.Logger;
@@ -16,7 +17,8 @@ import java.time.Instant;
import java.util.List;
import java.util.concurrent.*;
public final class BackgroundScanHandler {
public class BackgroundScanHandler
{
private enum ScanStatus {
NOT_STARTED,
RUNNING,
@@ -31,6 +33,7 @@ public final class BackgroundScanHandler {
private final ExecutorService modContentScanner;
private final List<ModFile> modFiles;
private ScanStatus status;
private LoadingModList loadingModList;
public BackgroundScanHandler(final List<ModFile> modFiles) {
this.modFiles = modFiles;
@@ -55,7 +58,7 @@ public final class BackgroundScanHandler {
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
.whenComplete(file::setScanResult);
if (DEBUG) future = future.whenComplete((_, t) -> addCompletedFile(file, t));
if (DEBUG) future = future.whenComplete((r, t) -> addCompletedFile(file, t));
file.setFutureScanResult(future);
}
@@ -66,6 +69,16 @@ public final class BackgroundScanHandler {
}
}
public void setLoadingModList(LoadingModList loadingModList)
{
this.loadingModList = loadingModList;
}
public LoadingModList getLoadingModList()
{
return loadingModList;
}
public void waitForScanToComplete(final Runnable ticker) {
boolean timeoutActive = System.getProperty("fml.disableScanTimeout") == null;
Instant deadline = Instant.now().plus(Duration.ofMinutes(10));
@@ -45,7 +45,7 @@ public final class ClasspathLocator extends AbstractModProvider implements IModL
// Find 'minecraft' during dev time this is also Forge. So skip it.
// Can return null for dedicated server as Minecraft isn't on the classpath
// On the client it doesn't matter as it's the vanilla jar so wouldnt have mods.toml in it.
var minecraft = getPathFromResource(cl, "net/minecraft/world/item/Item.class");
var minecraft = getPathFromResource(cl, "net/minecraft/obfuscate/DontObfuscate.class");
var claimed = new HashSet<Path>();
var tomls = getUrls(cl, MODS_TOML);
@@ -24,7 +24,7 @@ public enum InvalidModIdentifier {
LITELOADER(filePresent("litemod.json")),
OPTIFINE(filePresent("optifine/Installer.class")),
BUKKIT(filePresent("plugin.yml")),
INVALIDZIP((_,zf) -> zf.isEmpty()); // note: only this one INVALIDZIP check is ran until the todo on this class is fixed
INVALIDZIP((f,zf) -> zf.isEmpty()); // note: only this one INVALIDZIP check is ran until the todo on this class is fixed
private final BiPredicate<Path, Optional<ZipFile>> ident;
@@ -51,7 +51,7 @@ public enum InvalidModIdentifier {
private static BiPredicate<Path, Optional<ZipFile>> filePresent(String filename)
{
return (_, zfo) -> zfo.map(zf -> zf.getEntry(filename) != null).orElse(false);
return (f, zfo) -> zfo.map(zf -> zf.getEntry(filename) != null).orElse(false);
}
private static <T> Optional<T> optionalFromException(Supplier_WithExceptions<T, ? extends Exception> supp)
@@ -145,7 +145,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
var ids = new TreeMap<String, List<Entry>>();
for (var entry : selector.entries.values()) {
if (entry != FAILED && entry.coord != null)
ids.computeIfAbsent(entry.coord, _ -> new ArrayList<>()).add(entry);
ids.computeIfAbsent(entry.coord, id -> new ArrayList<>()).add(entry);
}
for (var entry : ids.entrySet()) {
@@ -27,7 +27,7 @@ public final class MinecraftLocator extends AbstractModProvider implements IModL
// Minecraft itself.
var meta = new ModJarMetadata();
var mcjar = SecureJar.from(_ -> meta, paths);
var mcjar = SecureJar.from(jar -> meta, paths);
var mc = ModFileFactory.FACTORY.build(mcjar, this, MinecraftLocator::buildMinecraftTOML);
meta.setModFile(mc);
@@ -41,10 +41,9 @@ public final class ModDiscoverer {
@SuppressWarnings("removal")
public static ModValidator discoverMods(Map<String, ?> arguments) {
var env = Launcher.INSTANCE.environment();
env.computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), _ -> ModsFolderLocator::new);
env.computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), _ -> StartupNotificationManager.locatorConsumer().orElse(_ -> {}));
final var moduleLayerManager = env.findModuleLayerManager().orElseThrow();
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupNotificationManager.locatorConsumer().orElseGet(()-> s->{}));
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
ServiceLoader<IModLocator> modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
ServiceLoader<IDependencyLocator> dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
List<IModLocator> modLocatorList = ServiceLoaderUtils.streamWithErrorHandling(modLocators, sce -> LOGGER.error("Failed to load mod locator list", sce)).toList();
@@ -8,7 +8,7 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.LanguageLoadingProvider;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
@@ -25,6 +25,7 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -47,7 +48,8 @@ public class ModFile implements IModFile {
private final IModFileInfo modFileInfo;
private ModFileScanData fileModFileScanData;
private CompletableFuture<ModFileScanData> futureScanResult;
private List<Path> accessTransformers = List.of();
private Path accessTransformer;
private List<Path> accessTransformers = Collections.emptyList();
static final Attributes.Name TYPE = new Attributes.Name("FMLModType");
private SecureJar.Status securityStatus;
@@ -90,6 +92,11 @@ public class ModFile implements IModFile {
return modFileInfo.getMods();
}
@Deprecated(forRemoval = true, since = "1.21.11")
public Optional<Path> getAccessTransformer() {
return Optional.ofNullable(accessTransformer);
}
public List<Path> getAccessTransformers() {
return this.accessTransformers;
}
@@ -104,6 +111,7 @@ public class ModFile implements IModFile {
if (cfg == null) {
var path = findResource("META-INF", "accesstransformer.cfg");
if (Files.exists(path)) {
this.accessTransformer = path;
this.accessTransformers = List.of(path);
}
} else if (!cfg.isEmpty()) {
@@ -173,8 +181,9 @@ public class ModFile implements IModFile {
public void identifyLanguage() {
var lst = new ArrayList<IModLanguageProvider>(this.modFileInfo.requiredLanguageLoaders().size());
var services = FMLLoader.getLanguageLoadingProvider();
for (var spec : this.modFileInfo.requiredLanguageLoaders()) {
var service = LanguageLoadingProvider.findLanguage(this, spec.languageName(), spec.acceptedVersions());
var service = services.findLanguage(this, spec.languageName(), spec.acceptedVersions());
lst.add(service);
}
this.loaders = List.copyOf(lst);
@@ -35,18 +35,16 @@ public class ModFileParser {
LOGGER.warn(LogMarkers.LOADING, "Mod file {} is missing mods.toml file", modFile.getFilePath());
return null;
}
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
try {
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
} catch (ParsingException e) { // Handle landmine toml errors, e.g. incorrectly ported mods.
} catch (ParsingException e) {
LOGGER.error("Mod candidate {} contains a corrupt or misconfigured toml.", modFile.getFileName());
throw new ModFileLoadingException("Mod candidate " + modFile.getFileName() + " contains a corrupt or misconfigured toml.");
} catch (Exception other) { // Otherwise this is just someone who (probably) forgot a comma or something.
LOGGER.error("Mod candidate {}'s toml .", modFile.getFileName());
throw new ModFileLoadingException("Mod candidate " + modFile.getFileName() + " contains broken toml, likely due to a typo.");
}
}
@@ -31,6 +31,7 @@ public class ModValidator {
private final List<ModFile> candidatePlugins;
private final List<ModFile> candidateMods;
private final List<ModFile> gameLibraries;
private LoadingModList loadingModList;
private final List<IModFile> brokenFiles = new ArrayList<>();
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
@@ -86,7 +87,7 @@ public class ModValidator {
// If any detectable error happens during the sorting process {missing deps, duplicates,
// This helps prevent coremods/mixins from screwing up us displaying the error screens that the sorting/validation is trying to display.
// This won't fix them all as they are still loaded and will still apply but it might help
for (var info : LoadingModList.getModFiles())
for (var info : this.loadingModList.getModFiles())
mods.add(info.getFile().getSecureJar());
// Add any game libraries, this *may* be duplicates, depending on the state of the sorting. But until I get around to re-writing
@@ -123,12 +124,11 @@ public class ModValidator {
var allErrors = new ArrayList<>(errors);
allErrors.addAll(this.discoveryErrorData);
ModSorter.sort(candidateMods, allErrors);
LoadingModList.getBrokenFiles().addAll(brokenFiles);
var backgroundScanHandler = new BackgroundScanHandler(candidateMods);
LoadingModList.getModFiles().stream()
.map(ModFileInfo::getFile)
.forEach(backgroundScanHandler::submitForScanning);
loadingModList = ModSorter.sort(candidateMods, allErrors);
loadingModList.addAccessTransformers();
loadingModList.setBrokenFiles(brokenFiles);
BackgroundScanHandler backgroundScanHandler = new BackgroundScanHandler(candidateMods);
loadingModList.addForScanning(backgroundScanHandler);
return backgroundScanHandler;
}
}
@@ -56,7 +56,7 @@ public class StartupNotificationManager {
private synchronized static void addMessage(Message.MessageType type, String message, int maxSize)
{
EnumMap<Message.MessageType, List<Message>> newMessages = new EnumMap<>(messages);
newMessages.compute(type, (_, existingList) -> {
newMessages.compute(type, (key, existingList) -> {
List<Message> newList = new ArrayList<>();
if (existingList != null)
{
@@ -141,7 +141,7 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
protected static Path getForgeOnly(Path forge) {
var packages = getPackages(); // Pulled out so it is passed to the lambda as value
// We need to separate out our resources/code so that we can show up as a different data pack.
var modJar = SecureJar.from((path, _) -> {
var modJar = SecureJar.from((path, base) -> {
if (!path.endsWith(".class")) return true;
for (var pkg : packages)
if (path.startsWith(pkg)) return true;
@@ -112,7 +112,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
// We want just the class files from the root of the input paths. So make a new union with a filter.
var classes = UnionHelper.newFileSystem(
(name, _) -> {
(name, base) -> {
if (name.endsWith("/")) {
if (name.startsWith("/"))
name = name.substring(1);
@@ -164,7 +164,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
if ("value".equals(key)) {
int idx = clsName.lastIndexOf('/');
var pkg = clsName.substring(0, idx);
mods.computeIfAbsent(pkg, _ -> new HashSet<>()).add((String)value);
mods.computeIfAbsent(pkg, k -> new HashSet<>()).add((String)value);
idx = pkg.lastIndexOf('/');
while (idx != -1) {
@@ -25,9 +25,7 @@ sealed abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
@Override
public List<Path> getMinecraftPaths() {
// We use a marker because 3rd party launcher don't build their class paths correctly
// https://github.com/MinecraftForge/MinecraftForge/issues/10797
return List.of(getPathFromResource(".forge_patched_minecraft"));
return List.of(getPathFromResource("net/minecraft/client/Minecraft.class"));
}
}
+8 -8
View File
@@ -40,16 +40,16 @@ dependencies {
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge.transformers',
'Forge-Module-Layer' : 'boot'
'Automatic-Module-Name': 'net.minecraftforge.forge.transformers',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
], 'net/minecraftforge/forge/transformers/')
}
}
@@ -126,7 +126,7 @@ record FieldToMethodTransformer(String className, Map<String, String> fields) im
throw new IllegalStateException("No field with name " + fieldName + " found");
if (!Modifier.isPrivate(foundField.access) || Modifier.isStatic(foundField.access))
throw new IllegalStateException("Field " + classNode.name + '.' + fieldName + " is not private and an instance field");
throw new IllegalStateException("Field " + fieldName + " is not private and an instance field");
var methodSignature = "()" + foundField.desc;
var foundMethod = findMethod(classNode, methodName, methodSignature);
@@ -41,7 +41,7 @@ record MethodRedirector() implements ITransformer<ClassNode> {
"finalizeSpawn",
"(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;",
GSON.fromJson(new InputStreamReader(sneak(() -> MethodRedirector.class.getModule().getResourceAsStream("coremods/finalize_spawn_targets.json"))), Target[].class),
_ -> new MethodInsnNode(
insn -> new MethodInsnNode(
Opcodes.INVOKESTATIC,
"net/minecraftforge/event/ForgeEventFactory",
"onFinalizeSpawn",
@@ -1,8 +1,8 @@
[
{
"class": "net/minecraft/gametest/framework/GameTestEntityBuilder",
"class": "net/minecraft/gametest/framework/GameTestHelper",
"methods": [
"spawn()Lnet/minecraft/world/entity/Entity;"
"spawn(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/entity/EntitySpawnReason;)Lnet/minecraft/world/entity/Entity;"
]
},
{
@@ -20,7 +20,7 @@
{
"class": "net/minecraft/world/entity/EntityType",
"methods": [
"create(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/PostSpawnProcessor;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntitySpawnReason;ZZ)Lnet/minecraft/world/entity/Entity;"
"create(Lnet/minecraft/server/level/ServerLevel;Ljava/util/function/Consumer;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntitySpawnReason;ZZ)Lnet/minecraft/world/entity/Entity;"
]
},
{
@@ -42,6 +42,10 @@
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/animal/frog/Tadpole",
"methods": []
},
{
"class": "net/minecraft/world/entity/monster/Strider",
"methods": [
@@ -79,12 +83,22 @@
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/zombie/ZombieVillager",
"methods": []
},
{
"class": "net/minecraft/world/entity/npc/CatSpawner",
"methods": [
"spawnCat(Lnet/minecraft/core/BlockPos;Lnet/minecraft/server/level/ServerLevel;Z)V"
]
},
{
"class": "net/minecraft/world/entity/npc/villager/Villager",
"methods": [
"getBreedOffspring(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/AgeableMob;)Lnet/minecraft/world/entity/npc/villager/Villager;"
]
},
{
"class": "net/minecraft/world/entity/raid/Raid",
"methods": [
@@ -140,5 +154,9 @@
"methods": [
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate",
"methods": []
}
]
+9 -9
View File
@@ -41,16 +41,16 @@ tasks.withType(JavaCompile).configureEach {
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.javafmlmod',
'FMLModType' : 'LANGPROVIDER'
'Automatic-Module-Name': 'net.minecraftforge.javafmlmod',
'FMLModType' : 'LANGPROVIDER'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
], 'net/minecraftforge/fml/javafmlmod/')
}
}
@@ -76,4 +76,4 @@ publishing {
}
}
}
}
}
@@ -6,6 +6,7 @@
package net.minecraftforge.fml.javafmlmod;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.CancellableEventBus;
import net.minecraftforge.eventbus.api.bus.EventBus;
@@ -51,6 +52,7 @@ public final class AutomaticEventSubscriber {
private static final Logger LOGGER = LogManager.getLogger();
private static final Type AUTO_SUBSCRIBER = Type.getType(EventBusSubscriber.class);
private static final Type MOD_TYPE = Type.getType(Mod.class);
private static final Type ONLY_IN_TYPE = Type.getType(OnlyIn.class);
private static final List<EnumData> DEFAULT_SIDES = List.of(new EnumData(null, "CLIENT"), new EnumData(null, "DEDICATED_SERVER"));
private static final EnumData DEFAULT_BUS = new EnumData(null, "BOTH");
@@ -62,11 +64,20 @@ public final class AutomaticEventSubscriber {
.filter(data -> AUTO_SUBSCRIBER.equals(data.annotationType()))
.toList();
var onlyIns = FMLEnvironment.production ? Collections.emptySet() : scanData.getAnnotations().stream()
.filter(data -> ONLY_IN_TYPE.equals(data.annotationType()))
.map(data -> data.clazz().getClassName())
.collect(Collectors.toSet());
var modids = scanData.getAnnotations().stream()
.filter(data -> MOD_TYPE.equals(data.annotationType()))
.collect(Collectors.toMap(a -> a.clazz().getClassName(), a -> (String)a.annotationData().get("value")));
for (var data : targets) {
if (!FMLEnvironment.production && onlyIns.contains(data.clazz().getClassName())) {
throw new RuntimeException("Found @OnlyIn on @EventBusSubscriber class " + data.clazz().getClassName() + " - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead");
}
var modId = modids.getOrDefault(data.clazz().getClassName(), mod.getModId());
modId = value(data, "modid", modId);
@@ -81,7 +81,7 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
.filter(ad -> ad.annotationType().equals(MODANNOTATION))
.peek(ad -> LOGGER.debug(SCAN, "Found @Mod class {} with id {}", ad.clazz().getClassName(), ad.annotationData().get("value")))
.map(ad -> new FMLModTarget(ad.clazz().getClassName(), (String)ad.annotationData().get("value")))
.collect(Collectors.toMap(FMLModTarget::modId, Function.identity(), (a, _) -> a));
.collect(Collectors.toMap(FMLModTarget::modId, Function.identity(), (a,b)->a));
scanResult.addLanguageLoader(modTargetMap);
};
}
+8 -8
View File
@@ -39,16 +39,16 @@ tasks.withType(JavaCompile).configureEach {
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.lowcodemod',
'FMLModType' : 'LANGPROVIDER'
'Automatic-Module-Name': 'net.minecraftforge.lowcodemod',
'FMLModType' : 'LANGPROVIDER'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1.0',
'Implementation-Title' : project.name,
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion.split('\\.')[0]
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1.0',
'Implementation-Title' : project.name,
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion.split('\\.')[0]
], 'net/minecraftforge/fml/lowcodemod/')
}
}
@@ -74,7 +74,7 @@ public class LowCodeModLanguageProvider implements IModLanguageProvider
.flatMap(fi->fi.getMods().stream())
.map(IModInfo::getModId)
.map(LowCodeModTarget::new)
.collect(Collectors.toMap(LowCodeModTarget::modId, Function.identity(), (a, _)->a));
.collect(Collectors.toMap(LowCodeModTarget::modId, Function.identity(), (a, b)->a));
scanResult.addLanguageLoader(modTargetMap);
};
}
+8 -8
View File
@@ -39,16 +39,16 @@ tasks.withType(JavaCompile).configureEach {
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.mclanguageprovider',
'FMLModType' : 'LANGPROVIDER'
'Automatic-Module-Name': 'net.minecraftforge.mclanguageprovider',
'FMLModType' : 'LANGPROVIDER'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': '1.0'
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': '1.0'
], 'net/minecraftforge/fml/mclanguageprovider/')
}
}
+5 -3
View File
@@ -6,19 +6,21 @@ plugins {
id 'java'
id 'idea'
id 'eclipse'
id 'net.minecraftforge.gradle' version '[7.0.17,8)'
id 'net.minecraftforge.gradle' version '[7.0.3,8)'
}
version = '1.0.0'
group = 'com.example.examplemod'
// Mojang ships Java 25 to end users in 26.1+, so your mod should target Java 25.
java.toolchain.languageVersion = JavaLanguageVersion.of(25)
// Mojang ships Java 21 to end users in 1.20.5+, so your mod should target Java 21.
java.toolchain.languageVersion = JavaLanguageVersion.of(21)
// Include generated resources
sourceSets.main.resources { srcDir 'src/generated/resources' }
minecraft {
mappings channel: 'official', version: '@MC_VERSION@'
runs {
configureEach {
workingDir = layout.projectDirectory.dir('run')
+7 -5
View File
@@ -1,6 +1,8 @@
[versions]
java = "25"
minecraft = "26.2"
minecraft-next = "26.3"
mcp = "20260616.103818"
changelog-base = "65.0"
java = "21"
minecraft = "1.21.11"
minecraft-next = "1.22"
mcp = "20251223.124241"
mappings-channel = "official"
mappings-version = "1.21.11"
changelog-base = "61.0"
@@ -1,12 +1,12 @@
--- a/com/mojang/blaze3d/opengl/GlCommandEncoder.java
+++ b/com/mojang/blaze3d/opengl/GlCommandEncoder.java
@@ -201,6 +_,9 @@
GlStateManager._colorMask(15);
GlStateManager._clear(16384);
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, 0, 0);
+ // Forge: if using stencil, clear it as well (3553 = GlConst.GL_TEXTURE_2D)
+ if (colorTexture.isStencilEnabled())
+ GlStateManager._glFramebufferTexture2D(GlConst.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, GlConst.GL_TEXTURE_2D, 0, 0);
GlStateManager._glBindFramebuffer(36160, 0);
@@ -133,6 +_,9 @@
GlStateManager._colorMask(true, true, true, true);
GlStateManager._clear(16384);
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, 0, 0);
+ // Forge: if using stencil, clear it as well (3553 = GlConst.GL_TEXTURE_2D)
+ if (p_394273_.isStencilEnabled())
+ GlStateManager._glFramebufferTexture2D(GlConst.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, GlConst.GL_TEXTURE_2D, 0, 0);
GlStateManager._glBindFramebuffer(36160, 0);
}
}
@@ -1,35 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlConst.java
+++ b/com/mojang/blaze3d/opengl/GlConst.java
@@ -239,6 +_,10 @@
}
public static int toGlInternalId(final GpuFormat gpuFormat) {
+ return toGlInternalId(gpuFormat, false);
+ }
+ public static int toGlInternalId(final GpuFormat gpuFormat, final boolean stencil) {
+ if (stencil && gpuFormat.hasDepthAspect()) return org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8;
return switch (gpuFormat) {
case R8_UNORM -> 33321;
case R8_SNORM -> 36756;
@@ -291,6 +_,10 @@
}
public static int toGlExternalId(final GpuFormat gpuFormat) {
+ return toGlExternalId(gpuFormat, false);
+ }
+ public static int toGlExternalId(final GpuFormat gpuFormat, final boolean stencil) {
+ if (stencil && gpuFormat.hasDepthAspect()) return org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL;
return switch (gpuFormat) {
case R8_UNORM, R8_SNORM, R16_UNORM, R16_SNORM, R16_FLOAT, R32_FLOAT -> 6403;
case RG8_UNORM, RG8_SNORM, RG16_UNORM, RG16_SNORM, RG16_FLOAT, RG32_FLOAT -> 33319;
@@ -308,6 +_,10 @@
}
public static int toGlType(final GpuFormat gpuFormat) {
+ return toGlType(gpuFormat, false);
+ }
+ public static int toGlType(final GpuFormat gpuFormat, boolean stencil) {
+ if (stencil && gpuFormat.hasDepthAspect()) return org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
return switch (gpuFormat) {
case R8_UNORM, RG8_UNORM, RGB8_UNORM, RGBA8_UNORM, R8_UINT, RG8_UINT, RGBA8_UINT, S8_UINT, RGB8_UINT -> 5121;
case R8_SNORM, RG8_SNORM, RGB8_SNORM, RGBA8_SNORM, R8_SINT, RG8_SINT, RGBA8_SINT, RGB8_SINT -> 5120;
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/opengl/GlDebug.java
+++ b/com/mojang/blaze3d/opengl/GlDebug.java
@@ -22,6 +_,14 @@
@@ -24,6 +_,14 @@
@OnlyIn(Dist.CLIENT)
public class GlDebug {
@@ -15,30 +15,20 @@
private static final Logger LOGGER = LogUtils.getLogger();
private static final int CIRCULAR_LOG_SIZE = 10;
private final Queue<GlDebug.LogEntry> MESSAGE_BUFFER = EvictingQueue.create(10);
@@ -83,6 +_,8 @@
@@ -105,6 +_,8 @@
}
LOGGER.info("OpenGL debug message: {}", entry);
LOGGER.info("OpenGL debug message: {}", gldebug$logentry);
+ // TODO: [VEN] Trim the stack trace
+ if (PRINT_STACKTRACE_ON_ERROR) LOGGER.info("Trace: ", new Throwable("GlDebug"));
}
public List<String> getLastOpenGlDebugMessages() {
@@ -107,7 +_,7 @@
GlDebug debug = new GlDebug();
enabledExtensions.add("GL_KHR_debug");
GL33C.glEnable(37600);
- if (debugSynchronousGlLogs) {
+ if (debugSynchronousGlLogs | PRINT_STACKTRACE_ON_ERROR) {
GL33C.glEnable(33346);
}
@@ -121,7 +_,7 @@
} else if (caps.GL_ARB_debug_output && GlDevice.USE_GL_ARB_debug_output) {
GlDebug debug = new GlDebug();
enabledExtensions.add("GL_ARB_debug_output");
- if (debugSynchronousGlLogs) {
+ if (debugSynchronousGlLogs | PRINT_STACKTRACE_ON_ERROR) {
GL33C.glEnable(33346);
}
@@ -120,6 +_,7 @@
}
public static @Nullable GlDebug enableDebugCallback(int p_394351_, boolean p_393026_, Set<String> p_393339_) {
+ p_393026_ |= PRINT_STACKTRACE_ON_ERROR;
if (p_394351_ <= 0) {
return null;
} else {
@@ -1,50 +1,74 @@
--- a/com/mojang/blaze3d/opengl/GlDevice.java
+++ b/com/mojang/blaze3d/opengl/GlDevice.java
@@ -150,7 +_,12 @@
final int depthOrLayers,
final int mipLevels
@@ -124,8 +_,15 @@
int p_407582_,
int p_408928_
) {
- return this.createTexture(this.debugLabels.exists() && label != null ? label.get() : null, usage, format, width, height, depthOrLayers, mipLevels);
+ return this.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, false);
+ return createTexture(p_397830_, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_, false);
+ }
+
+ @Override
+ public GpuTexture createTexture(final @Nullable Supplier<String> label, @GpuTexture.Usage final int usage, final GpuFormat format, final int width, final int height, final int depthOrLayers, final int mipLevels, final boolean stencil) {
+ return this.createTexture(this.debugLabels.exists() && label != null ? label.get() : null, usage, format, width, height, depthOrLayers, mipLevels, stencil);
+ public GpuTexture createTexture(
+ @Nullable Supplier<String> p_397830_, int p_394481_, TextureFormat p_394839_, int p_391831_, int p_395609_, int p_407582_, int p_408928_, boolean stencil
+ ) {
return this.createTexture(
- this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_
+ this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_, stencil
);
}
@Override
@@ -163,6 +_,11 @@
final int depthOrLayers,
final int mipLevels
@@ -133,6 +_,15 @@
public GpuTexture createTexture(
@Nullable String p_394142_, @GpuTexture.Usage int p_395535_, TextureFormat p_394951_, int p_393944_, int p_392329_, int p_408015_, int p_406483_
) {
+ return this.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, false);
+ return createTexture(p_394142_, p_395535_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, false);
+ }
+
+ @Override
+ public GpuTexture createTexture(@Nullable String label, @GpuTexture.Usage final int usage, final GpuFormat format, final int width, final int height, final int depthOrLayers, final int mipLevels, boolean stencil) {
GlStateManager.clearGlErrors();
int id = GlStateManager._genTexture();
if (label == null) {
@@ -186,9 +_,9 @@
GlStateManager._texParameter(target, 34892, 0);
}
- int glInternalID = GlConst.toGlInternalId(format);
- int glExternalID = GlConst.toGlExternalId(format);
- int glType = GlConst.toGlType(format);
+ int glInternalID = GlConst.toGlInternalId(format, stencil);
+ int glExternalID = GlConst.toGlExternalId(format, stencil);
+ int glType = GlConst.toGlType(format, stencil);
if (glInternalID != 0 && glExternalID != 0 && glType != 0) {
if (isCubemap) {
for (int cubeTarget : GlConst.CUBEMAP_TARGETS) {
@@ -211,7 +_,7 @@
throw new IllegalStateException("OpenGL error " + error);
+ public GpuTexture createTexture(
+ @Nullable String p_394142_, @GpuTexture.Usage int p_395535_, TextureFormat p_394951_, int p_393944_, int p_392329_, int p_408015_, int p_406483_, boolean stencil
+ ) {
+ // Forge: stencil rendering is only for depth
+ stencil &= p_394951_.hasDepthAspect();
if (p_406483_ < 1) {
throw new IllegalArgumentException("mipLevels must be at least 1");
} else if (p_408015_ < 1) {
@@ -183,11 +_,14 @@
GlStateManager._texImage2D(
k,
l,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8 :
GlConst.toGlInternalId(p_394951_),
p_393944_ >> l,
p_392329_ >> l,
0,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL :
GlConst.toGlExternalId(p_394951_),
+ stencil ? org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV :
GlConst.toGlType(p_394951_),
null
);
@@ -198,11 +_,14 @@
GlStateManager._texImage2D(
j,
i1,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8 :
GlConst.toGlInternalId(p_394951_),
p_393944_ >> i1,
p_392329_ >> i1,
0,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL :
GlConst.toGlExternalId(p_394951_),
+ stencil ? org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV :
GlConst.toGlType(p_394951_),
null
);
@@ -215,7 +_,7 @@
} else if (j1 != 0) {
throw new IllegalStateException("OpenGL error " + j1);
} else {
- GlTexture gltexture = new GlTexture(p_395535_, p_394142_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, i);
+ GlTexture gltexture = new GlTexture(p_395535_, p_394142_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, i, stencil);
this.debugLabels.applyLabel(gltexture);
return gltexture;
}
- GlTexture texture = new GlTexture(usage, label, format, width, height, depthOrLayers, mipLevels, id, this.frameBufferCache);
+ GlTexture texture = new GlTexture(usage, label, format, width, height, depthOrLayers, mipLevels, id, this.frameBufferCache, stencil);
this.debugLabels.applyLabel(texture);
return texture;
} else {
@@ -1,18 +1,18 @@
--- a/com/mojang/blaze3d/opengl/GlStateManager.java
+++ b/com/mojang/blaze3d/opengl/GlStateManager.java
@@ -80,6 +_,11 @@
@@ -86,6 +_,11 @@
}
}
+ public static boolean _isBlendEnabled(int index) {
+ public static boolean _isBlendEnabled() {
+ RenderSystem.assertOnRenderThread();
+ return BLEND[index].mode.enabled;
+ return BLEND.mode.enabled;
+ }
+
public static void _disableBlend(int index) {
public static void _disableBlend() {
RenderSystem.assertOnRenderThread();
BLEND[index].mode.disable();
@@ -390,9 +_,17 @@
BLEND.mode.disable();
@@ -371,9 +_,17 @@
}
}
@@ -20,13 +20,13 @@
+ public static float lastBrightnessX = 0.0f;
+ public static float lastBrightnessY = 0.0f;
+
public static void _texParameter(final int target, final int name, final int value) {
public static void _texParameter(int p_393876_, int p_397998_, int p_396741_) {
RenderSystem.assertOnRenderThread();
GL33C.glTexParameteri(target, name, value);
+ if (target == GL33C.GL_TEXTURE1) {
+ lastBrightnessX = name;
+ lastBrightnessY = value;
GL11.glTexParameteri(p_393876_, p_397998_, p_396741_);
+ if (p_393876_ == GL13.GL_TEXTURE1) {
+ lastBrightnessX = p_397998_;
+ lastBrightnessY = p_396741_;
+ }
}
public static int _getTexLevelParameter(final int target, final int level, final int name) {
public static int _getTexLevelParameter(int p_393352_, int p_397204_, int p_396319_) {
@@ -1,21 +1,20 @@
--- a/com/mojang/blaze3d/opengl/GlTexture.java
+++ b/com/mojang/blaze3d/opengl/GlTexture.java
@@ -27,9 +_,14 @@
final int id,
final FrameBufferCache frameBufferCache
@@ -21,8 +_,13 @@
protected GlTexture(
@GpuTexture.Usage int p_394590_, String p_393950_, TextureFormat p_392837_, int p_391379_, int p_391947_, int p_396659_, int p_408255_, int p_408889_
) {
+ this(usage, label, format, width, height, depthOrLayers, mipLevels, id, frameBufferCache, false);
+ this(p_394590_, p_393950_, p_392837_, p_391379_, p_391947_, p_396659_, p_408255_, p_408889_, false);
+ }
+
+ protected GlTexture(@GpuTexture.Usage int usage, String label, GpuFormat format, int width, int height, int depthOrLayers, int mipLevels, int id, FrameBufferCache frameBufferCache, boolean stencil) {
super(usage, label, format, width, height, depthOrLayers, mipLevels);
this.id = id;
this.frameBufferCache = frameBufferCache;
+ protected GlTexture(int p_394590_, String p_393950_, TextureFormat p_392837_, int p_391379_, int p_391947_, int p_396659_, int p_408255_, int p_408889_, boolean stencil) {
super(p_394590_, p_393950_, p_392837_, p_391379_, p_391947_, p_396659_, p_408255_);
this.id = p_408889_;
+ this.stencilEnabled = stencil;
}
@Override
@@ -84,5 +_,12 @@
@@ -89,5 +_,12 @@
if (this.closed && this.views == 0) {
this.destroyImmediately();
}
@@ -1,15 +1,15 @@
--- a/com/mojang/blaze3d/pipeline/RenderTarget.java
+++ b/com/mojang/blaze3d/pipeline/RenderTarget.java
@@ -83,7 +_,7 @@
this.width = width;
this.height = height;
@@ -80,7 +_,7 @@
this.width = p_83951_;
this.height = p_83952_;
if (this.useDepth) {
- this.depthTexture = device.createTexture(() -> this.label + " / Depth", 15, GpuFormat.D32_FLOAT, width, height, 1, 1);
+ this.depthTexture = device.createTexture(() -> this.label + " / Depth", 15, GpuFormat.D32_FLOAT, width, height, 1, 1, this.stencilEnabled);
this.depthTextureView = device.createTextureView(this.depthTexture);
- this.depthTexture = gpudevice.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, p_83951_, p_83952_, 1, 1);
+ this.depthTexture = gpudevice.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, p_83951_, p_83952_, 1, 1, this.stencilEnabled);
this.depthTextureView = gpudevice.createTextureView(this.depthTexture);
}
@@ -121,5 +_,26 @@
@@ -126,5 +_,26 @@
public @Nullable GpuTextureView getDepthTextureView() {
return this.depthTextureView;
@@ -1,31 +1,30 @@
--- a/com/mojang/blaze3d/platform/Window.java
+++ b/com/mojang/blaze3d/platform/Window.java
@@ -101,8 +_,9 @@
Monitor initialMonitor = monitorManager.getMonitor(GLFW.glfwGetPrimaryMonitor());
this.windowedWidth = this.width = allowedWindowMinSize(displayData.width());
this.windowedHeight = this.height = allowedWindowMinSize(displayData.height());
- this.handle = this.createWindow(backend, this.width, this.height, title, this.fullscreen && initialMonitor != null ? initialMonitor.monitor() : 0L);
+ this.handle = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(this.width, this.height, title, this.fullscreen && initialMonitor != null ? initialMonitor.monitor() : 0L, () -> backend);
this.backend = backend;
+ if (!net.minecraftforge.fml.loading.ImmediateWindowHandler.positionWindow(Optional.ofNullable(initialMonitor), w->this.width = this.windowedWidth = w, h->this.height = this.windowedHeight = h, x->this.x = this.windowedX = x, y->this.y = this.windowedY = y)) {
if (initialMonitor != null) {
VideoMode mode = initialMonitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
this.windowedX = this.x = initialMonitor.x() + mode.getWidth() / 2 - this.width / 2;
@@ -114,6 +_,7 @@
this.windowedX = this.x = actualX[0];
this.windowedY = this.y = actualY[0];
@@ -91,7 +_,8 @@
GLFW.glfwWindowHint(139267, 3);
GLFW.glfwWindowHint(139272, 204801);
GLFW.glfwWindowHint(139270, 1);
- this.handle = GLFW.glfwCreateWindow(this.width, this.height, p_85376_, this.fullscreen && monitor != null ? monitor.getMonitor() : 0L, 0L);
+ this.handle = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(()->this.width, ()->this.height, ()->p_85376_, ()->this.fullscreen && monitor != null ? monitor.getMonitor() : 0L);
+ if (!net.minecraftforge.fml.loading.ImmediateWindowHandler.positionWindow(Optional.ofNullable(monitor), w->this.width = this.windowedWidth = w, h->this.height = this.windowedHeight = h, x->this.x = this.windowedX = x, y->this.y = this.windowedY = y)) {
if (monitor != null) {
VideoMode videomode = monitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
this.windowedX = this.x = monitor.getX() + videomode.getWidth() / 2 - this.width / 2;
@@ -103,6 +_,7 @@
this.windowedX = this.x = aint1[0];
this.windowedY = this.y = aint[0];
}
+ }
this.setMode();
this.refreshFramebufferSize();
@@ -306,6 +_,9 @@
outHeight[0] = this.isSoftScreen() ? outHeight[0] - 1 : outHeight[0];
this.framebufferWidth = outWidth[0] > 0 ? outWidth[0] : 1;
this.framebufferHeight = outHeight[0] > 0 ? outHeight[0] : 1;
@@ -269,6 +_,9 @@
GLFW.glfwGetFramebufferSize(this.handle, aint, aint1);
this.framebufferWidth = aint[0] > 0 ? aint[0] : 1;
this.framebufferHeight = aint1[0] > 0 ? aint1[0] : 1;
+ if (this.framebufferHeight == 0 || this.framebufferWidth == 0) {
+ net.minecraftforge.fml.loading.ImmediateWindowHandler.updateFBSize(w -> this.framebufferWidth = w, h -> this.framebufferHeight = h);
+ }
}
private void onResize(final long handle, final int newWidth, final int newHeight) {
private void onResize(long p_85428_, int p_85429_, int p_85430_) {
@@ -1,46 +1,22 @@
--- a/com/mojang/blaze3d/systems/GpuDevice.java
+++ b/com/mojang/blaze3d/systems/GpuDevice.java
@@ -73,6 +_,21 @@
return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
}
@@ -36,9 +_,19 @@
int p_407070_
);
+ /** Forge: same as {@link #createTexture(Supplier, int, GpuFormat, int, int, int)} but with stencil support */
+ public GpuTexture createTexture(
+ final @Nullable Supplier<String> label,
+ @GpuTexture.Usage final int usage,
+ final GpuFormat format,
+ final int width,
+ final int height,
+ final int depthOrLayers,
+ final int mipLevels,
+ final boolean stencil
+ ) {
+ this.verifyTextureCreationArgs(usage, width, height, depthOrLayers, mipLevels);
+ return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, stencil);
+ /** Forge: same as {@link #createTexture(Supplier, int, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable Supplier<String> p_394357_, int p_395623_, TextureFormat p_395807_, int p_395802_, int p_396157_, int p_408810_, int p_407070_, boolean stencil) {
+ return this.createTexture(p_394357_, p_395623_, p_395807_, p_395802_, p_396157_, p_408810_, p_407070_);
+ }
+
public GpuTexture createTexture(
final @Nullable String label,
final @GpuTexture.Usage int usage,
@@ -84,6 +_,21 @@
) {
this.verifyTextureCreationArgs(usage, width, height, depthOrLayers, mipLevels);
return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
+ }
GpuTexture createTexture(
@Nullable String p_391798_, @GpuTexture.Usage int p_391800_, TextureFormat p_393333_, int p_395600_, int p_394065_, int p_408732_, int p_407125_
);
+
+ /** Forge: same as {@link #createTexture(Supplier, int, GpuFormat, int, int, int)} but with stencil support */
+ public GpuTexture createTexture(
+ final @Nullable String label,
+ @GpuTexture.Usage final int usage,
+ final GpuFormat format,
+ final int width,
+ final int height,
+ final int depthOrLayers,
+ final int mipLevels,
+ final boolean stencil
+ ) {
+ this.verifyTextureCreationArgs(usage, width, height, depthOrLayers, mipLevels);
+ return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, stencil);
}
+ /** Forge: same as {@link #createTexture(String, int, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable String p_391798_, int p_391800_, TextureFormat p_393333_, int p_395600_, int p_394065_, int p_408732_, int p_407125_, boolean stencil) {
+ return this.createTexture(p_391798_, p_391800_, p_393333_, p_395600_, p_394065_, p_408732_, p_407125_);
+ }
GpuTextureView createTextureView(GpuTexture p_409045_);
private void verifyTextureCreationArgs(final @GpuTexture.Usage int usage, final int width, final int height, final int depthOrLayers, final int mipLevels) {
@@ -1,20 +0,0 @@
--- a/com/mojang/blaze3d/systems/GpuDeviceBackend.java
+++ b/com/mojang/blaze3d/systems/GpuDeviceBackend.java
@@ -32,7 +_,17 @@
@Nullable Supplier<String> label, @GpuTexture.Usage int usage, GpuFormat format, int width, int height, int depthOrLayers, int mipLevels
);
+ /** Forge: same as {@link #createTexture(Supplier<String>, int, GpuFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable Supplier<String> label, @GpuTexture.Usage final int usage, GpuFormat format, int width, int height, int depthOrLayers, int mipLevels, boolean stencil) {
+ return createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
+ }
+
GpuTexture createTexture(@Nullable String label, @GpuTexture.Usage int usage, GpuFormat format, int width, int height, int depthOrLayers, int mipLevels);
+
+ /** Forge: same as {@link #createTexture(String, int, GpuFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable String label, @GpuTexture.Usage final int usage, GpuFormat format, int width, int height, int depthOrLayers, int mipLevels, boolean stencil) {
+ return createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
+ }
GpuTextureView createTextureView(GpuTexture texture);
@@ -1,9 +1,9 @@
--- a/com/mojang/blaze3d/textures/GpuTexture.java
+++ b/com/mojang/blaze3d/textures/GpuTexture.java
@@ -9,7 +_,7 @@
import net.minecraftforge.api.distmarker.OnlyIn;
@@ -10,7 +_,7 @@
@OnlyIn(Dist.CLIENT)
@DontObfuscate
-public abstract class GpuTexture implements AutoCloseable {
+public abstract class GpuTexture implements AutoCloseable, net.minecraftforge.client.extensions.IForgeGpuTexture {
public static final int USAGE_COPY_DST = 1;
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/vertex/PoseStack.java
+++ b/com/mojang/blaze3d/vertex/PoseStack.java
@@ -16,7 +_,7 @@
@@ -15,7 +_,7 @@
import org.joml.Vector3fc;
@OnlyIn(Dist.CLIENT)
@@ -0,0 +1,11 @@
--- a/com/mojang/blaze3d/vertex/VertexConsumer.java
+++ b/com/mojang/blaze3d/vertex/VertexConsumer.java
@@ -14,7 +_,7 @@
import org.joml.Vector3fc;
@OnlyIn(Dist.CLIENT)
-public interface VertexConsumer {
+public interface VertexConsumer extends net.minecraftforge.client.extensions.IForgeVertexConsumer {
VertexConsumer addVertex(float p_344294_, float p_342213_, float p_344859_);
VertexConsumer setColor(int p_342749_, int p_344324_, int p_343336_, int p_342831_);
@@ -1,31 +1,41 @@
--- a/com/mojang/blaze3d/vertex/VertexFormat.java
+++ b/com/mojang/blaze3d/vertex/VertexFormat.java
@@ -19,6 +_,7 @@
private final int vertexSize;
private final int stepRate;
private final List<VertexFormatElement> elementValues;
@@ -20,7 +_,7 @@
@OnlyIn(Dist.CLIENT)
@DontObfuscate
-public class VertexFormat {
+public class VertexFormat implements net.minecraftforge.client.extensions.IForgeVertexFormat {
public static final int UNKNOWN_ELEMENT = -1;
private final List<VertexFormatElement> elements;
private final List<String> names;
@@ -29,6 +_,7 @@
private final int[] offsetsByElement = new int[32];
private @Nullable GpuBuffer immediateDrawVertexBuffer;
private @Nullable GpuBuffer immediateDrawIndexBuffer;
+ private final com.google.common.collect.ImmutableMap<String, VertexFormatElement> elementMapping;
private VertexFormat(final List<VertexFormatElement> elements, final int vertexSize, final int stepRate) {
this.vertexSize = vertexSize;
@@ -29,6 +_,11 @@
VertexFormat(List<VertexFormatElement> p_343616_, List<String> p_345241_, IntList p_345522_, int p_344162_) {
this.elements = p_343616_;
@@ -41,6 +_,11 @@
int j = vertexformatelement != null ? p_343616_.indexOf(vertexformatelement) : -1;
this.offsetsByElement[i] = j != -1 ? p_345522_.getInt(j) : -1;
}
this.elementValues = elements;
+
+ var elementMapping = com.google.common.collect.ImmutableMap.<String, VertexFormatElement>builder();
+ for (var element : elements)
+ elementMapping.put(element.name(), element);
+ this.elementMapping = elementMapping.buildOrThrow();
+ ImmutableMap.Builder<String, VertexFormatElement> elements = ImmutableMap.builder();
+ for (int i = 0; i < p_343616_.size(); i++)
+ elements.put(p_345241_.get(i), p_343616_.get(i));
+ this.elementMapping = elements.buildOrThrow();
}
public static VertexFormat.Builder builder(final int stepRate) {
@@ -69,6 +_,8 @@
public int hashCode() {
return this.elementValues.hashCode();
public static VertexFormat.Builder builder() {
@@ -139,6 +_,9 @@
this.immediateDrawIndexBuffer = uploadToBuffer(this.immediateDrawIndexBuffer, p_391835_, 72, () -> "Immediate index buffer for " + this);
return this.immediateDrawIndexBuffer;
}
+
+ public com.google.common.collect.ImmutableMap<String, VertexFormatElement> getElementMapping() { return elementMapping; }
+ public ImmutableMap<String, VertexFormatElement> getElementMapping() { return elementMapping; }
+ public int getOffset(int index) { return offsetsByElement[index]; }
@OnlyIn(Dist.CLIENT)
public static class Builder {
@DontObfuscate
@@ -1,32 +1,31 @@
--- a/com/mojang/math/Transformation.java
+++ b/com/mojang/math/Transformation.java
@@ -14,7 +_,7 @@
@@ -16,7 +_,7 @@
import org.joml.Vector3fc;
import org.jspecify.annotations.Nullable;
-public final class Transformation {
+public final class Transformation implements net.minecraftforge.common.extensions.IForgeTransformation {
private static final Vector3fc ZERO_TRANSLATION = new Vector3f();
private static final Vector3fc UNIT_SCALE = new Vector3f(1.0F, 1.0F, 1.0F);
private static final Quaternionfc ZERO_ROTATION = new Quaternionf();
@@ -172,6 +_,20 @@
this.scale().lerp(that.scale(), progress, new Vector3f()),
this.rightRotation().slerp(that.rightRotation(), progress, new Quaternionf())
private final Matrix4fc matrix;
public static final Codec<Transformation> CODEC = RecordCodecBuilder.create(
p_269604_ -> p_269604_.group(
@@ -168,5 +_,19 @@
this.getScale().lerp(p_175938_.getScale(), p_175939_, new Vector3f()),
this.getRightRotation().slerp(p_175938_.getRightRotation(), p_175939_, new Quaternionf())
);
+ }
+
+ private org.joml.Matrix3f normalTransform = null;
+ public org.joml.Matrix3f getNormalMatrix() {
+ private Matrix3f normalTransform = null;
+ public Matrix3f getNormalMatrix() {
+ checkNormalTransform();
+ return normalTransform;
+ }
+
+ private void checkNormalTransform() {
+ if (normalTransform == null) {
+ normalTransform = new org.joml.Matrix3f(matrix);
+ normalTransform = new Matrix3f(matrix);
+ normalTransform.invert();
+ normalTransform.transpose();
+ }
}
public static Matrix4fc compose(final Matrix4fc parent, final Optional<Transformation> transform) {
}
@@ -1,18 +1,18 @@
--- a/com/mojang/realmsclient/gui/screens/RealmsGenericErrorScreen.java
+++ b/com/mojang/realmsclient/gui/screens/RealmsGenericErrorScreen.java
@@ -60,6 +_,15 @@
@@ -62,6 +_,15 @@
}
@Override
+ public boolean keyPressed(net.minecraft.client.input.KeyEvent event) {
+ if (event.key() == org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE) {
+ minecraft.gui.setScreen(this.nextScreen);
+ minecraft.setScreen(this.nextScreen);
+ return true;
+ }
+ return super.keyPressed(event);
+ }
+
+ @Override
public void extractRenderState(final GuiGraphicsExtractor graphics, final int xm, final int ym, final float a) {
super.extractRenderState(graphics, xm, ym, a);
graphics.centeredText(this.font, this.title, this.width / 2, 80, -1);
public void render(GuiGraphics p_283497_, int p_88680_, int p_88681_, float p_88682_) {
super.render(p_283497_, p_88680_, p_88681_, p_88682_);
p_283497_.drawCenteredString(this.font, this.title, this.width / 2, 80, -1);
@@ -1,11 +1,11 @@
--- a/com/mojang/realmsclient/gui/screens/RealmsResetWorldScreen.java
+++ b/com/mojang/realmsclient/gui/screens/RealmsResetWorldScreen.java
@@ -300,7 +_,7 @@
int y = this.getY();
graphics.blit(RenderPipelines.GUI_TEXTURED, this.image, x + 2, y + 2, 0.0F, 0.0F, 56, 56, 56, 56, 56, 56, color);
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, SLOT_FRAME_SPRITE, x, y, 60, 60, color);
- int textColor = hoveredOrFocused ? -6250336 : -1;
+ int textColor = getFGColor();
graphics.centeredText(RealmsResetWorldScreen.this.font, this.getMessage(), x + 28, y - 14, textColor);
@@ -263,7 +_,7 @@
int k = this.getY();
p_282595_.blit(RenderPipelines.GUI_TEXTURED, this.image, j + 2, k + 2, 0.0F, 0.0F, 56, 56, 56, 56, 56, 56, i);
p_282595_.blitSprite(RenderPipelines.GUI_TEXTURED, SLOT_FRAME_SPRITE, j, k, 60, 60, i);
- int l = flag ? -6250336 : -1;
+ int l = getFGColor();
p_282595_.drawCenteredString(RealmsResetWorldScreen.this.font, this.getMessage(), j + 28, k - 14, l);
}
}
@@ -1,45 +1,48 @@
--- a/net/minecraft/CrashReport.java
+++ b/net/minecraft/CrashReport.java
@@ -58,14 +_,9 @@
if (this.uncategorizedStackTrace.length > 0) {
builder.append("-- Head --\n");
builder.append("Thread: ").append(Thread.currentThread().getName()).append("\n");
- builder.append("Stacktrace:\n");
if (this.uncategorizedStackTrace != null && this.uncategorizedStackTrace.length > 0) {
p_127520_.append("-- Head --\n");
p_127520_.append("Thread: ").append(Thread.currentThread().getName()).append("\n");
- p_127520_.append("Stacktrace:\n");
-
- for (StackTraceElement element : this.uncategorizedStackTrace) {
- builder.append("\t").append("at ").append(element);
- builder.append("\n");
- for (StackTraceElement stacktraceelement : this.uncategorizedStackTrace) {
- p_127520_.append("\t").append("at ").append(stacktraceelement);
- p_127520_.append("\n");
- }
-
- builder.append("\n");
+ builder.append(net.minecraftforge.logging.CrashReportAnalyser.appendSuspectedMods(this.exception, this.uncategorizedStackTrace));
+ builder.append("Stacktrace:");
+ builder.append(net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(this.uncategorizedStackTrace));
- p_127520_.append("\n");
+ p_127520_.append(net.minecraftforge.logging.CrashReportAnalyser.appendSuspectedMods(this.exception, this.uncategorizedStackTrace));
+ p_127520_.append("Stacktrace:");
+ p_127520_.append(net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(this.uncategorizedStackTrace));
}
for (CrashReportCategory entry : this.details) {
for (CrashReportCategory crashreportcategory : this.details) {
@@ -73,6 +_,7 @@
builder.append("\n\n");
p_127520_.append("\n\n");
}
+ net.minecraftforge.logging.CrashReportExtender.extendSystemReport(systemReport);
this.systemReport.appendToCrashReportString(builder);
this.systemReport.appendToCrashReportString(p_127520_);
}
@@ -84,15 +_,7 @@
exception = replaceMessage(exception, this.title);
@@ -92,18 +_,7 @@
throwable.setStackTrace(this.exception.getStackTrace());
}
- String s;
- try {
- writer = new StringWriter();
- printWriter = new PrintWriter(writer);
- exception.printStackTrace(printWriter);
- return writer.toString();
- stringwriter = new StringWriter();
- printwriter = new PrintWriter(stringwriter);
- throwable.printStackTrace(printwriter);
- s = stringwriter.toString();
- } finally {
- IOUtils.closeQuietly(writer);
- IOUtils.closeQuietly(printWriter);
- IOUtils.closeQuietly((Writer)stringwriter);
- IOUtils.closeQuietly((Writer)printwriter);
- }
+ return net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(exception);
-
- return s;
+ return net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(throwable);
}
private static Throwable copyProperties(final Throwable original, final Throwable copy) {
public String getFriendlyReport(ReportType p_343869_, List<String> p_342487_) {
@@ -1,28 +1,28 @@
--- a/net/minecraft/CrashReportCategory.java
+++ b/net/minecraft/CrashReportCategory.java
@@ -138,8 +_,10 @@
@@ -116,8 +_,10 @@
if (astacktraceelement.length <= 0) {
return 0;
} else {
- this.stackTrace = new StackTraceElement[astacktraceelement.length - 3 - p_128149_];
- System.arraycopy(astacktraceelement, 3 + p_128149_, this.stackTrace, 0, this.stackTrace.length);
+ int len = astacktraceelement.length - 3 - p_128149_;
+ if (len <= 0) len = astacktraceelement.length;
+ this.stackTrace = new StackTraceElement[len];
+ System.arraycopy(astacktraceelement, astacktraceelement.length - len, this.stackTrace, 0, this.stackTrace.length);
return this.stackTrace.length;
}
- this.stackTrace = new StackTraceElement[full.length - 3 - nestedOffset];
- System.arraycopy(full, 3 + nestedOffset, this.stackTrace, 0, this.stackTrace.length);
+ int len = full.length - 3 - nestedOffset;
+ if (len <= 0) len = full.length;
+ this.stackTrace = new StackTraceElement[len];
+ System.arraycopy(full, full.length - len, this.stackTrace, 0, this.stackTrace.length);
return this.stackTrace.length;
}
@@ -164,16 +_,16 @@
@@ -181,16 +_,16 @@
if (this.stackTrace.length > 0) {
builder.append("\nStacktrace:");
if (this.stackTrace != null && this.stackTrace.length > 0) {
p_128169_.append("\nStacktrace:");
-
- for (StackTraceElement element : this.stackTrace) {
- builder.append("\n\tat ");
- builder.append(element);
- for (StackTraceElement stacktraceelement : this.stackTrace) {
- p_128169_.append("\n\tat ");
- p_128169_.append(stacktraceelement);
- }
+ builder.append(net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(this.stackTrace));
+ p_128169_.append(net.minecraftforge.logging.CrashReportExtender.generateEnhancedStackTrace(this.stackTrace));
}
}
@@ -34,4 +34,4 @@
+ this.stackTrace = t.getStackTrace();
}
public static void populateBlockDetails(
public static void populateBlockDetails(CrashReportCategory p_178951_, LevelHeightAccessor p_178952_, BlockPos p_178953_, BlockState p_178954_) {
@@ -1,6 +1,6 @@
--- a/net/minecraft/SharedConstants.java
+++ b/net/minecraft/SharedConstants.java
@@ -218,6 +_,7 @@
@@ -216,6 +_,7 @@
}
static {
@@ -1,15 +1,15 @@
--- a/net/minecraft/advancements/Advancement.java
+++ b/net/minecraft/advancements/Advancement.java
@@ -220,7 +_,11 @@
@@ -231,7 +_,11 @@
}
public AdvancementHolder save(final Consumer<AdvancementHolder> output, final String name) {
- AdvancementHolder advancement = this.build(Identifier.parse(name));
+ return save(output, Identifier.parse(name));
public AdvancementHolder save(Consumer<AdvancementHolder> p_138390_, String p_138391_) {
- AdvancementHolder advancementholder = this.build(Identifier.parse(p_138391_));
+ return save(p_138390_, Identifier.parse(p_138391_));
+ }
+
+ public AdvancementHolder save(Consumer<AdvancementHolder> output, Identifier id) {
+ AdvancementHolder advancement = this.build(id);
output.accept(advancement);
return advancement;
+ public AdvancementHolder save(Consumer<AdvancementHolder> p_138390_, Identifier id) {
+ AdvancementHolder advancementholder = this.build(id);
p_138390_.accept(advancementholder);
return advancementholder;
}
@@ -1,10 +1,10 @@
--- a/net/minecraft/advancements/AdvancementRewards.java
+++ b/net/minecraft/advancements/AdvancementRewards.java
@@ -43,6 +_,7 @@
LootParams params = new LootParams.Builder(level)
.withParameter(LootContextParams.THIS_ENTITY, player)
.withParameter(LootContextParams.ORIGIN, player.position())
+ .withLuck(player.getLuck())
@@ -44,6 +_,7 @@
LootParams lootparams = new LootParams.Builder(serverlevel)
.withParameter(LootContextParams.THIS_ENTITY, p_9990_)
.withParameter(LootContextParams.ORIGIN, p_9990_.position())
+ .withLuck(p_9990_.getLuck())
.create(LootContextParamSets.ADVANCEMENT_REWARD);
boolean changes = false;
boolean flag = false;
@@ -1,37 +1,21 @@
--- a/net/minecraft/client/Camera.java
+++ b/net/minecraft/client/Camera.java
@@ -224,11 +_,13 @@
}
float fov = this.minecraft.options.fov().get().intValue() * Mth.lerp(partialTicks, this.oldFovModifier, this.fovModifier);
- return this.modifyFovBasedOnDeathOrFluid(partialTicks, fov);
+ var ret = this.modifyFovBasedOnDeathOrFluid(partialTicks, fov);
+ return net.minecraftforge.client.event.ForgeEventFactoryClient.fireComputeFov(this.minecraft.gameRenderer, this, partialTicks, ret, true).getFOV();
@@ -136,9 +_,13 @@
}
private float calculateHudFov(final float partialTicks) {
- return this.modifyFovBasedOnDeathOrFluid(partialTicks, 70.0F);
+ var ret = this.modifyFovBasedOnDeathOrFluid(partialTicks, 70.0F);
+ return net.minecraftforge.client.event.ForgeEventFactoryClient.fireComputeFov(this.minecraft.gameRenderer, this, partialTicks, ret, false).getFOV();
}
private float modifyFovBasedOnDeathOrFluid(final float partialTicks, float fov) {
@@ -337,9 +_,13 @@
}
protected void setRotation(final float yRot, final float xRot) {
+ setRotation(yRot, xRot, 0.0f);
protected void setRotation(float p_90573_, float p_90574_) {
+ setRotation(p_90573_, p_90574_, 0.0f);
+ }
+
+ public void setRotation(float yRot, float xRot, float z) {
this.xRot = xRot;
this.yRot = yRot;
- this.rotation.rotationYXZ((float) Math.PI - yRot * (float) (Math.PI / 180.0), -xRot * (float) (Math.PI / 180.0), 0.0F);
+ this.rotation.rotationYXZ((float) Math.PI - yRot * (float) (Math.PI / 180.0), -xRot * (float) (Math.PI / 180.0), z * (float) (Math.PI / 180.0));
+ public void setRotation(float p_90573_, float p_90574_, float z) {
this.xRot = p_90574_;
this.yRot = p_90573_;
- this.rotation.rotationYXZ((float) Math.PI - p_90573_ * (float) (Math.PI / 180.0), -p_90574_ * (float) (Math.PI / 180.0), 0.0F);
+ this.rotation.rotationYXZ((float) Math.PI - p_90573_ * (float) (Math.PI / 180.0), -p_90574_ * (float) (Math.PI / 180.0), z * (float) (Math.PI / 180.0));
FORWARDS.rotate(this.rotation, this.forwards);
UP.rotate(this.rotation, this.up);
LEFT.rotate(this.rotation, this.left);
@@ -370,6 +_,13 @@
@@ -168,6 +_,13 @@
public float yRot() {
return this.yRot;
@@ -1,8 +1,8 @@
--- a/net/minecraft/client/ClientBrandRetriever.java
+++ b/net/minecraft/client/ClientBrandRetriever.java
@@ -8,6 +_,6 @@
public static final String VANILLA_NAME = "vanilla";
@@ -10,6 +_,6 @@
@DontObfuscate
public static String getClientModName() {
- return "vanilla";
+ return net.minecraftforge.internal.BrandingControl.getBranding();
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/DeltaTracker.java
+++ b/net/minecraft/client/DeltaTracker.java
@@ -82,18 +_,22 @@
@@ -87,18 +_,22 @@
private void pause() {
if (!this.paused) {
@@ -22,4 +22,4 @@
+ net.minecraftforge.client.ForgeHooksClient.onClientPauseChangePost(false);
}
public void updateFrozenState(final boolean frozen) {
public void updateFrozenState(boolean p_344005_) {
@@ -15,12 +15,12 @@
@@ -39,7 +_,7 @@
}
private static void forAllKeyMappings(final InputConstants.Key key, final Consumer<KeyMapping> operation) {
- List<KeyMapping> keyMappings = MAP.get(key);
+ List<KeyMapping> keyMappings = MAP.getAll(key);
if (keyMappings != null && !keyMappings.isEmpty()) {
for (KeyMapping keyMapping : keyMappings) {
operation.accept(keyMapping);
private static void forAllKeyMappings(InputConstants.Key p_424096_, Consumer<KeyMapping> p_427756_) {
- List<KeyMapping> list = MAP.get(p_424096_);
+ List<KeyMapping> list = MAP.getAll(p_424096_);
if (list != null && !list.isEmpty()) {
for (KeyMapping keymapping : list) {
p_427756_.accept(keymapping);
@@ -106,7 +_,7 @@
}
@@ -30,12 +30,12 @@
}
public KeyMapping.Category getCategory() {
@@ -147,8 +_,17 @@
if (this.category == o.category) {
return this.order == o.order ? I18n.get(this.name).compareTo(I18n.get(o.name)) : Integer.compare(this.order, o.order);
@@ -149,8 +_,17 @@
? I18n.get(this.name).compareTo(I18n.get(p_90841_.name))
: Integer.compare(this.order, p_90841_.order);
} else {
- return Integer.compare(KeyMapping.Category.SORT_ORDER.indexOf(this.category), KeyMapping.Category.SORT_ORDER.indexOf(o.category));
+ return compareSort(this.category, o.category);
- return Integer.compare(KeyMapping.Category.SORT_ORDER.indexOf(this.category), KeyMapping.Category.SORT_ORDER.indexOf(p_90841_.category));
+ return compareSort(this.category, p_90841_.category);
}
+ }
+
@@ -48,17 +48,17 @@
+ return o1 - o2;
}
public static Supplier<Component> createNameSupplier(final String key) {
@@ -157,6 +_,20 @@
public static Supplier<Component> createNameSupplier(String p_90843_) {
@@ -159,6 +_,20 @@
}
public boolean same(final KeyMapping that) {
+ if (getKeyConflictContext().conflicts(that.getKeyConflictContext()) || that.getKeyConflictContext().conflicts(getKeyConflictContext())) {
public boolean same(KeyMapping p_90851_) {
+ if (getKeyConflictContext().conflicts(p_90851_.getKeyConflictContext()) || p_90851_.getKeyConflictContext().conflicts(getKeyConflictContext())) {
+ var keyModifier = getKeyModifier();
+ var otherKeyModifier = that.getKeyModifier();
+ if (keyModifier.matches(that.getKey()) || otherKeyModifier.matches(getKey())) {
+ var otherKeyModifier = p_90851_.getKeyModifier();
+ if (keyModifier.matches(p_90851_.getKey()) || otherKeyModifier.matches(getKey())) {
+ return true;
+ } else if (getKey().equals(that.getKey())) {
+ } else if (getKey().equals(p_90851_.getKey())) {
+ // IN_GAME key contexts have a conflict when at least one modifier is NONE.
+ // For example: If you hold shift to crouch, you can still press E to open your inventory. This means that a Shift+E hotkey is in conflict with E.
+ // GUI and other key contexts do not have this limitation.
@@ -67,10 +67,10 @@
+ (keyModifier == net.minecraftforge.client.settings.KeyModifier.NONE || otherKeyModifier == net.minecraftforge.client.settings.KeyModifier.NONE));
+ }
+ }
return this.key.equals(that.key);
return this.key.equals(p_90851_.key);
}
@@ -179,11 +_,13 @@
@@ -177,11 +_,13 @@
}
public Component getTranslatedKeyMessage() {
@@ -85,16 +85,16 @@
}
public String saveString() {
@@ -195,11 +_,94 @@
@@ -193,11 +_,94 @@
}
private void registerMapping(final InputConstants.Key key) {
- MAP.computeIfAbsent(key, k -> new ArrayList<>()).add(this);
+ MAP.put(key, this);
private void registerMapping(InputConstants.Key p_423386_) {
- MAP.computeIfAbsent(p_423386_, p_420623_ -> new ArrayList<>()).add(this);
+ MAP.put(p_423386_, this);
}
public static @Nullable KeyMapping get(final String name) {
return ALL.get(name);
public static @Nullable KeyMapping get(String p_378660_) {
return ALL.get(p_378660_);
+ }
+
+ private net.minecraftforge.client.settings.KeyModifier keyModifierDefault = net.minecraftforge.client.settings.KeyModifier.NONE;
Loaded 100 of 1797 files, more files were not shown because too many files have changed in this diff. Show more