Compare commits

...
24 Commits
Author SHA1 Message Date
berkay8987 4e25d8106c Fix missing forge config UI translation keys (#10912) 2026-11-02 17:30:09 -08:00
LexManos cedee17635 Add protection against large network channel lists 2026-10-12 19:11:17 -07:00
BonsUnleashed fafa95bdf4 Store object holder handlers per registry name (#10899) 2026-10-12 13:46:08 -07:00
coolsim d5d1e3157d Fixed getToolModifiedState for AXE_SCRAPE (#10881) 2026-08-26 18:24:54 -07:00
RealMangoRage b1008b481b Add TooltipContext and TooltipDisplay to ItemTooltipEvent (#10879) 2026-08-20 08:47:20 -07:00
LexManos e14159c05d Fix mod packets being processed after vanilla packets when received in the same tick. 2026-08-13 13:23:46 -07:00
LexManos 211d3aa692 26.1.2 RB 1 2026-07-27 13:34:36 -07:00
LexManos 88bb2217e5 Fix ModList screen selection getting out of sync. Closes #10855 2026-07-27 13:26:59 -07:00
LexManos 278bdfc132 Bump Dependencies, Notably ASM for Java 27 support 2026-07-27 13:24:33 -07:00
Matt 66d4d888eb Add DeltaTracker (partialTick) context to PassDefinitions (#10848) 2026-07-21 11:34:01 -07:00
Meme Man ee1e695124 Update equality check for RegistryObject to include RegistryObject#key. (#10859) 2026-07-21 10:44:44 -07:00
zznty 6435274d1e Handle glfw window pos errors gracefully for wayland (#10852) 2026-07-18 14:36:59 -07:00
6c3729d6e9 ForgeDev 7 (#10821)
Co-authored-by: LexManos <[email protected]>
Co-authored-by: Jonathing <[email protected]>
2026-06-28 13:21:48 -07:00
Matt 6a702686db Toml parsing errors are now visible in the latest log. (#10825) 2026-06-18 11:09:43 -07:00
Matt d3476f3f80 [26.1.2] Split hotbar rendering into components, allow replacing renders (#10824) 2026-06-09 11:21:51 -07:00
LexManos 6665cdcf4e Fix misaligned ClientLevel load patch 2026-05-04 09:26:36 -07:00
LexManos d9665f6447 Fix IForgeBlockStateModel#getModelData is never called Fixes #10817 2026-05-02 16:48:24 -07:00
LexManos ec73fd5240 Fix base bucket model not being flipped for gasses. Closes #10814 2026-04-27 10:45:07 -07:00
LexManos 3bbdbacb81 Fix being considered in water when not. Closes #10813 2026-04-23 14:36:51 -07:00
Paint_Ninja 478d8b3f18 Add c:bars/copper and c:bars/iron tags (#10810) 2026-04-15 22:20:21 +01:00
Paint_Ninja 03f928a3de Add c:foods/dough tag (#10811) 2026-04-15 22:19:57 +01:00
Paint_Ninja 4834f16aef Add c:drink_containing/bottle and c:drink_containing/bucket (#10812) 2026-04-15 22:19:44 +01:00
LexManos 7846d25606 Copy fluid and lava still textures to Item atlas for DynamicFluidContainerModel. Fixes #10809 2026-04-12 12:53:34 -07:00
LexManos 77cb0c5c4e 26.1.2 2026-04-09 10:16:29 -07:00
110 changed files with 2431 additions and 4520 deletions

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ name: Publish
on:
push:
branches: [ '26.1.1' ]
branches: [ '26.1.2' ]
permissions:
contents: read
+13 -3
View File
@@ -1,3 +1,6 @@
#forgedev dev if the folder exists in here
forgedev
#eclipse
**/bin
**/.settings
@@ -22,10 +25,12 @@
/*/build
/*/.gradle
# Minecraft
/run
/*.launch
# Projects repo, either ignore it, or ignore patches.
/projects/mcp/
/projects/clean/
/projects/forge/
src/minecraft
#occupational hazards
/projects/**/build/
@@ -47,3 +52,8 @@ src/*/generated/**/.cache/
/*/.factorypath
/*/.apt_generated/
/fmlcore/logs/
/lib/
/_actual/
/runs/
/forge.ipr
/forge.iws
+1 -1
View File
@@ -3,7 +3,7 @@ parts herein are licensed under the terms of the LGPL 2.1 found
here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and
copied below.
Homepage: http://minecraftforge.net/
Homepage: https://minecraftforge.net/
https://github.com/MinecraftForge/MinecraftForge
+776 -37
View File
@@ -1,57 +1,796 @@
import net.minecraftforge.forge.tasks.*
import net.minecraftforge.gradleutils.PomUtils
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'net.minecraftforge.licenser' version '1.0.1'
id 'com.github.ben-manes.versions' version '0.46.0'
id 'net.minecraftforge.gradleutils' version '[2.6.4,2.7)'
id 'java-library'
id 'idea'
id 'eclipse'
id 'de.undercouch.download' version '5.4.0'
id 'net.minecraftforge.gradle.patcher' version '[6.0.51,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0.51,6.2)' apply false
id 'net.minecraftforge.gradlejarsigner' version '1.0.4'
id 'org.barfuin.gradle.taskinfo' version '3.0.1'
id 'maven-publish'
alias libs.plugins.licenser
alias libs.plugins.versions
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
alias libs.plugins.jarsigner
id 'net.minecraftforge.forgedev'
id 'net.minecraftforge.forge.build.convention'
}
Util.init() //Init all our extension methods!
gradleutils.displayName = 'Forge'
group = 'net.minecraftforge'
description = 'Modifications to Minecraft to enable mod developers.'
ext {
VERSION = gitversion.getMCTagOffsetBranch(MC_VERSION)
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
// NOTE: See settings.gradle for all the referenceable version variables.
// All subprojects (that apply 'net.minecraftforge.forge.build.convention') will have these available.
println "Version: $version"
java.toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
jarSigner.autoDetect('forge')
// We have to tell ForgeDev to use the exact versions of tools we ship in the installer
fdtools {
configure('binpatcher') {
version = buildLibs.binarypatcher.get().version
}
configure('installertools') {
version = buildLibs.installertools.get().version
}
configure('jarcompatibilitychecker') {
javaLauncher = javaToolchains.launcherFor(java.toolchain)
}
}
changelog {
from '47.999'
/* This is used for debugging, comparing all published artifacts against actual production
forgedev.validatePublish {
task {
baseBranch = 'upstream/26.1.2'
versionPrefix = minecraftVersion
}
}
*/
final minecraftFiles = forgedev.minecraftFiles(minecraftVersion)
final mcpBase = forgedev.mcpBase {
mcpVersion = "${minecraftVersion}-${project.ext.mcpVersion}"
accessTransformers.from file('src/main/resources/META-INF/accesstransformer.cfg')
sideAnnotationStrippers.from file('src/main/resources/forge.sas')
}
forgedev.patches {
base = mcpBase
patches = file('patches/minecraft')
patched = file('src/minecraft/java')
sourceSets.main.java.srcDir patched
}
tasks.register('setup') {
dependsOn ':forge:extractMapped'
if (findProject(':clean'))
dependsOn ':clean:extractMapped'
dependsOn forgedev.patches.apply
}
tasks.register('doChecks') {
dependsOn ':forge:checkJarCompatibility'
dependsOn ':forge:publish'
}
project(':mcp') {
apply plugin: 'net.minecraftforge.gradle.mcp'
mcp {
config MC_VERSION + '-' + MCP_VERSION
pipeline = 'joined'
sourceSets {
named('main') {
resources.srcDir 'src/main/generated'
}
repositories {
mavenLocal()
named('test') {
resources.srcDir 'src/test/generated'
}
}
if (System.env.TEAMCITY_VERSION) {
//Only setup the CI environment if and only if the environment variables are set.
tasks.named('configureTeamCity').configure {
doLast {
println "##teamcity[buildNumber '${project(':forge').version}']"
println "##teamcity[setParameter name='env.PUBLISHED_JAVA_ARTIFACT_VERSION' value='${project(':forge').version}']"
license {
header = file('LICENSE-header.txt')
include 'net/minecraftforge/'
exclude 'net/minecraftforge/common/LenientUnboundedMapCodec.java'
tasks.tap {
register('main') {
files.from files('src/main/java')
}
register('test') {
files.from files('src/test/java')
}
}
}
changelog {
from changelogBase
publishAll = false
}
// TODO [ForgeDev] Support compileOnlyApi, runtimeOnly, and a consumable annotation processor configuration
configurations {
// Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it.
register('bootstrap') {
transitive = false
canBeDeclared = true
canBeResolved = true
}
register('installer') {
transitive = false
canBeDeclared = true
canBeResolved = true
extendsFrom(bootstrap)
}
named('api') {
extendsFrom(installer)
}
named('implementation') {
// Inherit vanilla Minecraft's dependencies
extendsFrom(mcpBase.dependencyConfiguration)
}
}
dependencies {
// These need to actually be on the classpath at the start. This is only used for the server shim jar.
// And this is only needed because custom file systems are REQUIRED to be on the boot classloader.
// This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project.
bootstrap libs.jarjar.fs // JarInJar file system
bootstrap libs.roimfs // JarInJar File System - FileSystems need to be on boot loader.
bootstrap libs.bundles.jimfs // In memory file system used for ForgeDev launches
bootstrap libs.securemodules // Has Union file system in it
bootstrap libs.unsafe // Needed by securemodules
bootstrap libs.bundles.asm // Needed by securemodules
implementation libs.jopt.simple
installer libs.bootstrap
installer libs.bootstrap.api // Needed by securemodules
installer libs.accesstransformers
installer libs.eventbus
installer libs.jspecify // Dep of EventBus
installer libs.forgespi
installer libs.coremods.api
installer libs.modlauncher
installer libs.mergetool.api
installer libs.bundles.night.config
installer libs.maven.artifact
installer libs.bundles.terminalconsoleappender
installer libs.mixin
installer libs.mixinextras.forge
installer libs.bundles.jarjar
installer libs.roimfs
installer projects.fmlcore
installer projects.fmlloader
installer projects.fmlearlydisplay
installer projects.javafmllanguage
installer projects.lowcodelanguage
installer projects.mclanguage
installer projects.forgeTransformers
runtimeOnly files(mcpBase.extra)
runtimeOnly libs.bootstrap
runtimeOnly libs.bootstrap.dev
annotationProcessor libs.eventbus.validator
}
final EXTRA_TXTS = [
file('LICENSE.txt'),
changelog.task.flatMap(task -> task.outputFile)
]
forgedev.runs {
configureEach {
options {
workingDir = layout.projectDirectory.dir("runs/$name")
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders'
systemProperty 'bsl.debug', 'true'
systemProperty 'terminal.jline', 'true'
with(sourceSets.test) {
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'forgedev.enableTestMods', 'true'
}
}
}
create('client') {
options {
systemProperty 'eventbus.api.strictRuntimeChecks', 'true'
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}'
}
}
create('server') {
options {
args '--launchTarget', 'forge_dev_server'
}
}
create('gameTestServer') {
options {
args '--launchTarget', 'forge_dev_server_gametest'
args '--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
}
}
create('data') {
options {
args '--launchTarget', 'forge_dev_data',
'--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/')
}
with(sourceSets.test) {
args '--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
}
}
}
create('clientData') {
options {
args '--all',
'--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'
}
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/')
}
}
}
}
tasks.register('genAllData') {
dependsOn forgedev.runs.data.run, forgedev.runs.data.runTest, forgedev.runs.clientData.run, forgedev.runs.clientData.runTest
}
final serverShim = forgedev.shim {
config {
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args = ['--launchTarget', 'forge_server']
}
classpath {
libraries configurations.installer
library tasks.named('universalJar', Jar)
library forgedev.binaryPatches('server').apply, 'server'
serverBundle.fileProvider(minecraftFiles.server)
}
bootstrapClasspath(configurations.bootstrap)
jar {
jarSigner.sign(it)
}
}
final finalizeSpawn = forgedev.methodCallFinder('findFinalizeSpawnTargets') {
jar.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;')
}
forgedev.validateDeprecations(tasks.named('jar', Jar)) {
mcVersion = minecraftVersion
}
forgedev.checks() {
base = mcpBase
ats {}
sas {}
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',
]
}
fix {
dependsOn finalizeSpawn
}
forgedev.patches.make.configure {
finalizedBy forgedev.checks.patches.fix
}
}
final crowdin = forgedev.crowdin // Create the crowdin tasks
tasks.named('jar', Jar) {
from(EXTRA_TXTS)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude '.cache'
from(zipTree(crowdin.flatMap { it.output } )) {
include 'assets/forge/lang/*.json'
}
manifest {
attributes([
'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
], '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
], 'net/minecraftforge/versions/mcp/')
}
jarSigner.sign(it)
}
final universalJar = tasks.register('universalJar', Jar) {
from (zipTree(tasks.named('jar', Jar).flatMap { it.archiveFile } )) {
exclude(forgedev.filterVanilla(mcpBase.classes))
}
manifest = tasks.named('jar', Jar).get().manifest
archiveClassifier = 'universal'
jarSigner.sign(it)
}
final sourcesJarAll = tasks.register('sourcesJarAll', Jar) {
archiveClassifier = 'sources-all'
from(sourceSets.main.allJava.srcDirs)
}
// 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) {
into 'patches/'
}
}
forgedev.userDev {
base(mcpBase)
config {
universal universalJar
sources sourcesJar
addLibraries(configurations.installer)
// TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config
// so we don't gotta keep making exceptions for MixinExtras
addCompileDependency(libs.mixinextras.common)
addAnnotationProcessorDependency(libs.mixinextras.common)
// TODO [ForgeDev] Consolidate Patcher + UserDev Config runs in a sane way
runs {
configureEach {
parents = []
main = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders'
client = it.name.containsIgnoreCase('client')
environment 'MCP_MAPPINGS', '{mcp_mappings}'
}
register('clientData') {
args '--launchTarget', "forge_userdev_client_data"
args '--assetIndex', '{asset_index}'
args '--assetsDir', '{assets_root}'
}
register('client') {
property 'forge.enableGameTest', 'true'
args '--launchTarget', "forge_userdev_client"
args '--version', 'MOD_DEV'
args '--assetIndex', '{asset_index}'
args '--assetsDir', '{assets_root}'
}
register('server') {
property 'forge.enableGameTest', 'true'
args '--launchTarget', "forge_userdev_server"
}
register('gameTestServer') {
args '--launchTarget', "forge_userdev_server_gametest"
}
register('data') {
args '--launchTarget', "forge_userdev_data"
args '--assetIndex', '{asset_index}'
args '--assetsDir', '{assets_root}'
}
}
}
patches {
modified.setFrom(sourcesJarAll.flatMap { it.archiveFile })
}
binaryPatches {
mustRunAfter(forgedev.patches.make)
patches.setFrom(forgedev.patches.patches)
dirty.setFrom(tasks.named('jar', Jar).flatMap { it.archiveFile })
}
}
forgedev.userdevCompatibility(minecraftVersion) {
clean = mcpBase.classes
dirty = tasks.named('jar', Jar)
}
// 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 serverBinPatches = forgedev.binaryPatches('server') {
clean = minecraftFiles.serverExtracted
apply { store = true }
}
// endregion
forgedev.installer {
dev = !forgedev.ci
offline = false
baseVersion = buildLibs.installer.get().version.toString()
// 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
launcherLibrary universalJar
// We configure the launcher here to keep the order for easier diff, after publishing, move this to the LauncherJson configuration below
launcherJson {
// We create this during install, so it's not downloadable
generated(clientBinPatches.apply, 'client')
}
launcherLibraries configurations.installer
final launcherId = "${minecraftVersion}-${project.name}-${forgeVersion}"
final installertools = tool(buildLibs.installertools)
final binarypatcher = tool(buildLibs.binarypatcher)
jar {
from(EXTRA_TXTS)
from(rootProject.file('/forge_installer_logo.png')) {
rename { 'big_logo.png' }
}
from(clientBinPatches.create.flatMap { t -> t.output }) {
rename { 'data/client.lzma' }
}
from(serverBinPatches.create.flatMap { t -> t.output }) {
rename { 'data/server.lzma' }
}
final argsFile = rootProject.file('server_files/args.txt')
final tokens = [tokens: [
SHIM_JAR_FILE: serverShim.jar.get().archiveFileName.get(),
MAVEN_PATH: forgedev.mavenArtifact.directory
]]
from(argsFile) {
filter(tokens, ReplaceTokens)
into 'data'
rename { 'unix_args.txt' }
}
from(argsFile) {
filter(tokens, ReplaceTokens)
into 'data'
rename { 'win_args.txt' }
}
from(rootProject.file('server_files')) {
filter(tokens, ReplaceTokens)
into 'data'
exclude 'args.txt'
}
jarSigner.sign(it)
}
json {
icon = rootProject.file('icon.ico')
executable serverShim.jar
minecraft = minecraftVersion
profileVersion = launcherId
final projectArtifact = "${project.group}:${project.name}:${project.version}"
data 'MC_UNPACKED', "[net.minecraft:client:${minecraftVersion}]", "[net.minecraft:server:${minecraftVersion}:unpacked]"
data 'MC_UNPACKED_SHA', minecraftFiles.client, minecraftFiles.serverExtracted
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 }
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}'
}
]
}
launcherJson {
// These are flags to preserve glitchy old behavior to make diffing easier, removing when migrating to production is recommended
sortLibraries = false // Don't sort, makes things non-deterministic, but easier to diff old versions
duplicateLibraries = true // Our 'bootstrap' layer go added twice
librariesLast = false // Makes libraries be added before args
id = launcherId
inheritsFrom = minecraftVersion
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
gameArgs = [ '--launchTarget', 'forge_client' ]
jvmArgs = [ '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders' ]
}
}
final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) {
gradleVersion = '9.5.0'
scriptFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradlew")
jarFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradle/wrapper/gradle-wrapper.jar")
}
final mdkZip = tasks.register('mdkZip', Zip) {
dependsOn mdkGradleWrapper
archiveBaseName = project.name
archiveClassifier = 'mdk'
archiveVersion = project.version
destinationDirectory = file('build/libs')
from(EXTRA_TXTS)
from mdkGradleWrapper.map(Wrapper.&getScriptFile)
from mdkGradleWrapper.map(Wrapper.&getBatchScript)
into('gradle/wrapper/') {
from mdkGradleWrapper.map(Wrapper.&getJarFile)
from mdkGradleWrapper.map(Wrapper.&getPropertiesFile)
}
from(rootProject.file('mdk/')){
rootProject.file('mdk/gitignore.txt').eachLine {
if (!it.trim().isEmpty() && !it.trim().startsWith('#'))
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()
])
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
}
from(rootProject.file('src/test/java/com/example/examplemod/')) {
into('src/main/java/com/example/examplemod/')
}
from(rootProject.file('src/test/generated/mdk_datagen/')) {
into('src/main/resources/')
exclude '**/.cache/'
}
}
/*
tasks.named('javadoc', Javadoc) {
description = 'Generates the combined javadocs for the FML projects and the main Forge project'
var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage', ':forge-transformers' ]
source includedProjects.collect { project(it).sourceSets.main.allJava }
classpath = classpath + files(includedProjects.collect { project(it).sourceSets.main.compileClasspath })
var docsDir = rootProject.file('src/docs/')
inputs.dir(docsDir)
.withPropertyName('docs resources directory')
.withPathSensitivity(PathSensitivity.RELATIVE)
.optional()
failOnError = false
// Exclude the Minecraft classes if not enabled
if (!project.hasProperty('generateAllDocumentation')) {
exclude 'net/minecraft/**'
exclude 'com/mojang/**'
}
exclude 'mcp/**'
options {
stylesheetFile = new File(docsDir, 'stylesheet.css')
tags = [
'apiNote:a:<em>API Note:</em>',
'implSpec:a:<em>Implementation Requirements:</em>',
'implNote:a:<em>Implementation Note:</em>'
]
groups = [
'Forge Mod Loader': [
'net.minecraftforge.fml.common.asm*',
'net.minecraftforge.fml.loading*',
'net.minecraftforge.fml.server*'
],
'FML Core': [
'net.minecraftforge.fml',
'net.minecraftforge.fml.config*',
'net.minecraftforge.fml.event*',
'net.minecraftforge.fml.util*'
],
'FML Common': [
'net.minecraftforge.fml.core',
'net.minecraftforge.fml.event.config',
'net.minecraftforge.fml.event.lifecycle'
],
'FML Java/MC Language Providers': [
'net.minecraftforge.fml.common',
'net.minecraftforge.fml.javafmlmod',
'net.minecraftforge.fml.mclanguageprovider'
],
'Minecraft Forge API': [
'net.minecraftforge*'
]
]
author = false
noSince = true
noHelp = true
bottom = "Minecraft Forge is an open source modding API for Minecraft: Java Edition, licensed under the Lesser GNU General Public License, version 2.1."
windowTitle = "Minecraft Forge API ${version}"
docTitle = "Minecraft Forge API - ${forgeVersion} for Minecraft ${minecraftVersion}"
header = "<div style=\"margin-top: 9px;padding: 5px 6px;\"><strong>${forgeVersion} for Minecraft ${minecraftVersion}</strong></div>"
}
doLast {
project.copy {
from docsDir
exclude '/stylesheet.css'
into destinationDir
}
}
}
*/
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
artifact universalJar
artifact forgedev.installer.jar
artifact mdkZip
artifact forgedev.userDev.jar
artifact sourcesJar
artifact serverShim.jar
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
/*
// These are debugging tasks that are only used when developing ForgeDevPlugin, to bulk test task's config/caching
// region ForgeDev Tests
def all = tasks.register('all') {}
afterEvaluate {
if (gradle.startParameter.taskNames.contains('all')) {
tasks.forEach { task -> // This forces everything to be realized, which is why we have the startParameter check
if (!gradle.startParameter.isTaskGraph() && task.name.startsWith('run')) return
if (task.name in [all.name, 'dependencyInsight', 'dependencies', 'dependencyUpdates', 'init', 'buildEnvironment', 'idea',
'eclipse', 'javaToolchains', 'outgoingVariants', 'projects', 'properties', 'resolvableConfigurations', 'tasks', 'updateDaemonJvm',
'generateActionsWorkflow'
]) return
if (task.name.startsWith('clean')) return
if (task.name == 'checkJarCompatibility') return // This doesn't work in FG6 dev, I think it's cuz we check vs srg names. Will need to look at later
all.configure { dependsOn(task) }
}
}
}
*/
// endregion
// We dont have any unit tests, so disable this error
tasks.withType(AbstractTestTask).configureEach {
failOnNoDiscoveredTests = false
}
// 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 }}
forgedev.runs.configureEach {
run.configure { dependsOn finalizeSpawn}
runTest.configure { dependsOn finalizeSpawn}
}
// run gradlew dependencyUpdates --no-parallel
//noinspection UnnecessaryQualifiedReference
tasks.named('dependencyUpdates', com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask) {
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
}
}
-3
View File
@@ -1,3 +0,0 @@
/.gradle/
/build/
/out/
-14
View File
@@ -1,14 +0,0 @@
repositories {
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
}
dependencies {
implementation 'org.ow2.asm:asm:9.9.1'
implementation 'org.ow2.asm:asm-tree:9.9.1'
implementation 'net.minecraftforge:srgutils:0.6.2'
implementation 'net.minecraftforge:JarJarMetadata:0.3.27'
implementation 'commons-io:commons-io:2.13.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
}
@@ -1,67 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.util.zip.ZipFile
abstract class BundleList extends DefaultTask {
@InputFiles abstract ConfigurableFileCollection getConfig()
@InputFile abstract RegularFileProperty getServerBundle()
@OutputFile abstract RegularFileProperty getOutput()
BundleList() {
config.setFrom(project.configurations.installer)
output.convention(project.layout.buildDirectory.file("$name/output.list"))
configure {
dependsOn(project.tasks.universalJar)
inputs.file(project.tasks.universalJar.archiveFile)
dependsOn(project.tasks.applyServerBinPatches)
inputs.file(project.tasks.applyServerBinPatches.output)
}
}
@TaskAction
void run() {
def entries = [:] as TreeMap
def resolved = project.configurations.installer.resolvedConfiguration.resolvedArtifacts
for (def dep : resolved) {
def info = Util.getMavenInfoFromDep(dep)
//println("$dep.file.sha1\t$info.name\t$info.path")
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
}
var packed = (AbstractArchiveTask) project.tasks.universalJar
var info = Util.getMavenInfoFromTask(packed)
def file = packed.archiveFile.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
var classifier = 'server'
var genned = project.tasks.applyServerBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
file = genned.output.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
try (def zip = new ZipFile(serverBundle.get().asFile)) {
def entry = zip.getEntry('META-INF/libraries.list')
def data = zip.getInputStream(entry).text.split('\n')
for (def line : data) {
def (sha, artifact, path) = line.split('\t')
def (group, name, other) = artifact.split(':', 3)
//println("Group: $group Name: $name")
def key = "$group:$name"
if (other.indexOf(':') != -1){
key += ':' + other.split(':', 2)[1]
//println("\tKey: $key")
}
if (!entries.containsKey(key))
entries.put(key, line)
}
}
output.get().asFile.text = entries.values().join('\n')
}
}
@@ -1,52 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodeFinder extends DefaultTask {
@InputFile abstract RegularFileProperty getJar()
// It should be fine to mark the output as internal as we want to control when we run it anyways.
// This also shuts Gradle 8 up about implicit task dependencies.
@Internal abstract RegularFileProperty getOutput()
BytecodeFinder() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") })
}
@TaskAction
protected void exec() {
Util.init()
var outputFile = output.get().asFile
if (outputFile.exists())
outputFile.delete()
pre()
Util.processClassNodes(jar.get().asFile, this.&process)
post()
outputFile.text = new JsonBuilder(getData()).toPrettyString()
}
protected process(ClassNode node) {
if (node.fields !== null) node.fields.each { process(node, it) }
if (node.methods !== null) node.methods.each { process(node, it) }
}
protected pre() {}
protected process(ClassNode parent, FieldNode node) {}
protected process(ClassNode parent, MethodNode node) {}
protected post() {}
protected abstract Object getData()
}
@@ -1,48 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
@Override
protected process(ClassNode parent, MethodNode node) {
for (final current : node.instructions) {
if (predicate.get().call(parent, node, current)) {
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
// only add non-synthetic methods
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
methods.add(node.name + node.desc)
}
return
}
}
}
@Internal
@Override
protected Object getData() {
var array = new ArrayList<Object>()
matches.forEach { c, m ->
{
array.add(
'class': c,
'methods': m
)
}
}
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
}
}
@@ -1,97 +0,0 @@
package net.minecraftforge.forge.tasks;
import groovy.transform.CompileDynamic;
import groovy.transform.CompileStatic;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeSet;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Set;
/**
* Eclipse config files are literally just java properties, with the header cleaned up.
* https://github.com/eclipse/buildship/blob/5b2c7fca7fa86cd74d71b3c099c7b1559eba038e/org.eclipse.buildship.core/src/main/java/org/eclipse/buildship/core/internal/configuration/PreferenceStore.java#L238
*
* This does the same thing, as well as sorting alphabetically.
* It also ignores all comments. We can add them latter if someone cares.
*/
@CompileStatic
public class CleanProperties extends Properties {
private static final long serialVersionUID = 1L;
private static final String LINE_SEP = System.getProperty("line.separator");
private static final String UNIX_LINE_SEP = "\n";
private static final Charset ENCODING = StandardCharsets.UTF_8;
public CleanProperties load(File input) throws IOException {
if (input.exists()) {
try (Reader is = new InputStreamReader(new FileInputStream(input), ENCODING)) {
super.load(is);
}
}
return this;
}
public void store(File out) throws IOException {
if (!out.getParentFile().exists())
out.getParentFile().mkdirs();
try (OutputStream os = new FileOutputStream(out)) {
store(os, null);
}
}
@Override
public synchronized Enumeration<Object> keys() {
Set<Object> ret = new TreeSet<>();
for (Enumeration<?> e = super.keys(); e.hasMoreElements();)
ret.add(e.nextElement());
return Collections.enumeration(ret);
}
@CompileDynamic
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
Set<Map.Entry<Object, Object>> ret = new TreeSet<>((l, r) -> ((String)l.getKey()).compareTo((String)r.getKey()));
ret.addAll(super.entrySet());
return ret;
}
@Override
public void store(OutputStream out, String comments) throws IOException {
out.write(clean().getBytes(ENCODING));
out.flush();
}
@Override
public void store(Writer out, String comments) throws IOException {
out.write(clean());
out.flush();
}
private String clean() throws IOException {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
try {
super.store(tmp, null);
} finally {
tmp.close();
}
String ret = tmp.toString(ENCODING).replace(LINE_SEP, UNIX_LINE_SEP);
ret = ret.substring(ret.indexOf(UNIX_LINE_SEP) + 1);
return ret;
}
}
@@ -1,33 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.stc.FirstParam
import java.util.function.BiConsumer
public class ClosureHelper {
BiConsumer<String, Closure> callback
public ClosureHelper(Closure cl, BiConsumer<String, Closure> callback) {
this.callback = callback
apply(this, cl)
}
def methodMissing(String name, Object args) {
if (!args.class.isArray()) return
Object[] aargs = (Object[])args
if (aargs.length == 1 && aargs[0] instanceof Closure) {
this.callback.accept(name, (Closure)aargs[0])
} else {
throw new IllegalArgumentException('Unknown method: "' + name + '" with arguments ' + args + ' for ' + this)
}
}
static <T> T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) {
cl.delegate = obj
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
return obj
}
}
@@ -1,54 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.OutputDirectory
import java.nio.file.Files
abstract class DownloadLibraries extends DefaultTask {
@InputFile abstract RegularFileProperty getInput()
@OutputDirectory abstract DirectoryProperty getOutput()
@OutputFile abstract RegularFileProperty getLibrariesOutput()
DownloadLibraries() {
output.convention(project.layout.buildDirectory.dir(name))
librariesOutput.convention(project.layout.buildDirectory.dir(name).map(d -> d.file('libraries.txt')))
}
@TaskAction
def run() {
Util.init()
File outputDir = output.get().asFile
var libraries = new ArrayList<String>()
def json = input.get().asFile.json().libraries.each { lib ->
//TODO: Thread?
def artifacts = [lib.downloads.artifact] + lib.downloads.get('classifiers', [:]).values()
artifacts.each{ art ->
def target = new File(outputDir, art.path)
libraries.add(target.absolutePath)
if (!target.exists() || art.sha1 != target.sha1()) {
project.logger.lifecycle("Downloading ${art.url}")
if (!target.parentFile.exists()) {
target.parentFile.mkdirs()
}
new URL(art.url).withInputStream { i ->
target.withOutputStream { it << i }
}
if (art.sha1 != target.sha1()) {
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
}
}
}
}
Files.write(librariesOutput.get().asFile.toPath(), libraries)
}
}
@@ -1,28 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import java.util.zip.ZipFile
@CompileStatic
abstract class ExtractFile extends DefaultTask {
@InputFile abstract RegularFileProperty getInput()
@Input abstract Property<String> getTarget()
@OutputFile abstract RegularFileProperty getOutput()
@TaskAction
protected void exec() {
var zip = new ZipFile(input.get().asFile);
var entry = zip.getEntry(target.get())
if (entry == null)
throw new IllegalStateException(input.get().asFile.absolutePath + " does not contain " + target.get())
var stream = zip.getInputStream(entry)
output.get().asFile.withOutputStream { out ->
stream.transferTo(out)
}
}
}
@@ -1,101 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import org.gradle.api.tasks.*
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
import static org.objectweb.asm.Opcodes.*
abstract class FieldCompareFinder extends BytecodeFinder {
@Nested
Map<String, Search> fields = [:] as HashMap
@Internal
Map<Search, String> fieldsReverse = [:] as HashMap
@Internal
Map<String, Set<ObjectTarget>> targets = [:] as TreeMap
@Override
protected pre() {
//fields.each{ k,v -> logger.lifecycle("Fields: " + k + ' ' + v) }
}
@Override
protected process(ClassNode parent, MethodNode node) {
AbstractInsnNode last = null
def parentInstance = new ObjectTarget(owner: parent.name, name: '', desc: '')
for (int x = 0; x < node.instructions.size(); x++) {
def current = node.instructions.get(x)
if (current.opcode === IF_ACMPEQ || current.opcode === IF_ACMPNE) {
if (last !== null && (last.opcode === GETSTATIC || last.opcode === GETFIELD)) {
def target = new Search(cls: last.owner, name: last.name)
def wanted = fieldsReverse.get(target)
def original = fields.get(wanted)
def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc)
if (wanted !== null && (original.blacklist === null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
}
}
}
last = current
}
}
@Internal
@Override
protected Object getData() {
def ret = [:] as HashMap
targets.forEach{ k, v ->
def e = fields.get(k)
ret[k] = [
cls: e.cls,
name: e.name,
replacement: e.replacement,
targets: v
]
}
return ret
}
@CompileStatic
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
static class Search {
@Input
String cls
@Input
String name
@Input
String replacement
@Nested
@Optional
Set<ObjectTarget> blacklist
@Override
String toString() {
return cls + '.' + name
}
def blacklist(String owner, String name, String desc) {
if (blacklist === null)
blacklist = new HashSet<>()
blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc))
}
def blacklist(String owner) {
blacklist(owner, '', '')
}
}
void fields(Closure cl) {
new ClosureHelper(cl, {name, ccl ->
def search = ClosureHelper.apply(new Search(), ccl)
this.fields.put(name, search)
this.fieldsReverse.put(search, name)
})
}
}
@@ -1,49 +0,0 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
@CompileStatic
@TupleConstructor
final class InheritanceData implements Annotatable {
String name
int access
String superName
List<String> interfaces = []
Map<String, Method> methods = [:]
Map<String, Field> fields = [:]
List<Annotation> annotations = []
@TupleConstructor
static final class Method implements Annotatable {
int access
String override
List<Annotation> annotations = []
}
@TupleConstructor
static final class Field implements Annotatable {
int access
String desc
List<Annotation> annotations = []
}
@TupleConstructor
static final class Annotation {
String desc
}
private static final Gson GSON = new Gson()
static Map<String, InheritanceData> parse(File file) {
try (final reader = file.newReader()) {
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
}
}
}
@CompileStatic
interface Annotatable {
List<InheritanceData.Annotation> getAnnotations()
}
@@ -1,158 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import groovy.json.JsonSlurper
abstract class InstallerJar extends Zip {
@Input @Optional abstract Property<Boolean> getFat()
@Input @Optional abstract Property<Boolean> getOffline()
InstallerJar() {
archiveClassifier.set('installer')
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
destinationDirectory.set(project.layout.buildDirectory.dir('libs'))
fat.convention(false)
offline.convention(false)
def installerJson = project.tasks.installerJson
def launcherJson = project.tasks.launcherJson
def downloadInstaller = project.tasks.downloadInstaller
dependsOn(installerJson, launcherJson, downloadInstaller, project.configurations.installer)
from(installerJson, launcherJson)
from(project.rootProject.file('/src/main/resources/url.png'))
project.afterEvaluate {
from(project.zipTree(downloadInstaller.output)) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
if (fat.get() || offline.get()) {
def cfg = project.tasks.register(name + "Config", Configure)
cfg.get().configure {
parent = this
dependsOn(project.tasks.installerJson, project.tasks.launcherJson)
}
dependsOn(cfg)
} else {
// Things we ALWAYS bundle, this just the server shim jar, because the installer spec only says to extract the file.
// I should make it allow downloads but thats a spec break, and this is just a ~14KB jar
[
project.tasks.serverShimJar // Server bootstrap executable jar
].forEach { AbstractArchiveTask packed ->
def path = Util.getMavenInfoFromTask(packed).path
from(packed) {
rename { "maven/$path" }
}
}
}
}
}
static abstract class Configure extends DefaultTask {
public Zip parent
private int count = 0;
@TaskAction
protected void exec() {
def deps = [:] as java.util.TreeMap
// Gather all things that need downloading
[
project.tasks.installerJson,
project.tasks.launcherJson
].each { task ->
def json = task.output.get().asFile.json
json.libraries.each { lib ->
if (lib.downloads?.artifact?.url !== null && !lib.downloads.artifact.url.isEmpty())
deps.put(lib.name, lib.downloads.artifact)
}
}
// First find things we build in this project.
[
project.tasks.universalJar, // Forge runtime code
project.tasks.serverShimJar // Shim jar for dedicated server
].forEach { AbstractArchiveTask packed ->
def name = Util.getMavenInfoFromTask(packed).name
def info = deps.remove(name)
if (info !== null) {
println("Adding: $packed.path $name")
parent.from(packed) {
rename { "maven/$info.path" }
}
}
}
// Find any artifacts from the 'installer' config
// This config specifies the runtime files we intend for the interaller to have.
// And are typically what we would be developing and testing alongside Forge.
// So we may have local modified versions
def cfg = project.configurations.installer
while (cfg != null) {
//println('')
def resolved = cfg.resolvedConfiguration.resolvedArtifacts
int found = 0
for (def dep : resolved) {
def name = Util.getMavenInfoFromDep(dep).name
def info = deps.remove(name)
if (info == null) {
//println("Skipping: $name")
continue
}
//println("-$name")
found++
addFile(dep.file, info)
}
if (deps.isEmpty()) {
cfg = null
continue
}
// Prevent infinite loops if something fucky happens
if (found == 0)
throw new IllegalStateException("Failed to find any installer dependencies")
def seen = [] as Set
cfg = project.configurations.detachedConfiguration()
cfg.transitive = false
for (def key : deps.keySet()) {
def (group, artifact, other) = key.split(':', 3)
// Only resolve unique group:artifact so we don't get version overrides
if (seen.add(group + ':' + artifact)) {
//println("+$key")
cfg.dependencies.add(project.dependencies.create(key))
}
}
}
}
void addFile(file, info) {
boolean pack = parent.offline.get() || info.url.isEmpty()
// If it's a offline jar just always pack
if (!pack) {
try {
var remote = new URL("${info.url}.sha1").getText('UTF-8')
pack = info.sha1 != remote
} catch (FileNotFoundException e) {
pack = !info.url.startsWith('https://libraries.minecraft.net/')
// Oh noes its not there!
}
}
if (pack) {
println("Adding: $file.absolutePath")
parent.from(file) {
rename { "maven/$info.path" }
}
}
}
}
}
@@ -1,73 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
abstract class InstallerJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input @Optional final Map<String, Object> libraries = new LinkedHashMap<>()
@Input Map<String, Object> json = new LinkedHashMap<>()
@InputFile abstract RegularFileProperty getIcon()
@Input abstract Property<String> getLauncherJsonName()
@Input abstract Property<String> getLogo()
@Input abstract Property<String> getMirrors()
@Input abstract Property<String> getWelcome()
InstallerJson() {
launcherJsonName.convention('/version.json')
logo.convention('/big_logo.png')
mirrors.convention('https://files.minecraftforge.net/mirrors-2.0.json')
welcome.convention("Welcome to the ${project.name.capitalize()} installer.")
output.convention(project.layout.buildDirectory.file('libs/install_profile.json'))
project.afterEvaluate {
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
}
}
}
@TaskAction
protected void exec() {
def libs = libraries
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { AbstractArchiveTask packed ->
def info = Util.getMavenInfoFromTask(packed)
libs.put(info.name, [
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
]
]
])
}
json.libraries = libs.values().sort{a,b -> a.name.compareTo(b.name)}
json.icon = "data:image/png;base64," + new String(Base64.getEncoder().encode(Files.readAllBytes(icon.get().asFile.toPath())))
json.json = launcherJsonName.get()
json.logo = logo.get()
if (!mirrors.get().isEmpty())
json.mirrorList = mirrors.get()
json.welcome = welcome.get()
Files.writeString(output.get().getAsFile().toPath(), new JsonBuilder(json).toPrettyString())
}
}
@@ -1,415 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
import net.minecraftforge.jarjar.metadata.ContainedVersion;
import net.minecraftforge.jarjar.metadata.Metadata;
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
import net.minecraftforge.jarjar.metadata.json.ArtifactVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarIdentifierSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarMetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.MetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.VersionRangeSerializer;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.zip.ZipFile;
// TODO SUPER SUPER SUPER UGLY, CLEAN UP IN FORGEDEV 7
@Deprecated(forRemoval = true) // Will be moved to JarJar plugin in ForgeDev 7
public abstract class JarJarMetadataOptions extends DefaultTask {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(VersionRange.class, new VersionRangeSerializer())
.registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(DefaultArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(ContainedJarIdentifier.class, new ContainedJarIdentifierSerializer())
.registerTypeAdapter(ContainedJarMetadata.class, new ContainedJarMetadataSerializer())
.registerTypeAdapter(ContainedVersion.class, new ContainedVersionSerializer())
.registerTypeAdapter(Metadata.class, new MetadataSerializer())
.setPrettyPrinting()
.create();
protected abstract @Input SetProperty<ResolvedDependencyInfoImpl> getResolvedDependencies();
protected abstract @OutputFile RegularFileProperty getMetadataFile();
protected abstract @Inject ProjectLayout getLayout();
// NOTE: I'm not adding a non-provider version. please just use the version catalog entries for now.
public void add(Provider<MinimalExternalModuleDependency> dependency, Action<? super ResolvedDependencyInfo> action) {
this.getResolvedDependencies().add(dependency.map(d -> {
var ret = ResolvedDependencyInfoImpl.from(this.getProject().getConfigurations(), d);
action.execute(ret);
return ret;
}));
}
@Inject
public JarJarMetadataOptions() {
this.getMetadataFile().convention(this.getLayout().getBuildDirectory().file(this.getName() + "/options.json"));
}
@TaskAction
protected void exec() {
record ForgeLocaterOptions(String resource, String layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) { }
var resolved = this.getResolvedDependencies().get();
var jars = new ArrayList<ForgeLocaterOptions>(resolved.size());
for (var dependency : resolved) {
var deps = new ArrayList<ContainedJarMetadata>();
try (var zip = new ZipFile(dependency.artifact)) {
var entry = zip.getEntry("META-INF/jarjar/metadata.json");
if (entry != null) {
try (var stream = zip.getInputStream(entry)) {
var meta = MetadataIOHandler.fromStream(stream).orElse(null);
if (meta == null)
throw new IllegalStateException("Corrupt metadata.json in " + dependency.artifact.getAbsolutePath());
for (var dep : meta.jars())
deps.add(new ContainedJarMetadata(dep.identifier(), dep.version(), "", dep.isObfuscated()));
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
jars.add(new ForgeLocaterOptions(
dependency.resource,
dependency.layer,
dependency.identifier,
deps,
new ContainedJarMetadata(
new ContainedJarIdentifier(validateGroup(dependency), dependency.module.getName()),
new ContainedVersion(null, parseVersion(dependency)),
Objects.requireNonNull(dependency.path, "Dependency path is unspecified: " + dependency.asString),
false
),
dependency.nested
));
}
try {
record Meta(List<ForgeLocaterOptions> options){}
Files.writeString(
this.getMetadataFile().getAsFile().get().toPath(),
GSON.toJson(new Meta(jars), Meta.class)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String validateGroup(ResolvedDependencyInfoImpl dependency) {
try {
return Objects.requireNonNull(dependency.module.getGroup());
} catch (NullPointerException e) {
throw new IllegalArgumentException("Module dependency has no group: " + dependency.asString, e);
}
}
private ArtifactVersion parseVersion(ResolvedDependencyInfoImpl resolved) {
try {
return VersionRange.createFromVersionSpec(Objects.requireNonNull(resolved.version)).getRecommendedVersion();
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + resolved.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + resolved.asString, e);
}
}
private VersionRange parseVersionRange(ResolvedDependencyInfoImpl dependency) {
if (dependency.hasManuallySpecifiedRange) {
try {
return VersionRange.createFromVersionSpec(dependency.versionRange);
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + dependency.asString, e);
}
} else {
try {
return VersionRange.createFromVersionSpec("[%s,)".formatted(Objects.requireNonNull(dependency.versionRange)));
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version range is invalid for: " + dependency.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + dependency.asString, e);
}
}
}
public interface ResolvedDependencyInfo {
void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action);
void setResource(String resource);
void setLayer(String layer);
void setId(String identifier);
void setNested(boolean nested);
interface ContainedJarMetadataInfo {
void setGroup(String group);
void setName(String name);
void setPath(String version);
}
}
static final class ResolvedDependencyInfoImpl implements ResolvedDependencyInfo, ResolvedDependencyInfo.ContainedJarMetadataInfo, Serializable {
private static final @Serial long serialVersionUID = -7577318115877822993L;
final MinimalModuleVersionIdentifier module;
final String version;
String versionRange;
boolean hasManuallySpecifiedRange;
boolean nested;
final File artifact;
String path;
String resource;
String layer;
String identifier;
final String asString;
public ResolvedDependencyInfoImpl(MinimalModuleVersionIdentifier module, String version, File artifact, String asString) {
this.module = module;
this.version = version;
this.artifact = artifact;
this.asString = asString;
}
@Override
public void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action) {
action.execute(this);
}
@Override
public void setGroup(String group) {
this.module.group = group;
}
@Override
public void setName(String name) {
this.module.name = name;
}
@Override
public void setPath(String path) {
this.path = path;
}
@Override
public void setResource(String resource) {
this.resource = resource;
}
@Override
public void setLayer(String layer) {
this.layer = layer;
}
@Override
public void setId(String identifier) {
this.identifier = identifier;
}
@Override
public void setNested(boolean nested) {
this.nested = nested;
}
static Set<File> getFiles(Set<ResolvedDependencyInfoImpl> resolvedDependencies) {
var ret = new HashSet<File>(resolvedDependencies.size());
for (var dependency : resolvedDependencies) {
ret.add(dependency.artifact);
}
return ret;
}
static ResolvedDependencyInfoImpl from(ConfigurationContainer configurations, Dependency dependency) {
var group = dependency.getGroup();
var name = dependency.getName();
var version = dependency.getVersion();
if (dependency instanceof FileCollectionDependency filesDependency) {
File artifact;
try {
artifact = filesDependency.getFiles().getSingleFile();
} catch (IllegalStateException e) {
// TODO fileCollectionDependencyIsNotSingleFile
throw e;
}
return new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, version),
version,
artifact,
filesDependency.toString()
);
} else if (dependency instanceof ModuleDependency moduleDependency) {
moduleDependency = moduleDependency.copy();
if (moduleDependency instanceof ExternalModuleDependency externalModuleDependency) {
externalModuleDependency.version(v -> v.strictly(version.toString()));
}
var detachedConfiguration = configurations.detachedConfiguration(moduleDependency);
detachedConfiguration.setTransitive(false);
ResolvedDependencyInfoImpl ret = null;
for (var artifact : detachedConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies().iterator().next().getModuleArtifacts()) {
var fileName = getFileName(artifact);
if (!fileName.endsWith(".jar"))
continue;
if (ret != null)
throw new IllegalArgumentException("Module dependency has too many Jar artifacts: " + moduleDependency);
ret = new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, artifact.getModuleVersion().getId().getVersion()),
version,
artifact.getFile(),
moduleDependency.toString()
);
}
if (ret == null)
throw new IllegalArgumentException("Module dependency has no Jar artifacts: " + moduleDependency);
return ret;
} else {
throw new IllegalArgumentException("Unsupported dependency type: " + dependency.getClass().getName() + " -- " + dependency);
}
}
private static String getFileName(ResolvedArtifact artifact) {
try {
return InvokerHelper.getProperty(artifact.getId(), "fileName").toString();
} catch (Throwable e) {
// NOTE: Why not just use this to begin with?
// ComponentArtifactIdentifier can have a getFileName() method, which doesn't necessarily resolve the file itself.
// This allows us to get the name of the file to be used without asking Gradle to download the file.
// So, if a file is not a JAR file, we can check the name without actually downloading it.
return artifact.getFile().getName();
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (ResolvedDependencyInfoImpl) obj;
return Objects.equals(this.module, that.module) &&
Objects.equals(this.version, that.version) &&
Objects.equals(this.versionRange, that.versionRange) &&
Objects.equals(this.artifact, that.artifact);
}
@Override
public int hashCode() {
return Objects.hash(module, version, versionRange, artifact);
}
@Override
public String toString() {
return "ResolvedDependencyInfo[" +
"module=" + module + ", " +
"fixedVersion=" + version + ", " +
"versionRange=" + versionRange + ", " +
"artifact=" + artifact + ']';
}
static final class MinimalModuleVersionIdentifier implements ModuleIdentifier, ModuleVersionIdentifier {
private static final @Serial long serialVersionUID = -955346236759069739L;
private String group;
private String name;
private final String version;
@Inject
public MinimalModuleVersionIdentifier(String group, String name, String version) {
this.group = group;
this.name = name;
this.version = version;
}
@Override
public ModuleIdentifier getModule() {
return this;
}
@Override
public String getGroup() {
return this.group;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getVersion() {
return this.version;
}
@Override
public boolean equals(Object obj) {
return this == obj || obj instanceof MinimalModuleVersionIdentifier o
&& Objects.equals(this.group, o.group)
&& Objects.equals(this.name, o.name)
&& Objects.equals(this.version, o.version);
}
@Override
public int hashCode() {
return Objects.hash(group, name, version);
}
@Override
public String toString() {
return "MinimalModuleVersionIdentifier[" +
"group=" + group + ", " +
"name=" + name + ", " +
"version=" + version + ']';
}
}
}
}
@@ -1,94 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
import static net.minecraftforge.forge.tasks.Util.getArtifacts
import static net.minecraftforge.forge.tasks.Util.iso8601Now
abstract class LauncherJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input Map<String, Object> json = new LinkedHashMap<>()
LauncherJson() {
output.convention(project.layout.buildDirectory.file('libs/version.json'))
dependsOn(project.tasks.universalJar)
input.from(project.tasks.universalJar.archiveFile)
input.from(project.configurations.installer)
configure {
def mc = project.rootProject.ext.MC_VERSION
def forge = project.rootProject.ext.FORGE_VERSION
def timestamp = iso8601Now()
json.putAll([
_comment: [
"Please do not automate the download and installation of Forge.",
"Our efforts are supported by ads from the download page.",
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
],
id: "$mc-$project.name-$forge",
time: timestamp,
releaseTime: timestamp,
inheritsFrom: mc,
type: 'release',
logging: [:],
mainClass: '',
libraries: []
] as LinkedHashMap)
[
project.tasks.universalJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
}
def patched = project.tasks.applyClientBinPatches
dependsOn(patched)
input.from patched.output
}
}
@TaskAction
protected void exec() {
var packed = (AbstractArchiveTask) project.tasks.universalJar
def info = Util.getMavenInfoFromTask(packed)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
]
]
])
var classifier = 'client'
var genned = project.tasks.applyClientBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "",
sha1: genned.output.get().asFile.sha1(),
size: genned.output.get().asFile.length()
]
]
])
json.libraries.addAll(getArtifacts(project.configurations.installer).values())
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
}
}
@@ -1,46 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.apache.commons.io.IOUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@CompileStatic
abstract class MergeJars extends DefaultTask {
MergeJars() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
}
@TaskAction
void run() {
def jars = inputJars.files
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
for (def jar : jars) {
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
ZipEntry entry
while ((entry = zin.getNextEntry()) !== null) {
ZipEntry _new = new ZipEntry(entry.getName())
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
zout.putNextEntry(_new)
IOUtils.copy(zin, zout)
}
}
}
}
}
@InputFiles
abstract ConfigurableFileCollection getInputJars()
@OutputFile
abstract RegularFileProperty getOutput()
}
@@ -1,32 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.EqualsAndHashCode
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
@EqualsAndHashCode
public class ObjectTarget implements Comparable<ObjectTarget> {
@Input
String owner
@Input
String name
@Input
@Optional
String desc
@Override
String toString() {
if (desc == null)
return owner + '.' + name
return owner + '.' + name + desc
}
@Override
int compareTo(ObjectTarget o) {
return toString() <=> o.toString()
}
}
@@ -1,47 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.StandardCopyOption
abstract class SetupCheckJarCompatibility extends DefaultTask {
SetupCheckJarCompatibility() {
group = 'jar compatibility'
onlyIf {
inputVersion.getOrNull() !== null
}
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
}
@TaskAction
void run() {
def inputVersion = inputVersion.get()
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
project.rootProject.extensions.download.run {
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
dest baseForgeUserdev
}
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
@Input
@Optional
abstract Property<String> getInputVersion()
@OutputFile
abstract RegularFileProperty getBaseBinPatchesOutput()
}
@@ -1,74 +0,0 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import org.eclipse.jgit.api.Git
import javax.annotation.Nullable
import java.util.stream.Collectors
@CompileStatic
class TeamcityRequests {
public static final Gson GSON = new Gson()
@Nullable
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
conn.setRequestProperty('Accept', 'application/json')
conn.setReadTimeout(5 * 1000)
conn.setConnectTimeout(5 * 1000)
conn.connect()
if (conn.responseCode !== 200) {
return null
}
try (final InputStream is = conn.getInputStream()) {
GSON.fromJson(new InputStreamReader(is), clazz.getType())
}
}
@Nullable
static Map<String, Build> buildsByCommit() throws IOException {
final Map<String, Build> builds = [:]
jsonRequest(new TypeToken<Builds>() {}, 'https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),defaultFilter:false,count:300,status:SUCCESS')
?.build?.forEach {
it.revisions.revision.each { rev -> builds[rev.version] = it }
}
return builds
}
@Nullable
static String attemptFindBase(File gitPath) {
try (final git = Git.open(gitPath)) {
final Map<String, Build> buildByCommit = buildsByCommit()
for (final commit in git.log().setMaxCount(100).call()) {
// Find the first commit which was built on CI
final build = buildByCommit[commit.id.name]
if (build) {
return build.number
}
}
} catch (Exception ignored) {}
return null
}
static final class Builds {
public List<Build> build
}
static final class Build {
public String number
public Revisions revisions
}
static final class Revisions {
public int count
public List<Revision> revision
}
static final class Revision {
public String version
}
}
@@ -1,230 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Semaphore
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
final class Util {
public static final int ASM_LEVEL = Opcodes.ASM9
private static final HttpClient HTTP = HttpClient.newBuilder().build()
static void init() {
File.metaClass.sha1 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-1')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
File.metaClass.sha256 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-256')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
File.metaClass.getSha256 = { !delegate.exists() ? null : delegate.sha256() }
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
Date.metaClass.iso8601 = { ->
var format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
var result = format.format(delegate)
return result[0..21] + ':' + result[22..-1]
}
String.metaClass.rsplit = { String del, int limit = -1 ->
var lst = new ArrayList<String>()
int x = 0
int idx
String tmp = delegate
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit === -1 || x++ < limit)) {
lst.add(0, tmp.substring(idx + del.length(), tmp.length()))
tmp = tmp.substring(0, idx)
}
lst.add(0, tmp)
return lst
}
}
static String[] getClasspath(Project project, Map libs, String artifact) {
def ret = []
artifactTree(project, artifact).each { key, lib ->
libs[lib.name] = lib
if (lib.name != artifact)
ret.add(lib.name)
}
return ret
}
@CompileStatic
static Map getArtifacts(Configuration config) {
var ret = [:]
var semaphore = new Semaphore(1, true)
config.resolvedConfiguration.resolvedArtifacts.parallelStream().forEachOrdered(dep -> {
var info = getMavenInfoFromDep(dep)
var domain = 'libraries.minecraft.net'
var url = "https://$domain/$info.path"
if (!checkExists(url))
url.values[0] = 'maven.minecraftforge.net'
var sha1 = sha1(dep.file)
semaphore.acquire()
ret[info.key] = [
name: info.name,
downloads: [
artifact: [
path: info.path,
url: url.toString(),
sha1: sha1,
size: dep.file.length()
]
]
]
semaphore.release()
})
return ret
}
@CompileStatic
static Map getMavenInfoFromDep(ResolvedArtifact dep) {
return getMavenInfoFromMap([
group: dep.moduleVersion.id.group,
name: dep.moduleVersion.id.name,
version: dep.moduleVersion.id.version,
classifier: dep.classifier,
extension: dep.extension
])
}
@CompileStatic
static Map getMavenInfoFromTask(AbstractArchiveTask task) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: task.archiveClassifier.get(),
extension: task.archiveExtension.get()
])
}
@CompileStatic
static Map getMavenInfoFromTask(Task task, String classifier) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: classifier,
extension: 'jar'
])
}
@CompileStatic
private static Map getMavenInfoFromMap(Map<String, String> art) {
var key = "$art.group:$art.name"
var name = "$art.group:$art.name:$art.version"
var path = "${art.group.replace('.', '/')}/$art.name/$art.version/$art.name-$art.version"
if (art.classifier !== null) {
name += ":$art.classifier"
path += "-$art.classifier"
}
if ('jar' != art.extension) {
name += "@$art.extension"
path += ".$art.extension"
} else {
path += ".jar"
}
return [
key: key.toString(),
name: name.toString(),
path: path.toString(),
art: art
]
}
static String iso8601Now() { new Date().iso8601() }
@CompileStatic
static String sha1(File file) {
MessageDigest md = MessageDigest.getInstance('SHA-1')
file.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
@CompileStatic
private static String toHex(byte bite) {
return String.format('%02x', bite)
}
private static Map artifactTree(Project project, String artifact, boolean transitive = true) {
if (!project.ext.has('tree_resolver'))
project.ext.tree_resolver = 1
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
cfg.transitive = transitive
def dep = project.dependencies.create(artifact)
cfg.dependencies.add(dep)
def files = cfg.resolve()
return getArtifacts(cfg)
}
@CompileStatic
static boolean checkExists(String url) {
try {
return HTTP.send(HttpRequest.newBuilder(new URI(url))
.method('HEAD', HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.discarding()
).statusCode() === 200
} catch (Exception e) {
if (e.toString().contains('unable to find valid certification path to requested target'))
throw new RuntimeException("Failed to connect to $url: Missing certificate root authority, try updating Java")
throw e
}
}
static String getLatestForgeVersion(String mcVersion) {
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
final ver = json.promos["$mcVersion-latest"]
ver === null ? null : (mcVersion + '-' + ver)
}
@CompileStatic
static void processClassNodes(File file, @ClosureParams(value = SimpleType, options = 'org.objectweb.asm.tree.ClassNode') Closure process) {
file.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
var node = new ClassNode(ASM_LEVEL)
new ClassReader(zin).accept(node, 0)
process(node)
}
}
}
}
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import net.minecraftforge.srgutils.MinecraftVersion
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
abstract class ValidateDeprecations extends DefaultTask {
@InputFile
abstract RegularFileProperty getInput()
@Input
abstract Property<String> getMcVersion()
ValidateDeprecations() {
this.onlyIf { !System.env.TEAMCITY_VERSION }
}
@TaskAction
protected void exec() {
var mcVer = MinecraftVersion.from(mcVersion.get())
List<String> errors = []
Util.processClassNodes(input.get().asFile) {
processNode(mcVer, errors, it)
}
if (!errors.isEmpty()) {
errors.forEach {
this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed")
}
throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details")
}
}
protected processNode(MinecraftVersion mcVer, List<String> errors, ClassNode node) {
node.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"class ${node.name}"
}
}
node.fields?.each { field ->
field.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"field ${node.name}#${field.name}"
}
}
}
node.methods?.each { method ->
method.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"method ${node.name}#${method.name}${method.desc}"
}
}
}
}
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure<String> context) {
def values = annotation.values
if (values === null)
return
int forRemoval = values.indexOf('forRemoval')
int since = values.indexOf('since')
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
def oldVersion = MinecraftVersion.from(values[since + 1])
int[] split = ValidateDeprecations.splitDots(oldVersion.toString())
if (split.length < 2)
return
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
if (removeVersion <= mcVer)
errors.add([context(), removeVersion])
}
}
@CompileStatic
private static int[] splitDots(String version) {
String[] pts = version.split('\\.')
int[] values = new int[pts.length]
for (int x = 0; x < pts.length; x++)
values[x] = Integer.parseInt(pts[x])
return values
}
}
@@ -1,282 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
import net.minecraftforge.forge.tasks.InheritanceData
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.objectweb.asm.Opcodes
@CompileStatic
abstract class CheckATs extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
ats.each {
final lines = process(it, reporter, inheritance)
if (fix) {
it.text = joinBack(lines, inheritance).join('\n')
}
}
}
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
final Map<String, ATParser.Entry> constructorGroups = [:]
final itr = lines.entrySet().iterator()
final toRemove = []
while (itr.hasNext()) {
final next = itr.next()
String key = next.key
final entry = next.value
if (entry === null) continue
final binaryName = entry.cls.replaceAll('\\.', '/')
// Process Groups, this will remove any entries outside the group that is covered by the group
if (entry.group) {
final jcls = inheritance[binaryName]
if (jcls === null) {
itr.remove()
reporter.report("Invalid group: $key")
} else if ('*' == entry.desc) {
if (!jcls.fields) {
itr.remove()
reporter.report("Invalid group, class has no fields: $key")
} else {
jcls.fields.each { field, value ->
final fkey = entry.cls + ' ' + field
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(fkey)) {
toRemove.add(fkey)
} else if (!entry.existing.contains(fkey)) {
reporter.report("Missing group entry: $fkey")
}
entry.children.add(fkey)
} else if (lines.containsKey(fkey)) {
toRemove.add(fkey)
reporter.report("Found invalid group entry: $fkey")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('*()' == entry.desc) {
if (!jcls.methods) {
itr.remove()
reporter.report("Invalid group, class has no methods: $key")
} else {
jcls.methods.each { mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
key = entry.cls + ' ' + mtd.replace(' ', '')
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(key)) {
toRemove.add(key)
} else if (!entry.existing.contains(key)) {
reporter.report("Missing group entry: $key")
}
entry.children.add(key)
} else if (lines.containsKey(key)) {
toRemove.add(key)
reporter.report("Found invalid group entry: $key")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
constructorGroups.put(binaryName, entry)
}
}
// Process normal lines, remove invalid and remove narrowing
else {
def jcls = inheritance.get(binaryName)
if (jcls === null) {
itr.remove()
reporter.report("Invalid: $key")
} else if (entry.desc == '') {
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
} else if (!entry.desc.contains('(')) {
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.fields[entry.desc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
}
}
} else {
final jdesc = entry.desc.replace('(', ' (')
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.methods[jdesc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
}
}
}
}
inheritance.each { tcls, value ->
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
String parent = tcls
while (parent !== null) {
constructorGroups[parent]?.tap { entry ->
value.methods.each { mtd, v ->
if (mtd.startsWith('<init>')) {
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel(v.access) < 3) {
if (lines.containsKey(child)) {
toRemove.add(child)
} else if (child !in entry.existing) {
reporter.report("Missing group entry: $child")
}
entry.children.add(child)
} else if (lines.containsKey(child)) {
toRemove.add(child)
reporter.report("Found invalid group entry: $child")
}
}
}
}
parent = inheritance[parent]?.superName
}
}
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
toRemove.each(lines.&remove)
return lines
}
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance) {
final data = [] as List<String>
lines.each { key, value ->
if (!value.group) {
def comment = null //value.comment
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
value.children.each {
final line = value.modifier + ' ' + it
final entry = ATParser.parseEntry(line)
final comment = null //entry.comment
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
return data
}
static int accessStr(String access) {
if (access.endsWith('-f') || access.endsWith('+f')) return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
static int accessLevel(int access) {
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
return 1
}
}
@CompileStatic
class ATParser {
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
TreeMap<String, Entry> outLines = new TreeMap<>()
Entry group = null
for (final line : lines) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
final entry = parseEntry(line.substring(7))
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
reporter.report("Invalid group: $line", false)
}
entry.group = true
entry.children = []
entry.existing = []
group = entry
if (outLines.containsKey(entry.key)) {
reporter.report("Duplicate group: $line", false)
}
outLines[entry.key] = group
} else if (group !== null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
final key = parseEntry(line).key
group.existing.add(key)
}
} else if (line.startsWith('#endgroup')) {
reporter.report("Invalid group ending: $line", false)
} else if (line.startsWith('#')) {
//Nom
} else {
final entry = parseEntry(line)
if (outLines.containsKey(entry.key)) {
reporter.report("Found duplicate: $line")
continue
}
outLines[entry.key] = entry
}
}
return outLines
}
static Entry parseEntry(String line) {
final idx = line.indexOf('#')
final String comment = idx === -1 ? null : line.substring(idx)
if (idx !== -1) line = line.substring(0, idx - 1)
final data = (line.trim() + ' ').split(' ', -1)
data[1] = data[1].replaceAll('/', '.') // Convert to Source names, internal names are fine by spec, but not supported by old AST based AT implementations.
new Entry(data[0], data[1], data[2], comment)
}
@TupleConstructor
static final class Entry {
String modifier, cls, desc, comment
Set<String> existing
TreeSet<String> children
boolean group = false
@Lazy
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
Object getAt(String key) {
return getProperty(key)
}
}
}
@@ -1,95 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends CheckTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@Override
void check(Reporter reporter, boolean fix) {
final Set<String> known = []
collectKnown(known)
excs.each { f ->
final lines = []
f.eachLine { line ->
int idx = line.indexOf('#')
if (idx === 0 || line.isEmpty()) {
return
}
if (idx !== -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
reporter.report("Invalid: $line")
return
}
def (String key, String value) = line.split('=', 2)
if (!known.contains(key)) {
reporter.report("Unknown: $line")
return
}
String desc = key.split('\\.', 2)[1]
if (!desc.contains('(')) {
reporter.report("Invalid: $line")
return
}
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
reporter.report("Invalid: $line")
return
}
lines.add(line)
return
}
if (fix) f.text = lines.sort().join('\n')
}
}
private void collectKnown(Collection<String> known) {
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
final visitor = new ClassVisitor(Util.ASM_LEVEL) {
private String cls
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
}
}
@@ -1,11 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
@CompileStatic
enum CheckMode {
CHECK,
FIX
CheckMode() {}
}
@@ -1,182 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
@CompileStatic
abstract class CheckPatches extends CheckTask {
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
@Override
void check(Reporter reporter, boolean fix) {
final patchDir = getPatchDir().get().asFile.toPath()
Files.walk(patchDir).withCloseable {
it.filter(Files.&isRegularFile).forEach { path ->
final String relativeName = patchDir.relativize(path).toString()
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
}
}
}
static boolean accessChange(String previous, String current) {
//return ACCESS_MAP[previous] < ACCESS_MAP[current]
return previous != current
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
int oldFixedErrors = reporter.fixed.size()
final lines = Files.readAllLines(patch)
int hunksStart = 0
boolean onlyWhiteSpace = false
final List<String> newLines = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i
for (i = 2; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (HUNK_START_PATTERN.matcher(line).find()) {
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
int toRemove = i - hunksStart
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim == currTrim) {
prefixChange = true
}
def pMatcher = FIELD_PATTERN.matcher(prevLine)
def cMatcher = FIELD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = METHOD_PATTERN.matcher(prevLine)
cMatcher = METHOD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = CLASS_PATTERN.matcher(prevLine)
cMatcher = CLASS_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
final nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
final currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim == currTrim) {
prefixChange = true
}
}
}
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
} else if (isWhiteSpaceChange) {
final prevLineChange = prevLine.startsWithAny('+','-')
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false)
}
}
if (line.contains('\t')) {
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
}
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
}
}
}
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch)
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
Files.newBufferedWriter(patch).withCloseable {
newLines.each { l -> it.write(l) }
}
}
}
}
}
@@ -1,110 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Annotatable
import net.minecraftforge.forge.tasks.InheritanceData
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
@CompileStatic
abstract class CheckSAS extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
sass.each { f ->
final lines = []
f.eachLine { line ->
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
final idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
cls = cls.replaceAll('\\.', '/')
if (inheritance[cls] === null) {
reporter.report("Invalid: $line")
} else if (name.isEmpty()) { //Class SAS
final toAdd = []
final clsSided = isSided(inheritance[cls])
boolean sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
} */
final clsInh = inheritance[cls]
if (clsInh.methods) {
for (entry in clsInh.methods) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else {
reporter.report("Invalid: $line")
}
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else */
reporter.report("Invalid: $line")
} else { // Methods
final clsInh = inheritance[cls]
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
reporter.report("Invalid: $line")
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
if (fix) f.text = lines.join('\n')
}
}
protected static boolean isSided(Annotatable annotatable) {
if (annotatable === null) return false
for (ann in annotatable.annotations) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
return true
}
return false
}
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -1,114 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.ThirdParam
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.VerificationTask
@CompileStatic
abstract class CheckTask extends DefaultTask implements VerificationTask {
@Input
abstract Property<CheckMode> getMode()
private boolean ignoreFailures = false
@Input
@Override
boolean getIgnoreFailures() {
return ignoreFailures
}
@Override
void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures
}
@TaskAction
void run() {
Util.init()
boolean doFix = getMode().get() === CheckMode.FIX
final Reporter reporter = new Reporter(doFix)
check(reporter, doFix)
if (reporter.messages) {
if (getMode().get() === CheckMode.CHECK) {
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
}
} else {
if (logger.isEnabled(LogLevel.DEBUG)) {
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
} else {
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
}
if (reporter.notFixed) {
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
}
}
}
}
}
abstract void check(Reporter reporter, boolean fix)
@CompileStatic
static final class Reporter {
final boolean trackFixed
Reporter(boolean trackFixed) {
this.trackFixed = trackFixed
}
public final List<String> messages = []
public final List<String> fixed = []
public final List<String> notFixed = []
void report(String message, boolean canBeFixed = true) {
messages.add(message)
if (trackFixed) {
if (canBeFixed) {
fixed.add(message)
} else {
notFixed.add(message)
}
}
}
}
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
@DelegatesTo(genericTypeIndex = 0, target = 'type')
@ClosureParams(ThirdParam.FirstGenericType) Closure configuration) {
taskName = taskName.capitalize()
tasks.register("check$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.CHECK)
castedTask.group = 'checks'
}
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.FIX)
castedTask.group = 'checks'
}
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
}
}
-73
View File
@@ -1,73 +0,0 @@
plugins {
id 'java-library'
id 'net.minecraftforge.gradle.patcher'
}
evaluationDependsOn(':mcp')
repositories {
mavenCentral()
maven gradleutils.forgeMaven
maven gradleutils.minecraftLibsMaven
}
java.toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
dependencies {
implementation(libs.forgespi)
}
patcher {
parent = project(':mcp')
mcVersion = MC_VERSION
patchedSrc = file('src/main/java')
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
runs {
clean_client {
client true
taskName 'clean_client'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.client.main.Main'
workingDirectory project.file('run/client')
args '--gameDir', '.'
args '--version', MC_VERSION
args '--assetsDir', downloadAssets.output
args '--assetIndex', '{asset_index}'
args '--accessToken', '0'
}
clean_server {
client false
taskName 'clean_server'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.server.Main'
workingDirectory project.file('run/server')
}
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
}
eclipse.classpath.file.whenMerged {
// Disable optional warnings on the minecraft decompiled source code. It just muddies the warning window and hides warnings in our own codebase
def src = entries.find { it.path == 'src/main/java' }
if (src == null) throw new IllegalStateException("You must run `setup` task before importing")
src.entryAttributes['ignore_optional_problems'] = 'true'
}
tasks.named('genPatches').configure {
onlyIf { false }
}
tasks.named('extractRangeMap').configure {
onlyIf { false }
tool = 'net.minecraftforge:Srg2Source:8.1.0:fatjar'
}
-1021
View File
File diff suppressed because it is too large. Load diff
-89
View File
@@ -1,89 +0,0 @@
import net.minecraftforge.forge.tasks.CleanProperties
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'net.minecraftforge.gradleutils'
group = 'net.minecraftforge'
version = VERSION
println("Version: $version")
repositories {
mavenCentral()
maven gradleutils.forgeMaven
maven gradleutils.minecraftLibsMaven
//mavenLocal()
}
tasks.withType(Javadoc).configureEach {
options.tags = [
'apiNote:a:<em>API Note:</em>',
'implSpec:a:<em>Implementation Requirements:</em>',
'implNote:a:<em>Implementation Note:</em>'
]
options.addStringOption('Xdoclint:all,-missing', '-public')
}
// We need to write the manifest to the binary file so we have properly versioned packaged at dev time.
tasks.register('writeManifest') {
doLast {
if (plugins.findPlugin('net.minecraftforge.gradle.patcher')) // Forge project
universalJar.manifest.writeTo(rootProject.file('src/main/resources/META-INF/MANIFEST.MF'))
else
jar.manifest.writeTo(project.file('src/main/resources/META-INF/MANIFEST.MF'))
}
}
tasks.register('generateResources') {
dependsOn('writeManifest')
}
tasks.named('processResources') {
dependsOn(generateResources)
}
// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build.
tasks.withType(JavaCompile).configureEach {
dependsOn 'generateResources'
dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
options.forkOptions.jvmArgs += '-Xmx6G' // Needed to make compiling faster, and not run out of heap space in some cases.
}
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
// Minecraft and Forge are in the same sourceSet as they are inter dependent.. for now..
sourceSets.each {
def dir = layout.buildDirectory.dir("classes/java/$it.name")
it.output.resourcesDir = dir
it.java.destinationDirectory = dir
}
tasks.register('copyEclipseSettings') {
doLast {
rootProject.fileTree('ide/eclipse/template/.settings/').matching { include '**/*.prefs' }.each { file ->
def target = project.file('.settings/' + file.name)
def temp = new CleanProperties().load(file)
def exst = new CleanProperties().load(target)
exst.put('eclipse.preferences.version', '1')
exst.putAll(temp)
exst.store(target)
}
}
}
// TODO: [Gradle][IntelliJ] Auto trigger these tasks on import.
eclipse {
// Run everytime eclipse builds the code
//autoBuildTasks writeManifest
// Run when importing the project
synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject
}
idea {
module {
// IntelliJ IDEA does not do this by itself anymore...
downloadJavadoc = true
downloadSources = true
}
}
+2 -2
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=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=26.1-&query=$.promos["26.1.1-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=26.1.2-&query=$.promos["26.1.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.1.2-&query=$.promos["26.1.2-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!
+50 -40
View File
@@ -1,68 +1,78 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
gradleutils.displayName = 'FML'
final vendor = 'Forge Development LLC'
description = 'Modifications to Minecraft to enable mod developers.'
dependencies {
compileOnly(libs.jetbrains.annotations)
compileOnly libs.jetbrains.annotations
api(libs.eventbus)
annotationProcessor(libs.eventbus.validator)
api libs.eventbus
implementation(project(':fmlloader'))
implementation(libs.commons.io)
implementation projects.fmlloader
implementation libs.commons.io
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType': 'LIBRARY'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FML',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML',
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge Development LLC'
] as LinkedHashMap, 'net/minecraftforge/fml/')
}
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:-unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
tasks.named('jar', Jar) {
manifest {
attributes([
'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
], 'net/minecraftforge/fml/')
}
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlcore'
pom {
name = project.name
description = 'Modifactions to Minecraft to enable mod developers.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
+86 -61
View File
@@ -1,67 +1,74 @@
import net.minecraftforge.gradleutils.PomUtils
import org.gradle.api.plugins.jvm.JvmTestSuite
import org.gradle.internal.os.OperatingSystem
plugins {
id 'java-library'
id 'jvm-test-suite'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = "natives-windows"
break
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = "natives-windows"
break
}
gradleutils.displayName = 'FML Early Display'
final vendor = 'Forge Development LLC'
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation(libs.bundles.lwjgl)
implementation('org.slf4j:slf4j-api:1.8.0-beta4')
implementation(libs.jopt.simple)
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2')
testImplementation('org.powermock:powermock-core:2.0.9')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
testRuntimeOnly('org.slf4j:slf4j-jdk14:1.8.0-beta4')
testRuntimeOnly("org.lwjgl:lwjgl::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-glfw::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-opengl::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives")
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
implementation libs.bundles.lwjgl
implementation earlyDisplayLibs.slf4j.api
implementation libs.jopt.simple
}
tasks.named('test', Test).configure {
useJUnitPlatform()
// 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 }
].with { list ->
provider { list.collect { it.get() } }
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FML Early Display',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML Early Display',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': '1.0'
] as LinkedHashMap, 'net/minecraftforge/fml/earlydisplay/')
testing.suites.named('test', JvmTestSuite) {
useJUnitJupiter('5.8.2')
dependencies {
implementation earlyDisplayTestLibs.powermock.core
runtimeOnly earlyDisplayLibs.slf4j.jdk14
runtimeOnly.bundle lwjglTestLibs
}
}
@@ -69,24 +76,42 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
tasks.named('jar', Jar) {
manifest {
attributes([
'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'
], 'net/minecraftforge/fml/earlydisplay/')
}
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlearlydisplay'
pom {
name = project.name
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
@@ -459,7 +459,9 @@ 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];
@@ -482,7 +484,10 @@ 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);
@@ -491,6 +496,18 @@ 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;
@@ -648,4 +665,4 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.context.elementShader().close();
SimpleBufferBuilder.destroy();
}
}
}
+144 -100
View File
@@ -1,69 +1,75 @@
import net.minecraftforge.gradleutils.PomUtils
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import net.minecraftforge.forgedev.legacy.tasks.JarJarMetadataOptions
import org.gradle.api.plugins.jvm.JvmTestSuite
import javax.inject.Inject
plugins {
id 'java-library'
id 'jvm-test-suite'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias(libs.plugins.apt)
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
alias libs.plugins.apt
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
configurations.forEach{ it.transitive = false }
dependencies {
compileOnly(libs.jetbrains.annotations)
api(libs.bundles.asm) // Needed by all the black magic
api(libs.forgespi)
api(libs.mergetool.api)
api(libs.log4j.api)
api(libs.slf4j.api)
api(libs.guava)
api(libs.gson)
api(libs.maven.artifact)
api(libs.apache.commons)
api(libs.bundles.night.config)
api(libs.modlauncher)
api(libs.mojang.logging)
api(libs.jarjar.selector)
api(libs.jarjar.meta)
implementation(libs.jopt.simple)
implementation(libs.securemodules)
implementation(libs.accesstransformers)
implementation(libs.terminalconsoleappender)
implementation(libs.jimfs)
implementation(libs.roimfs)
// 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)
annotationProcessor(libs.bundles.log4j)
testCompileOnly(libs.jetbrains.annotations)
testRuntimeOnly(libs.bootstrap)
}
gradleutils.displayName = 'FMLLoader'
final vendor = 'Forge Development LLC'
description = 'Modifications to Minecraft to enable mod developers.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FMLLoader',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FMLLoader',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION
] as LinkedHashMap, 'net/minecraftforge/fml/loading/')
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
// TODO [FMLLoader] Figure out a better way to lock transitive dependencies to the explicit versions shipped by the isntaller
api(libs.bundles.asm ){ transitive = false } // Needed by all the black magic
api(libs.forgespi ){ transitive = false }
api(libs.mergetool.api ){ transitive = false }
api(libs.log4j.api ){ transitive = false }
api(libs.slf4j.api ){ transitive = false }
api(libs.guava ){ transitive = false }
api(libs.gson ){ transitive = false }
api(libs.maven.artifact ){ transitive = false }
api(libs.apache.commons ){ transitive = false }
api(libs.bundles.night.config){ transitive = false }
api(libs.modlauncher ){ transitive = false }
api(libs.mojang.logging ){ transitive = false }
api(libs.jarjar.selector ){ transitive = false }
api(libs.jarjar.meta ){ transitive = false }
implementation(libs.jopt.simple ){ transitive = false }
implementation(libs.securemodules ){ transitive = false }
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 }
annotationProcessor(libs.bundles.log4j){ transitive = false }
}
testing.suites.named('test', JvmTestSuite) {
dependencies {
compileOnly libs.jetbrains.annotations
runtimeOnly libs.bootstrap
}
}
@@ -71,39 +77,81 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
tasks.named('jar', Jar) {
manifest {
attributes([
'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
], 'net/minecraftforge/fml/loading/')
}
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlloader'
pom {
name = project.name
description = 'Modifactions to Minecraft to enable mod developers.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
}
tasks.register('writeForgeVersionJson') {
doLast {
file('src/main/resources/forge_version.json').json = [
forge: FORGE_VERSION,
mc: MC_VERSION,
mcp: MCP_VERSION
]
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadataOptions) {
// A simple task to write JSON to a file. Using Task#doLast to output files is no longer supported.
// This code may seem verbose, but it's better this way. If more projects need this, we can move it to buildSrc.
@CompileStatic
@PackageScope abstract class WriteForgeVersionJson extends DefaultTask {
abstract @Input Property<String> getForgeVersion()
abstract @Input Property<String> getMinecraftVersion()
abstract @Input Property<String> getMcpVersion()
abstract @OutputFile RegularFileProperty getOutputFile()
@Inject
WriteForgeVersionJson() {}
@TaskAction
@CompileDynamic
void exec() {
var json = new groovy.json.JsonBuilder()
json {
forge this.forgeVersion.get()
mc this.minecraftVersion.get()
mcp this.mcpVersion.get()
}
this.outputFile.asFile.get().text = json.toPrettyString()
}
}
tasks.register('writeForgeVersionJson', WriteForgeVersionJson) {
forgeVersion = project.forgeVersion
minecraftVersion = project.minecraftVersion
mcpVersion = project.mcpVersion
outputFile = file('src/main/resources/forge_version.json')
}
tasks.register('jarJarOptionsJson', JarJarMetadataOptions) {
metadataFile = project.file('src/main/resources/jarjar_options.json')
// This is resolved too early by cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(DiscoveryData) But eventually...
//add(libs.mixin, 'org/spongepowered/asm/mixin/Mixin.class')
@@ -118,29 +166,25 @@ tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadat
}
}
tasks.named('generateResources').configure {
dependsOn('eclipseJdt')
dependsOn('eclipseJdtApt')
dependsOn('eclipseFactorypath')
dependsOn('writeForgeVersionJson')
dependsOn('jarJarOptionsJson')
tasks.named('generateResources') {
dependsOn(
tasks.named('eclipseJdt'),
tasks.named('eclipseJdtApt'),
tasks.named('eclipseFactorypath'),
tasks.named('writeForgeVersionJson', WriteForgeVersionJson),
tasks.named('jarJarOptionsJson')
)
}
tasks.named('sourcesJar') {
dependsOn('jarJarOptionsJson')
}
eclipse {
classpath {
// We need to set the default output directory for the log4j annotation processor
// Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories
// So we have to do a hacky whenMerged
//defaultOutputDir = file('bin/main')
file.whenMerged {
entries.each { entry ->
if (entry.kind == 'output' && entry.hasProperty('path'))
entry.path = 'bin/main'
}
}
eclipse.classpath {
// We need to set the default output directory for the log4j annotation processor
// Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories
// So we have to do a hacky whenMerged
//defaultOutputDir = file('bin/main')
file.whenMerged {
entries.each { entry ->
if (entry.kind == 'output' && entry.hasProperty('path'))
entry.path = 'bin/main'
}
}
}
@@ -6,11 +6,13 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -24,6 +26,7 @@ public class ModFileParser {
return parser.build(modFile);
}
// Note: Although @Nullable, several other places in FML assume ModFileInfo is not null. Keep this in mind
public static @Nullable IModFileInfo modsTomlParser(final IModFile imodFile) {
ModFile modFile = (ModFile) imodFile;
LOGGER.debug(LogMarkers.LOADING,"Considering mod file candidate {}", modFile.getFilePath());
@@ -34,9 +37,15 @@ public class ModFileParser {
}
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
try {
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
} catch (ParsingException e) {
LOGGER.error("Mod candidate {} contains a corrupt or misconfigured toml.", modFile.getFileName());
throw new ModFileLoadingException("Mod candidate " + modFile.getFileName() + " contains a corrupt or misconfigured toml.");
}
}
}
@@ -11,8 +11,8 @@
"artifact": "MixinExtras"
},
"version": {
"range": "[0.5.3,)",
"artifactVersion": "0.5.3"
"range": "[0.5.4,)",
"artifactVersion": "0.5.4"
},
"path": "",
"isObfuscated": false
@@ -24,7 +24,7 @@
"artifact": "mixinextras-forge"
},
"version": {
"artifactVersion": "0.5.3"
"artifactVersion": "0.5.4"
},
"path": "",
"isObfuscated": false
+59 -54
View File
@@ -1,73 +1,78 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias(libs.plugins.apt)
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
configurations.forEach{ it.transitive = false }
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(libs.bundles.asm) // Needed by all the black magic
implementation(libs.log4j.api)
implementation(libs.gson)
implementation(libs.modlauncher)
implementation(libs.coremods.api)
testCompileOnly(libs.jetbrains.annotations)
}
gradleutils.displayName = 'Forge Transformers'
final vendor = 'Forge Development LLC'
description = 'Forge-specific transformers unrelated to the FML project.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge.transformers',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'Forge Transformers',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'Forge Transformers',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION
] as LinkedHashMap, 'net/minecraftforge/forge/transformers/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication) {
from components.java
artifactId = 'forge-transformers'
pom {
name = project.name
description = 'Forge-specific transformers unrelated to the FML project.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
changelog {
from changelogBase
}
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(libs.bundles.asm ){ transitive = false } // Needed by all the black magic
implementation(libs.log4j.api ){ transitive = false }
implementation(libs.gson ){ transitive = false }
implementation(libs.modlauncher ){ transitive = false }
implementation(libs.coremods.api){ transitive = false }
testCompileOnly(libs.jetbrains.annotations)
}
tasks.named('jar', Jar) {
manifest {
attributes([
'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
], 'net/minecraftforge/forge/transformers/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom { pom ->
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
@@ -42,12 +42,6 @@
"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": [
@@ -85,24 +79,12 @@
"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": [
]
},
{
"class": "net/minecraft/world/entity/raid/Raid",
"methods": [
@@ -158,11 +140,5 @@
"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": [
]
}
]
+23 -11
View File
@@ -1,17 +1,29 @@
# TODO Re-evaluate
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx6G
org.gradle.daemon=false
org.gradle.warning.mode=all
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configureondemand=true
JAVA_VERSION=25
MC_VERSION=26.1.1
MC_NEXT_VERSION=26.2
MCP_VERSION=20260401.101545
MAPPING_CHANNEL=official
MAPPING_VERSION=26.1.1
# TODO [ForgeDev] Enable this once ForgeDev 7 tasks are fully migrated
org.gradle.configuration-cache=false
org.gradle.configuration-cache.parallel=false
#org.gradle.configuration-cache.problems=warn
#org.gradle.configuration-cache.integrity-check=true
# Set to true before the first build of a new MC version, so we don't do compatibility checks
CHECK_COMPATIBILITY=false
# Set to true to allow for fuzzy patching. Useful during updateing
UPDATING=false
net.minecraftforge.gradleutils.ide.automatic.sources=true
net.minecraftforge.gradleutils.compilation.defaults=true
net.minecraftforge.gradle.merge-source-sets=true
# Controls if the compatibility checks are auto-added to the 'check' task.
# The check tasks are still created and can be run, they just are not grouped with the normal 'check' task.
# Set to false for the first builds of a new Minecraft version.
net.minecraftforge.forge.build.check.compatibility=false
# Set to true to allow for fuzzy patching. Useful during updating.
net.minecraftforge.forge.build.updating=false
# Set to true to enable the 'validatePublish' task which is currently a work in progress
net.minecraftforge.forgedev.validate.publish=false
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -170,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+3 -2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -68,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+61 -48
View File
@@ -1,66 +1,79 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation(libs.unsafe)
implementation(libs.securemodules)
}
gradleutils.displayName = 'JavaFMLMod'
final vendor = 'Forge Development LLC'
description = 'Language provider for Minecraft Forge that provides basic Java mod functionality.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.javafmlmod',
'FMLModType': 'LANGPROVIDER'
] as LinkedHashMap)
attributes([
'Specification-Title': 'JavaFMLMod',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'JavaFMLMod',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION
] as LinkedHashMap, 'net/minecraftforge/fml/javafmlmod/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'javafmllanguage'
pom {
name = project.name
description = 'Language provider for Minecraft Forge that provides basic java mod functionality'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
implementation libs.unsafe
implementation libs.securemodules
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'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
], 'net/minecraftforge/fml/javafmlmod/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
+58 -46
View File
@@ -1,65 +1,77 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
gradleutils.displayName = 'LowCodeMod'
final vendor = 'Forge Development LLC'
description = 'Language provider for Minecraft Forge that loads mods without a Java entrypoint.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.lowcodemod',
'FMLModType': 'LANGPROVIDER'
] as LinkedHashMap)
attributes([
'Specification-Title': 'LowCodeMod',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1.0',
'Implementation-Title': project.name,
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION.split('\\.')[0]
] as java.util.LinkedHashMap, 'net/minecraftforge/fml/lowcodemod/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'lowcodelanguage'
pom {
name = project.name
description = 'Language provider for Minecraft Forge that loads resource packs as mods'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'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]
], 'net/minecraftforge/fml/lowcodemod/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
+58 -45
View File
@@ -1,64 +1,77 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
}
gradleutils.displayName = 'MCLanguage'
final vendor = 'Forge Development LLC'
description = 'Language provider for Minecraft Forge that provides Minecraft Itself.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.mclanguageprovider',
'FMLModType': 'LANGPROVIDER'
] as LinkedHashMap)
attributes([
'Specification-Title': 'MCLanguage',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'MCLanguage',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': '1.0'
] as LinkedHashMap, 'net/minecraftforge/fml/mclanguageprovider/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'mclanguage'
pom {
name = project.name
description = 'Language provider for Minecraft Forge that provides Minecraft Itself'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'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'
], 'net/minecraftforge/fml/mclanguageprovider/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
+6
View File
@@ -0,0 +1,6 @@
[versions]
java = "25"
minecraft = "26.1.2"
minecraft-next = "26.2"
mcp = "20260409.101008"
changelog-base = "64.0"
@@ -54,13 +54,27 @@
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, getMobEffectSprite(effect), x + 3, y + 3, 18, 18, ARGB.white(alpha));
}
}
@@ -610,15 +_,23 @@
@@ -610,15 +_,37 @@
}
public void extractSelectedItemName(final GuiGraphicsExtractor graphics) {
+ renderSelectedItemName(graphics, 0);
+ }
+
+ // Forge: Maintain vanilla parity during FLD#BACKGROUND
+ public final void updateContextualInfo(final GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
+ Gui.ContextualInfo nextContextualInfo = this.nextContextualInfoState();
+ if (nextContextualInfo != this.contextualInfoBar.getKey()) {
+ this.contextualInfoBar = Pair.of(nextContextualInfo, this.contextualInfoBarRenderers.get(nextContextualInfo).get());
+ }
+
+ this.contextualInfoBar.getValue().extractBackground(graphics, deltaTracker);
+ }
+ // Forge: Same as above but for FLD#CONTEXTUAL_INFO
+ public final void extractContextualInfoState(final GuiGraphicsExtractor graphics, DeltaTracker deltaTracker) {
+ this.contextualInfoBar.getValue().extractRenderState(graphics, deltaTracker);
+ }
+
+ public void renderSelectedItemName(final GuiGraphicsExtractor graphics, int yShift) {
if (this.toolHighlightTimer > 0 && !this.lastToolHighlight.isEmpty()) {
MutableComponent str = Component.empty().append(this.lastToolHighlight.getHoverName()).withStyle(this.lastToolHighlight.getRarity().color());
@@ -17,15 +17,15 @@
public void handleBlockChangedAck(final int sequence) {
if (SharedConstants.DEBUG_BLOCK_BREAK) {
@@ -273,6 +_,8 @@
update.run();
}
@@ -243,6 +_,8 @@
this.serverSimulationDistance = serverSimulationDistance;
this.environmentAttributes = this.addEnvironmentAttributeLayers(EnvironmentAttributeSystem.builder()).build();
this.updateSkyBrightness();
+ this.gatherCapabilities();
+ net.minecraftforge.event.ForgeEventFactory.onLevelLoad(this);
}
public @Nullable EndFlashState endFlashState() {
private EnvironmentAttributeSystem.Builder addEnvironmentAttributeLayers(final EnvironmentAttributeSystem.Builder environmentAttributes) {
@@ -352,6 +_,7 @@
entity.setOldPosAndRot();
entity.tickCount++;
@@ -22,7 +22,7 @@
}
this.addLateDebugPass(frame, this.levelRenderState.cameraRenderState, terrainFog, modelViewMatrix);
+ net.minecraftforge.client.FramePassManager.insertForgePasses(frame, targets, this.levelRenderState); // Forge: Modded passes are inserted here.
+ net.minecraftforge.client.FramePassManager.insertForgePasses(frame, targets, this.levelRenderState, deltaTracker); // Forge: Modded passes are inserted here.
profiler.popPush("executeFrameGraph");
frame.execute(resourceAllocator, new FrameGraphBuilder.Inspector() {
{
@@ -8,13 +8,25 @@
SectionCompiler.Results results = new SectionCompiler.Results();
BlockPos minPos = sectionPos.origin();
BlockPos maxPos = minPos.offset(15, 15, 15);
@@ -109,7 +_,8 @@
@@ -100,6 +_,9 @@
}
if (blockState.getRenderShape() == RenderShape.MODEL) {
+ var model = this.blockModelSet.get(blockState);
+ var modelData = modelDataMap.getOrDefault(pos, net.minecraftforge.client.model.data.ModelData.EMPTY);
+ modelData = model.getModelData(region, pos, blockState, modelData);
blockRenderer.tesselateBlock(
ModelBlockRenderer.forceOpaque(this.cutoutLeaves, blockState) ? opaqueQuadOutput : quadOutput,
SectionPos.sectionRelative(pos.getX()),
@@ -108,8 +_,9 @@
region,
pos,
blockState,
this.blockModelSet.get(blockState),
- this.blockModelSet.get(blockState),
- blockState.getSeed(pos)
+ model,
+ blockState.getSeed(pos),
+ modelDataMap.getOrDefault(pos, net.minecraftforge.client.model.data.ModelData.EMPTY)
+ modelData
);
}
} catch (Throwable var21) {
@@ -166,7 +166,7 @@
+ public final boolean isInFluid(java.util.function.BiPredicate<net.minecraftforge.fluids.FluidType, Double> predicate, boolean forAllTypes) {
+ boolean match = false;
+ for (var entry : this.trackerByFluidType.entrySet()) {
+ if (predicate.test(entry.getKey(), entry.getValue().height)) {
+ if (entry.getValue().height > 0 && predicate.test(entry.getKey(), entry.getValue().height)) {
+ match = true;
+ if (!forAllTypes)
+ return true;
@@ -25,7 +25,7 @@
return strippedBlock;
} else {
- Optional<BlockState> scrapedBlock = WeatheringCopper.getPrevious(oldState);
+ var scrape = ctx == null ? null : oldState.getToolModifiedState(ctx, net.minecraftforge.common.ToolActions.AXE_STRIP, false);
+ var scrape = ctx == null ? null : oldState.getToolModifiedState(ctx, net.minecraftforge.common.ToolActions.AXE_SCRAPE, false);
+ Optional<BlockState> scrapedBlock = scrape != null ? Optional.of(scrape) : WeatheringCopper.getPrevious(oldState);
if (scrapedBlock.isPresent()) {
spawnSoundAndParticle(level, pos, player, oldState, SoundEvents.AXE_SCRAPE, 3005);
@@ -96,7 +96,7 @@
List<Component> lines = Lists.newArrayList();
lines.add(this.getStyledHoverName());
this.addDetailsToTooltip(context, display, player, tooltipFlag, lines::add);
+ net.minecraftforge.event.ForgeEventFactory.onItemTooltip(this, player, lines, tooltipFlag);
+ net.minecraftforge.event.ForgeEventFactory.onItemTooltip(this, player, lines, tooltipFlag, context, display);
return lines;
}
}
+191 -115
View File
@@ -1,119 +1,34 @@
pluginManagement {
repositories {
gradlePluginPortal()
//mavenLocal()
maven { url = 'https://maven.minecraftforge.net/' }
}
}
import groovy.transform.Field
buildscript {
dependencies {
classpath('com.google.code.gson:gson') {
version {
strictly '2.11.0'
pluginManagement {
private static final @Field String FORGEDEV_VERSION = '7.0.0-beta.51'
if (new File('forgedev/settings.gradle').exists()) {
includeBuild 'forgedev'
} else {
resolutionStrategy.eachPlugin {
// TODO [ForgeDev] Consolidate these entry-points
switch (requested.id.id) {
case 'net.minecraftforge.forgedev':
case 'net.minecraftforge.forge.build.convention':
useVersion(FORGEDEV_VERSION)
break
}
}
}
repositories {
gradlePluginPortal()
maven { url = 'https://maven.minecraftforge.net/' }
//mavenLocal()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
dependencyResolutionManagement {
versionCatalogs {
libs {
plugin('apt', 'com.diffplug.eclipse.apt').version('4.2.0')
library('forgespi', 'net.minecraftforge:forgespi:8.0.0') // Needs modlauncher
library('modlauncher', 'net.minecraftforge:modlauncher:10.2.4') // Needs securemodules
library('securemodules', 'net.minecraftforge:securemodules:2.2.24') // Needs unsafe
library('unsafe', 'net.minecraftforge:unsafe:0.9.2')
library('accesstransformers', 'net.minecraftforge:accesstransformers:8.2.2')
library('coremods-api', 'net.minecraftforge:coremods-api:5.3.0')
version('eventbus', '7.0.1')
library('eventbus', 'net.minecraftforge', 'eventbus').versionRef('eventbus')
library('eventbus-validator', 'net.minecraftforge', 'eventbus-validator').versionRef('eventbus')
library('mergetool-api', 'net.minecraftforge:mergetool-api:1.0')
library('roimfs', 'net.minecraftforge:roimfs:1.0.0') // ReadOnlyInMemoryFileSystem - Used for JarInJar extraction at runtime
library('mixin', 'org.spongepowered:mixin:0.8.7')
version('mixinextras', '0.5.3')
library('mixinextras-forge', 'io.github.llamalad7', 'mixinextras-forge').versionRef('mixinextras')
library('mixinextras-common', 'io.github.llamalad7', 'mixinextras-common').versionRef('mixinextras')
library('jetbrains-annotations', 'org.jetbrains:annotations:24.1.0') // for ApiStatus annotations
library('jspecify', 'org.jspecify:jspecify:1.0.0') // for nullability annotations
library('slf4j-api', 'org.slf4j:slf4j-api:2.0.7')
library('maven-artifact', 'org.apache.maven:maven-artifact:3.8.8')
// Google's InMemory File System. Used by ForgeDev Tests for now, but could be useful for a lot of things.
library('jimfs', 'com.google.jimfs:jimfs:1.3.0')
bundle('jimfs', ['guava', 'failureaccess'])
version('bootstrap', '2.1.8')
library('bootstrap', 'net.minecraftforge', 'bootstrap' ).versionRef('bootstrap') // Needs modlauncher
library('bootstrap-api', 'net.minecraftforge', 'bootstrap-api' ).versionRef('bootstrap')
library('bootstrap-dev', 'net.minecraftforge', 'bootstrap-dev' ).versionRef('bootstrap')
library('bootstrap-shim', 'net.minecraftforge', 'bootstrap-shim').versionRef('bootstrap')
// ASM it's used for so many of our hacks
version('asm', '9.9.1')
library('asm', 'org.ow2.asm', 'asm' ).versionRef('asm')
library('asm-tree', 'org.ow2.asm', 'asm-tree' ).versionRef('asm')
library('asm-util', 'org.ow2.asm', 'asm-util' ).versionRef('asm')
library('asm-commons', 'org.ow2.asm', 'asm-commons' ).versionRef('asm')
library('asm-analysis', 'org.ow2.asm', 'asm-analysis').versionRef('asm')
bundle('asm', ['asm', 'asm-tree', 'asm-util', 'asm-commons', 'asm-analysis'])
// Terminal Console Appender.. essentually make pretty colors in the console, but it's been a PITA
library('terminalconsoleappender', 'net.minecrell:terminalconsoleappender:1.2.0')
version('jline', '3.25.1')
library('jline-reader', 'org.jline', 'jline-reader' ).versionRef('jline')
library('jline-terminal', 'org.jline', 'jline-terminal' ).versionRef('jline')
library('jline-terminal-jna', 'org.jline', 'jline-terminal-jna').versionRef('jline') // Colors and tab completeion
bundle('terminalconsoleappender', ['terminalconsoleappender', 'jline-reader', 'jline-terminal', 'jline-terminal-jna'])
// The core of our configuration system, it has many flaws, but it works for the most part
version('night-config', '3.7.4')
library('night-config-toml', 'com.electronwill.night-config', 'toml').versionRef('night-config')
library('night-config-core', 'com.electronwill.night-config', 'core').versionRef('night-config')
bundle('night-config', ['night-config-toml', 'night-config-core'])
// Jar in Jar FileSystem
version('jarjar', '0.4.2')
library('jarjar-fs', 'net.minecraftforge', 'JarJarFileSystems').versionRef('jarjar')
library('jarjar-meta', 'net.minecraftforge', 'JarJarMetadata' ).versionRef('jarjar')
library('jarjar-selector', 'net.minecraftforge', 'JarJarSelector' ).versionRef('jarjar')
bundle('jarjar', ['jarjar-fs', 'jarjar-selector', 'jarjar-meta'])
// These are libraries shipped by the MC launcher, try and keep them in sync with the manifest
// but honestly if we don't it just means we ship them as normal libraries by adding them to the installer config in the forge project
library('gson', 'com.google.code.gson:gson:2.11.0')
library('guava', 'com.google.guava:guava:33.5.0-jre')
library('failureaccess', 'com.google.guava:failureaccess:1.0.3')
version('log4j', '2.24.3')
library('log4j-api', 'org.apache.logging.log4j', 'log4j-api' ).versionRef('log4j')
library('log4j-core', 'org.apache.logging.log4j', 'log4j-core').versionRef('log4j')
bundle('log4j', ['log4j-api', 'log4j-core'])
library('apache-commons', 'org.apache.commons:commons-lang3:3.17.0')
library('mojang-logging', 'com.mojang:logging:1.5.10')
library('jopt-simple', 'net.sf.jopt-simple:jopt-simple:5.0.4')
library('commons-io', 'commons-io:commons-io:2.17.0')
version('lwjgl', '3.4.1')
library('lwjgl', 'org.lwjgl', 'lwjgl' ).versionRef('lwjgl')
library('lwjgl-glfw', 'org.lwjgl', 'lwjgl-glfw' ).versionRef('lwjgl')
library('lwjgl-opengl', 'org.lwjgl', 'lwjgl-opengl').versionRef('lwjgl')
library('lwjgl-stb', 'org.lwjgl', 'lwjgl-stb' ).versionRef('lwjgl')
library('lwjgl-tinyfd', 'org.lwjgl', 'lwjgl-tinyfd').versionRef('lwjgl')
bundle('lwjgl', ['lwjgl', 'lwjgl-glfw', 'lwjgl-opengl', 'lwjgl-stb', 'lwjgl-tinyfd'])
}
}
}
rootProject.name = 'ForgeRoot'
rootProject.name = 'forge'
include 'fmlloader'
include 'fmlcore'
@@ -123,15 +38,176 @@ include 'lowcodelanguage'
include 'fmlearlydisplay'
include 'forge-transformers'
include ':mcp'
project(":mcp").projectDir = file("projects/mcp")
enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS'
include ':forge'
project(":forge").projectDir = file("projects/forge")
project(':forge').buildFileName = '../../build_forge.gradle'
gradle.beforeProject { Project project ->
project.pluginManager.withPlugin('net.minecraftforge.forge.build.convention') {
// NOTE: We are defining these variables in here so that the projects can stay isolated, increasing build time.
// See minecraft.versions.toml for the versions where you can change these values.
//@formatter:off
project.ext.javaVersion = project.gradleutils.unpack(project.bootLibs.versions.java)
project.ext.minecraftVersion = project.gradleutils.unpack(project.bootLibs.versions.minecraft)
project.version = project.gitversion.getMCTagOffsetBranch(project.ext.minecraftVersion)
project.ext.forgeVersion = project.version.substring(project.ext.minecraftVersion.length() + 1)
project.ext.minecraftNextVersion = project.gradleutils.unpack(project.bootLibs.versions.minecraft.next)
project.ext.mcpVersion = project.gradleutils.unpack(project.bootLibs.versions.mcp)
project.ext.changelogBase = project.gradleutils.unpack(project.bootLibs.versions.changelog.base)
//@formatter:on
}
if (false && !System.env.TEAMCITY_VERSION) {
include ':clean'
project(':clean').projectDir = file('projects/clean')
project(':clean').buildFileName = '../../build_clean.gradle'
// NOTE: We are adding dependencies to projects instead of through settings, so we don't have to apply forgedev in settings.
// Applying forgedev in settings will break IDE linting support, which makes it very hard to work with.
// We can reconsider this if/when switching to Kotlin DSL, or if JetBrains gets their shit together.
project.repositories {
// Libraries has to be before maven central because Mojang hosts a classifer that central doesn't (org.lwjgl:lwjgl-freetype:3.3.3:natives-macos-patch)
maven { url = 'https://libraries.minecraft.net/' }
mavenCentral()
project.pluginManager.withPlugin('net.minecraftforge.gradleutils') {
maven project.gradleutils.forgeMaven
}
project.pluginManager.withPlugin('net.minecraftforge.forgedev') {
maven project.forgedev.mavenizer
}
mavenLocal()
}
}
dependencyResolutionManagement.versionCatalogs {
register('bootLibs') {
description = 'Important version numbers that are externally declared for easy modification.'
// Contains Java, Minecraft, and MCP versions
from(files('minecraft.versions.toml'))
}
register('buildLibs') {
description = 'Libraries used solely during the build and publish process.'
library 'binarypatcher', 'net.minecraftforge', 'binarypatcher' version '1.3.4'
library 'installer', 'net.minecraftforge', 'installer' version '2.2.+'
library 'installertools', 'net.minecraftforge', 'installertools' version '1.4.5'
library 'srg2source', 'net.minecraftforge', 'Srg2Source' version '8.2.0'
}
//@formatter:off
register('libs') {
description = 'Libraries used by Forge and its subprojects.'
plugin 'licenser', 'net.minecraftforge.licenser' version '1.2.0'
plugin 'versions', 'com.github.ben-manes.versions' version '0.56.0'
plugin 'gradleutils', 'net.minecraftforge.gradleutils' version '3.4.5'
plugin 'gitversion', 'net.minecraftforge.gitversion' version '3.1.7'
plugin 'changelog', 'net.minecraftforge.changelog' version '3.2.2'
plugin 'renamer', 'net.minecraftforge.renamer' version '1.0.16'
plugin 'download', 'de.undercouch.download' version '5.6.0'
plugin 'jarsigner', 'net.minecraftforge.gradlejarsigner' version '1.2.1'
plugin 'apt', 'com.diffplug.eclipse.apt' version '4.4.1'
// TODO [Forge][Buildscript We should not be needing to manually add transitive dependencies
// If we need to make strict versions for dependencies, we should do so using dependency constraints
library 'forgespi', 'net.minecraftforge', 'forgespi' version '8.0.0' // Needs modlauncher
library 'modlauncher', 'net.minecraftforge', 'modlauncher' version '10.2.6' // Needs securemodules
library 'securemodules', 'net.minecraftforge', 'securemodules' version '2.2.24' // Needs unsafe
library 'unsafe', 'net.minecraftforge', 'unsafe' version '0.9.2'
library 'accesstransformers', 'net.minecraftforge', 'accesstransformers' version '8.2.17'
library 'coremods-api', 'net.minecraftforge', 'coremods-api' version '5.3.1'
version 'eventbus', '7.0.5'
library 'eventbus', 'net.minecraftforge', 'eventbus' versionRef 'eventbus'
library 'eventbus-validator', 'net.minecraftforge', 'eventbus-validator' versionRef 'eventbus'
library 'mergetool-api', 'net.minecraftforge', 'mergetool-api' version '1.0'
library 'roimfs', 'net.minecraftforge', 'roimfs' version '1.0.0' // ReadOnlyInMemoryFileSystem - Used for JarInJar extraction at runtime
library 'mixin', 'org.spongepowered', 'mixin' version '0.8.7'
version 'mixinextras', '0.5.4'
library 'mixinextras-forge', 'io.github.llamalad7', 'mixinextras-forge' versionRef 'mixinextras'
library 'mixinextras-common', 'io.github.llamalad7', 'mixinextras-common' versionRef 'mixinextras'
library 'jetbrains-annotations', 'org.jetbrains', 'annotations' version '24.0.1' // for ApiStatus annotations
library 'jspecify', 'org.jspecify', 'jspecify' version '1.0.0' // for nullability annotations
library 'slf4j-api', 'org.slf4j', 'slf4j-api' version '2.0.17'
library 'maven-artifact', 'org.apache.maven', 'maven-artifact' version '3.8.8'
// Google's InMemory File System. Used by ForgeDev Tests for now, but could be useful for a lot of things.
library 'jimfs', 'com.google.jimfs:jimfs:1.3.0'
bundle 'jimfs', ['guava', 'failureaccess']
version 'bootstrap', '2.1.8'
library 'bootstrap', 'net.minecraftforge', 'bootstrap' versionRef 'bootstrap' // Needs modlauncher
library 'bootstrap-api', 'net.minecraftforge', 'bootstrap-api' versionRef 'bootstrap'
library 'bootstrap-dev', 'net.minecraftforge', 'bootstrap-dev' versionRef 'bootstrap'
library 'bootstrap-shim', 'net.minecraftforge', 'bootstrap-shim' versionRef 'bootstrap'
// ASM it's used for so many of our hacks
version 'asm', '9.10.1'
library 'asm', 'org.ow2.asm', 'asm' versionRef 'asm'
library 'asm-tree', 'org.ow2.asm', 'asm-tree' versionRef 'asm'
library 'asm-util', 'org.ow2.asm', 'asm-util' versionRef 'asm'
library 'asm-commons', 'org.ow2.asm', 'asm-commons' versionRef 'asm'
library 'asm-analysis', 'org.ow2.asm', 'asm-analysis' versionRef 'asm'
bundle 'asm', ['asm', 'asm-tree', 'asm-util', 'asm-commons', 'asm-analysis']
// Terminal Console Appender. essentially make pretty colors in the console, but it's been a PITA
library 'terminalconsoleappender', 'net.minecrell', 'terminalconsoleappender' version '1.2.0'
version 'jline', '3.25.1'
library 'jline-reader', 'org.jline', 'jline-reader' versionRef 'jline'
library 'jline-terminal', 'org.jline', 'jline-terminal' versionRef 'jline'
library 'jline-terminal-jna', 'org.jline', 'jline-terminal-jna' versionRef 'jline' // Colors and tab completeion
bundle 'terminalconsoleappender', ['terminalconsoleappender', 'jline-reader', 'jline-terminal', 'jline-terminal-jna']
// The core of our configuration system, it has many flaws, but it works for the most part
version 'night-config', '3.7.4'
library 'night-config-toml', 'com.electronwill.night-config', 'toml' versionRef 'night-config'
library 'night-config-core', 'com.electronwill.night-config', 'core' versionRef 'night-config'
bundle 'night-config', ['night-config-toml', 'night-config-core']
// Jar in Jar FileSystem
version 'jarjar', '0.4.2'
library 'jarjar-fs', 'net.minecraftforge', 'JarJarFileSystems' versionRef 'jarjar'
library 'jarjar-meta', 'net.minecraftforge', 'JarJarMetadata' versionRef 'jarjar'
library 'jarjar-selector', 'net.minecraftforge', 'JarJarSelector' versionRef 'jarjar'
bundle 'jarjar', ['jarjar-fs', 'jarjar-selector', 'jarjar-meta']
// These are libraries shipped by the MC launcher, try and keep them in sync with the manifest
// but honestly if we don't it just means we ship them as normal libraries by adding them to the installer config in the forge project
library 'gson', 'com.google.code.gson', 'gson' version '2.11.0'
library 'guava', 'com.google.guava', 'guava' version '33.5.0-jre'
library 'failureaccess', 'com.google.guava', 'failureaccess' version '1.0.3'
version 'log4j', '2.25.2'
library 'log4j-api', 'org.apache.logging.log4j', 'log4j-api' versionRef 'log4j'
library 'log4j-core', 'org.apache.logging.log4j', 'log4j-core' versionRef 'log4j'
bundle 'log4j', ['log4j-api', 'log4j-core']
library 'apache-commons', 'org.apache.commons', 'commons-lang3' version '3.19.0'
library 'mojang-logging', 'com.mojang', 'logging' version '1.6.11'
library 'jopt-simple', 'net.sf.jopt-simple', 'jopt-simple' version '5.0.4'
library 'commons-io', 'commons-io', 'commons-io' version '2.20.0'
version 'lwjgl', '3.4.1'
library 'lwjgl', 'org.lwjgl', 'lwjgl' versionRef 'lwjgl'
library 'lwjgl-glfw', 'org.lwjgl', 'lwjgl-glfw' versionRef 'lwjgl'
library 'lwjgl-opengl', 'org.lwjgl', 'lwjgl-opengl' versionRef 'lwjgl'
library 'lwjgl-stb', 'org.lwjgl', 'lwjgl-stb' versionRef 'lwjgl'
library 'lwjgl-tinyfd', 'org.lwjgl', 'lwjgl-tinyfd' versionRef 'lwjgl'
bundle 'lwjgl', ['lwjgl', 'lwjgl-glfw', 'lwjgl-opengl', 'lwjgl-stb', 'lwjgl-tinyfd']
}
register('earlyDisplayLibs') {
version 'slf4j', '2.0.17'
library 'slf4j-api', 'org.slf4j', 'slf4j-api' versionRef 'slf4j'
library 'slf4j-jdk14', 'org.slf4j', 'slf4j-jdk14' versionRef 'slf4j'
}
register('earlyDisplayTestLibs') {
library 'powermock-core', 'org.powermock', 'powermock-core' version '2.0.9'
library 'lwjgl', 'org.lwjgl', 'lwjgl' withoutVersion()
library 'lwjgl-glfw', 'org.lwjgl', 'lwjgl-glfw' withoutVersion()
library 'lwjgl-opengl', 'org.lwjgl', 'lwjgl-opengl' withoutVersion()
library 'lwjgl-stb', 'org.lwjgl', 'lwjgl-stb' withoutVersion()
bundle 'lwjgl', ['lwjgl', 'lwjgl-glfw', 'lwjgl-opengl', 'lwjgl-stb']
}
//@formatter:on
}
@@ -0,0 +1,14 @@
{
"sources": [
{
"type": "minecraft:single",
"resource": "minecraft:block/lava_still",
"sprite": "minecraft:item/lava_still"
},
{
"type": "minecraft:single",
"resource": "minecraft:block/water_still",
"sprite": "minecraft:item/water_still"
}
]
}
@@ -0,0 +1,7 @@
{
"values": [
"#c:bars/copper",
"#c:bars/iron",
"#minecraft:bars"
]
}
@@ -0,0 +1,12 @@
{
"values": [
"minecraft:copper_bars",
"minecraft:waxed_copper_bars",
"minecraft:exposed_copper_bars",
"minecraft:waxed_exposed_copper_bars",
"minecraft:weathered_copper_bars",
"minecraft:waxed_weathered_copper_bars",
"minecraft:oxidized_copper_bars",
"minecraft:waxed_oxidized_copper_bars"
]
}
@@ -0,0 +1,5 @@
{
"values": [
"minecraft:iron_bars"
]
}
@@ -0,0 +1,7 @@
{
"values": [
"#c:bars/copper",
"#c:bars/iron",
"#minecraft:bars"
]
}
@@ -0,0 +1,12 @@
{
"values": [
"minecraft:copper_bars",
"minecraft:waxed_copper_bars",
"minecraft:exposed_copper_bars",
"minecraft:waxed_exposed_copper_bars",
"minecraft:weathered_copper_bars",
"minecraft:waxed_weathered_copper_bars",
"minecraft:oxidized_copper_bars",
"minecraft:waxed_oxidized_copper_bars"
]
}
@@ -0,0 +1,5 @@
{
"values": [
"minecraft:iron_bars"
]
}
@@ -0,0 +1,7 @@
{
"values": [
"minecraft:potion",
"minecraft:honey_bottle",
"minecraft:ominous_bottle"
]
}
@@ -0,0 +1,5 @@
{
"values": [
"minecraft:milk_bucket"
]
}
@@ -0,0 +1,3 @@
{
"values": []
}
@@ -109,7 +109,11 @@ public class ClientCommandHandler {
new CommandSource() {
@Override
public void sendSystemMessage(Component message) {
mc.gui.getChat().addClientSystemMessage(message);
var chat = mc.gui.getChat();
if (mc.isSameThread())
chat.addClientSystemMessage(message);
else
mc.execute(() -> chat.addClientSystemMessage(message));
}
@Override
@@ -0,0 +1,49 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import net.minecraft.client.data.AtlasProvider;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.data.AtlasIds;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.Identifier;
public class ForgeAtlasProvider extends AtlasProvider {
public ForgeAtlasProvider(PackOutput output) {
super(output);
}
@Override
public CompletableFuture<?> run(final CachedOutput cache) {
return CompletableFuture.allOf(
this.storeAtlas(cache, AtlasIds.BLOCKS, List.of(
single("white")
)),
this.storeAtlas(cache, AtlasIds.ITEMS, List.of(
fromBlock("block/lava_still"),
fromBlock("block/water_still")
))
);
}
private static SpriteSource single(String path) {
return new SingleFile(Identifier.fromNamespaceAndPath("forge", path));
}
private static SpriteSource fromBlock(String name) {
var resource = Identifier.withDefaultNamespace(name);
var id = resource;
if (resource.getPath().startsWith("block/"))
id = resource.withPath(path -> "item/" + path.substring(6));
return new SingleFile(resource, Optional.of(id));
}
}
@@ -7,10 +7,14 @@ package net.minecraftforge.client;
import com.mojang.blaze3d.framegraph.FrameGraphBuilder;
import com.mojang.blaze3d.framegraph.FramePass;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.renderer.LevelTargetBundle;
import net.minecraft.client.renderer.state.level.LevelRenderState;
import net.minecraft.resources.Identifier;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -27,25 +31,75 @@ public class FramePassManager {
// Note: Pass order is determined automatically within FrameGraphBuilder. It's unclear what must be done to guarantee ordering.
@ApiStatus.Internal
public static void insertForgePasses(FrameGraphBuilder graphBuilder, LevelTargetBundle bundle, LevelRenderState state) {
public static void insertForgePasses(FrameGraphBuilder graphBuilder, LevelTargetBundle bundle, LevelRenderState state, DeltaTracker deltaTracker) {
for (PassInfo info : addedPasses) {
FramePass pass = graphBuilder.addPass(info.name);
PassDefinition forgePass = info.pass;
forgePass.extracts(bundle, pass);
forgePass.extracts(bundle, pass, deltaTracker);
pass.executes(() -> forgePass.executes(state));
}
}
/// ### A PassDefinition must satisfy 3 things.
///
/// 1. The rendering order for the purpose of translucency sorting. Read further for specific details.
/// 2. A render state definition supplier; "what" will be rendered.
/// 3. A render state definition consumer; "how" it will be rendered.
///
///
/// To satisfy #1, all FramePasses (and thus PassDefinitions) must bind against at least one target.
/// The list of targets can be found in {@linkplain LevelTargetBundle}.
/// HOWEVER!!! Only {@linkplain LevelTargetBundle#main} is used if the graphics mode is not set to Fabulous!
/// So at minimum, all passes must guarantee a fallback binding to the main target, like the below.
/// ```
/// @Override
/// void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker deltaTracker) {
/// if (bundle.clouds != null) { // Perhaps we want to bind to clouds if Fabulous! is on.
/// bundle.clouds = pass.readsAndWrites(bundle.clouds);
/// }
/// // An else clause could be used, but is not mandatory. It is fine to bind to more than one target.
/// bundle.main = pass.readsAndWrites(bundle.main);
/// }
/// ```
/// </pre>
///
/// It is up to the user to decide how their pass should bind to targets, but it must always bind to at least one.
/// Failure to satisfy this requirement will result in {@linkplain FrameGraphBuilder#resolvePassOrder} exploding.
/// Custom render targets are possible but not documented, see the implementation of {@linkplain LevelTargetBundle}
/// if you want to take a crack at it.
///
/// Satisfying #2 is simple. Any state information you need to extract
/// should be done during {@linkplain PassDefinition#extracts(LevelTargetBundle, FramePass, DeltaTracker)}.
/// No actual rendering should be done at this time. This can happen before or after binding to a target.
/// The specific implementation of your render state is up to you, it can even be done with some instance variables.
///
/// Satisfying #3 is also simple, this is the actual rendering that will consume the state created
/// during the extracts phase.
@NullMarked
public interface PassDefinition {
/**
* Use to define which targets your pass will bind against, see {@link FramePass#reads} and {@link FramePass#readsAndWrites}
* A FramePass must bind to at least ONE target. Otherwise, you get freaky issues with >1 modded passes.
* Additionally, this method should be used for extracting render states into instance variables if desired.
*/
void extracts(LevelTargetBundle bundle, FramePass pass);
/**
* Use to define what your pass does during the render stage.
* @deprecated Prefer {@linkplain PassDefinition#extracts(LevelTargetBundle, FramePass, DeltaTracker)}
*/
@Deprecated(forRemoval = true, since="26.2")
default void extracts(LevelTargetBundle bundle, FramePass pass) {};
/**
* This method exists to do render state extraction. Your instance of a PassDefinition should have
* locals or some filled record instance that represents the render state created.
* The resulting render state should be consumed by {@linkplain PassDefinition#executes(LevelRenderState)}
* You must also use this to define which targets your pass will bind against. See PassDefinition javadocs for details.
*/
default void extracts(LevelTargetBundle bundle, FramePass pass, DeltaTracker deltaTracker) {
extracts(bundle, pass);
};
/**
* Use to define what your pass does during the render stage. This should consume the render state created
* during the extracts phase.
*/
void executes(LevelRenderState state);
}
@@ -13,7 +13,7 @@ import net.minecraftforge.eventbus.api.event.RecordEvent;
import org.jspecify.annotations.NullMarked;
/**
* Fired after all vanilla frame passes are added into the pass list.
* Fired during the construction of {@linkplain net.minecraft.client.renderer.LevelRenderer}.
*
* <p>This event is fired on the {@linkplain net.minecraftforge.common.MinecraftForge#EVENT_BUS main Forge event bus},
* only on the {@linkplain net.minecraftforge.fml.LogicalSide#CLIENT logical client}.
@@ -24,8 +24,9 @@ public record AddFramePassEvent() implements RecordEvent {
/**
* Adds a frame pass to pass list.
* Create a new {@linkplain FramePassManager.PassDefinition} to handle render targets and render code.
* See javadocs for details on what creating a PassDefinition entails.
*
* @param rl Resource location for frame pass name. Use RLs to avoid duplicate names.
* @param rl Identifier for frame pass name. Use RLs to avoid duplicate names.
* @param definition see usages of {@linkplain com.mojang.blaze3d.framegraph.FramePass} in {@linkplain net.minecraft.client.renderer.LevelRenderer}
* @throws IllegalArgumentException If the name is a duplicate.
*/
@@ -23,6 +23,7 @@ import net.minecraftforge.client.gui.widget.ModListWidget;
import net.minecraftforge.client.gui.widget.ScrollPanel;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.jspecify.annotations.Nullable;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ActiveTextCollector;
@@ -299,8 +300,6 @@ public class ModListScreen extends Screen {
@Override
public void tick() {
modList.setSelected(selected);
if (!search.getValue().equals(lastFilterText)) {
reloadMods();
sorted = false;
@@ -311,11 +310,11 @@ public class ModListScreen extends Screen {
mods.sort(sortType);
modList.refreshList();
if (selected != null) {
selected = modList.children().stream()
final var newSelected = modList.children().stream()
.filter(e -> e.getInfo() == selected.getInfo())
.findFirst()
.orElse(null);
updateCache();
this.modList.setSelected(newSelected);
}
sorted = true;
}
@@ -365,8 +364,14 @@ public class ModListScreen extends Screen {
}
public void setSelected(ModListWidget.ModEntry entry) {
this.selected = entry == this.selected ? null : entry;
updateCache();
if (this.selected != entry) {
this.selected = entry;
updateCache();
}
}
public ModListWidget.@Nullable ModEntry getSelected() {
return this.selected;
}
record Logo(Identifier texture, Size2i size) {}
@@ -473,7 +478,6 @@ public class ModListScreen extends Screen {
ModListWidget.ModEntry selected = this.selected;
this.init(width, height);
this.search.setValue(s);
this.selected = selected;
if (!this.search.getValue().isEmpty())
reloadMods();
@@ -481,7 +485,7 @@ public class ModListScreen extends Screen {
if (sort != SortType.NORMAL)
resortMods(sort);
updateCache();
this.modList.setSelected(selected);
}
@Override
@@ -10,6 +10,7 @@ import net.minecraft.client.DeltaTracker;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.contextualbar.ContextualBarRenderer;
import net.minecraft.resources.Identifier;
import net.minecraftforge.client.event.ForgeEventFactoryClient;
import net.minecraftforge.client.event.AddGuiOverlayLayersEvent;
@@ -38,14 +39,27 @@ public final class ForgeLayeredDraw implements ForgeLayer {
private final List<ForgeLayer> bakedLayers = new ArrayList<>();
private final Identifier name;
// Begin pre-sleep overlay
public static final Identifier PRE_SLEEP_STACK = Identifier.withDefaultNamespace("pre_sleep_phase");
public static final Identifier CAMERA_OVERLAY = Identifier.withDefaultNamespace("camera_overlay");
public static final Identifier CROSSHAIR = Identifier.withDefaultNamespace("crosshair");
public static final Identifier CHANGE_STRATUM = Identifier.withDefaultNamespace("stratum_change");
// Begin hotbar
public static final Identifier HOTBAR_AND_DECOS = Identifier.withDefaultNamespace("hotbar");
public static final Identifier ITEM_HOTBAR = Identifier.withDefaultNamespace("item_hotbar");
public static final Identifier SPECTATOR_HOTBAR = Identifier.withDefaultNamespace("spectator_hotbar");
public static final Identifier HEALTH_BAR = Identifier.withDefaultNamespace("health_bar");
public static final Identifier VEHICLE_HEALTH = Identifier.withDefaultNamespace("vehicle_health");
public static final Identifier BACKGROUND = Identifier.withDefaultNamespace("background"); // Any layer needing contextual info should order at some point after this layer.
public static final Identifier EXPERIENCE_LEVEL = Identifier.withDefaultNamespace("experience_level");
public static final Identifier CONTEXTUAL_INFO = Identifier.withDefaultNamespace("contextual_info");
public static final Identifier SELECTED_ITEM_NAME = Identifier.withDefaultNamespace("selected_item_name");
public static final Identifier SPECTATOR_ACTION = Identifier.withDefaultNamespace("spectator_action");
// End hotbar
public static final Identifier POTION_EFFECTS = Identifier.withDefaultNamespace("potion_effects");
public static final Identifier BOSS_OVERLAY = Identifier.withDefaultNamespace("boss_overlay");
// End pre-sleep overlay
// Begin post-sleep overlay
public static final Identifier POST_SLEEP_STACK = Identifier.withDefaultNamespace("post_sleep_phase");
public static final Identifier DEMO_OVERLAY = Identifier.withDefaultNamespace("demo");
public static final Identifier DEBUG_OVERLAY = Identifier.withDefaultNamespace("debug");
@@ -55,6 +69,7 @@ public final class ForgeLayeredDraw implements ForgeLayer {
public static final Identifier CHAT_OVERLAY = Identifier.withDefaultNamespace("chat_overlay");
public static final Identifier TAB_LIST = Identifier.withDefaultNamespace("tab_list");
public static final Identifier SUBTITLE_OVERLAY = Identifier.withDefaultNamespace("subtitle");
// End post-sleep overlay
public static final Identifier VANILLA_ROOT = Identifier.withDefaultNamespace("vanilla_root");
public static final Identifier SLEEP_OVERLAY = Identifier.withDefaultNamespace("sleep_overlay");
@@ -87,6 +102,17 @@ public final class ForgeLayeredDraw implements ForgeLayer {
return this;
}
/**
* Adds a full draw stack and assumes condition is always true.
* Use {@linkplain ForgeLayeredDraw#putAbove} and {@linkplain ForgeLayeredDraw#putBelow} for fine location adjustment.
* @param name RL of the name to identify this stack with.
* @param layeredDraw the draw stack
* @return this
*/
public ForgeLayeredDraw add(Identifier name, ForgeLayeredDraw layeredDraw) {
return add(name, layeredDraw, () -> true);
}
/**
* Add a layer to the layer list. This layer will be at the end of the list, which means
* it will be rendered last (on top) of already added layers.
@@ -279,6 +305,28 @@ public final class ForgeLayeredDraw implements ForgeLayer {
return this;
}
/**
* Replaces the renderer of a single layer and logs whodunnit, this is not recommended for obvious reasons.
* Will not work if target is a ForgeLayeredDraw
* Prefer {@linkplain ForgeLayeredDraw#addConditionTo}
* @param expectedLocation Layer stack where the target should be.
* @param targetLayer Target whose renderer should be replaced.
* @param replacementRenderer Renderer to use instead.
* @return this
*/
public ForgeLayeredDraw replace(Identifier expectedLocation, Identifier targetLayer, ForgeLayer replacementRenderer) {
locateStack(expectedLocation).ifPresentOrElse((stack) -> {
if (stack.namedLayers.get(targetLayer) != null) {
stack.namedLayers.put(targetLayer, replacementRenderer);
LogUtils.getLogger().debug("ForgeLayer {} in {} was replaced by {}.", targetLayer, expectedLocation, replacementRenderer);
} else {
LogUtils.getLogger().debug("ForgeLayer {} in {} was attempted to be replaced by {}, but it did not exist.", targetLayer, expectedLocation, replacementRenderer);
}
}, () -> stackNotPresentWarning(expectedLocation));
return this;
}
/**
* Propagate the layer order down to the inner render list after providing modders an opportunity to alter the list as they wish.
* @apiNote Modders should <emph>NEVER</emph> be calling this method.
@@ -381,11 +429,22 @@ public final class ForgeLayeredDraw implements ForgeLayer {
@ApiStatus.Internal
public static void init(Gui gui, Minecraft minecraft) {
BooleanSupplier spectator = () -> minecraft.gameMode.isSpectator();
var hotbarCluster = new ForgeLayeredDraw(HOTBAR_AND_DECOS)
.addWithCondition(SPECTATOR_HOTBAR, (gg, dt) -> gui.getSpectatorGui().extractHotbar(gg), spectator)
.addWithCondition(ITEM_HOTBAR, gui::extractItemHotbar, () -> !spectator.getAsBoolean())
.addWithCondition(HEALTH_BAR, (gg, dt) -> gui.extractPlayerHealth(gg), () -> minecraft.gameMode.canHurtPlayer())
.add(VEHICLE_HEALTH, (gg, dt) -> gui.extractVehicleHealth(gg))
.add(BACKGROUND, gui::updateContextualInfo)
.addWithCondition(EXPERIENCE_LEVEL, (gg, dt) -> ContextualBarRenderer.extractExperienceLevel(gg, minecraft.font, minecraft.player.experienceLevel), () -> minecraft.gameMode.hasExperience() && minecraft.player.experienceLevel > 0)
.add(CONTEXTUAL_INFO, gui::extractContextualInfoState)
.addWithCondition(SELECTED_ITEM_NAME, (gg, dt) -> gui.extractSelectedItemName(gg), () -> !spectator.getAsBoolean())
.addWithCondition(SPECTATOR_ACTION, (gg, dt) -> gui.getSpectatorGui().extractAction(gg), spectator);
var preSleepDraw = new ForgeLayeredDraw(PRE_SLEEP_STACK)
.add(CAMERA_OVERLAY, gui::extractCameraOverlays)
.add(CROSSHAIR, gui::extractCrosshair)
.add(CHANGE_STRATUM, (gg, dt) -> gg.nextStratum())
.add(HOTBAR_AND_DECOS, gui::extractHotbarAndDecorations)
.add(HOTBAR_AND_DECOS, hotbarCluster)
.add(POTION_EFFECTS, gui::extractEffects)
.add(BOSS_OVERLAY, gui::extractBossOverlay);
var postSleepDraw = new ForgeLayeredDraw(POST_SLEEP_STACK)
@@ -5,6 +5,8 @@
package net.minecraftforge.client.gui.widget;
import org.jspecify.annotations.Nullable;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.ObjectSelectionList;
@@ -60,6 +62,12 @@ public class ModListWidget extends ObjectSelectionList<ModListWidget.ModEntry> {
parent.buildModList(this::addEntry, mod->new ModEntry(mod, this.parent));
}
@Override
public void setSelected(final @Nullable ModEntry selected) {
parent.setSelected(selected);
super.setSelected(selected);
}
public class ModEntry extends ObjectSelectionList.Entry<ModEntry> {
private final IModInfo modInfo;
private final ModListScreen parent;
@@ -92,14 +100,15 @@ public class ModListWidget extends ObjectSelectionList<ModListWidget.ModEntry> {
guiGraphics.pose().pushMatrix();
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, VERSION_CHECK_ICONS, getX() + width - 12 - barOffset, top + entryHeight / 4, vercheck.status().getSheetOffset() * 8, (vercheck.status().isAnimated() && ((System.currentTimeMillis() / 800 & 1)) == 1) ? 8 : 0, 8, 8, 64, 16);
guiGraphics.pose().popMatrix();
}
}
@Override
public boolean mouseClicked(MouseButtonEvent info, boolean recent) {
parent.setSelected(this);
ModListWidget.this.setSelected(this);
if (this.parent.getSelected() == this)
ModListWidget.this.setSelected(null);
else
ModListWidget.this.setSelected(this);
return false;
}
@@ -8,7 +8,6 @@ package net.minecraftforge.client.model;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.mojang.math.Transformation;
import net.minecraft.client.renderer.block.dispatch.ModelState;
import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
import net.minecraft.client.resources.model.ModelBaker;
@@ -31,6 +30,7 @@ import net.minecraftforge.client.model.geometry.IGeometryLoader;
import net.minecraftforge.client.model.geometry.StandaloneGeometryBakingContext;
import net.minecraftforge.client.model.geometry.UnbakedGeometryHelper;
import net.minecraftforge.registries.ForgeRegistries;
import org.joml.Quaternionf;
import org.joml.Vector3f;
@@ -81,17 +81,26 @@ public class DynamicFluidContainerModel implements UnbakedGeometry {
@Override
public QuadCollection bake(TextureSlots textures, ModelBaker baker, ModelState state, ModelDebugName name, IGeometryBakingContext context) {
Material fluidMaskLocation = textures.getMaterial("fluid");
Material coverLocation = textures.getMaterial("cover");
Material stillMaterial = null;
if (fluid != Fluids.EMPTY) {
var stillTexture = IClientFluidTypeExtensions.of(fluid).getStillTexture();
stillMaterial = new Material(stillTexture);
if (stillTexture != null) {
// Models can no longer have textures across atlases, so redirect to the item model of the same name.
// Modders may need to make clones of their textures in the item atlas, or provide a custom atlas alias
if (stillTexture.getPath().startsWith("block/"))
stillTexture = stillTexture.withPath(path -> "item/" + path.substring(6));
stillMaterial = new Material(stillTexture);
}
}
var materials = baker.materials();
var baseMaterial = materials.resolveSlot(textures, "base", name);
var fluidMaterial = stillMaterial == null ? null : materials.get(stillMaterial, name);
var coverMaterial = materials.resolveSlot(textures, "cover", name);
var coverMaterial = coverLocation == null ? null : materials.get(coverLocation, name);
/*
var particleSprite = sprites.resolveSlot(textures, "particle", name);
@@ -104,8 +113,10 @@ public class DynamicFluidContainerModel implements UnbakedGeometry {
// TODO: [Forge][Rendering] See if we can get rid of SimpleModelState and wrap transforms completely
// If the fluid is lighter than air, rotate 180deg to turn it upside down
if (flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir())
if (flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir()) {
transformation = transformation.compose(new Transformation(null, new Quaternionf(0, 0, 1, 0), null, null));
state = new SimpleModelState(transformation);
}
var buf = new QuadCollection.Builder();
@@ -117,7 +128,7 @@ public class DynamicFluidContainerModel implements UnbakedGeometry {
var templateMaterial = materials.get(fluidMaskLocation, name);
if (templateMaterial != null) {
var transformedState = new SimpleModelState(transformation.compose(FLUID_TRANSFORM));
UnbakedGeometryHelper.bakeMaskedSprite(buf, baker.interner(), transformedState, info(baker, templateMaterial, 1), info(baker, fluidMaterial, 1));
UnbakedGeometryHelper.bakeMaskedSprite(buf, baker.interner(), transformedState, info(baker, fluidMaterial, 1), info(baker, templateMaterial, 1));
}
}
@@ -11,6 +11,7 @@ import net.minecraft.client.renderer.block.dispatch.ModelState;
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.cuboid.CuboidFace;
import net.minecraft.client.resources.model.cuboid.FaceBakery;
import net.minecraft.client.resources.model.cuboid.ItemModelGenerator;
import net.minecraft.client.resources.model.geometry.BakedQuad;
@@ -91,15 +92,16 @@ public class UnbakedGeometryHelper {
final BakedQuad.MaterialInfo texture,
final BakedQuad.MaterialInfo template
) {
var spriteContents = template.sprite().contents();
int width = spriteContents.width(), height = spriteContents.height();
var sprite = template.sprite().contents();
int width = sprite.width();
int height = sprite.height();
var bits = new BitSet(width * height);
// For every frame in the texture, mark all the opaque pixels (this is what vanilla does too)
spriteContents.getUniqueFrames().forEach(frame -> {
sprite.getUniqueFrames().forEach(frame -> {
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
if (!spriteContents.isTransparent(frame, x, y))
if (!sprite.isTransparent(frame, x, y))
bits.set(x + y * width);
});
@@ -132,8 +134,10 @@ public class UnbakedGeometryHelper {
var to = new Vector3f(16 * x / (float) width, 16 - 16 * y / (float) height, 8.5F);
// Create element
builder.addUnculledFace(FaceBakery.bakeQuad(interner, from, to, ItemModelGenerator.SOUTH_FACE_UVS, Quadrant.R0, texture, Direction.SOUTH, state, null));
builder.addUnculledFace(FaceBakery.bakeQuad(interner, from, to, ItemModelGenerator.NORTH_FACE_UVS, Quadrant.R0, texture, Direction.NORTH, state, null));
var southUvs = new CuboidFace.UVs(from.x, from.y, to.x, to.y);
var northUvs = new CuboidFace.UVs(to.x, from.y, from.x, to.y);
builder.addUnculledFace(FaceBakery.bakeQuad(interner, from, to, southUvs, Quadrant.R0, texture, Direction.SOUTH, state, null));
builder.addUnculledFace(FaceBakery.bakeQuad(interner, from, to, northUvs, Quadrant.R0, texture, Direction.NORTH, state, null));
// Reset xStart
xStart = -1;
@@ -75,6 +75,7 @@ public class ForgeConfig {
}
public final ForgeConfigSpec.EnumValue<MigrationHelperMode> migrationHelperMode;
public final ForgeConfigSpec.LongValue channelListLimit;
Common(ForgeConfigSpec.Builder builder) {
builder.comment("General configuration settings")
@@ -85,6 +86,17 @@ public class ForgeConfig {
.translation("forge.configgui.migrationHelperMode")
.defineEnum("logLegacyTagWarnings", MigrationHelperMode.OFF);
channelListLimit = builder
.comment(
"Maximum size to use to store the channel list for a network connection.",
"The other end of the connection may send as many channels as it wishes.",
"Some malicious actors may use this to use up all your memory causing OutOfMemoryException",
"A sane default is 1KB (1024), but may need to be expanded for larger modpacks.",
"Forge will auto-adjust this if mods register their channels using our channel manager."
)
.translation("forge.configgui.channellistlimit")
.defineInRange("channelListLimit", 1024, 1, Long.MAX_VALUE);
builder.pop();
}
}
@@ -15,6 +15,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
@@ -52,6 +53,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.Connection;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.PacketListener;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.VarInt;
import net.minecraft.network.chat.ClickEvent;
@@ -104,6 +106,8 @@ import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.PacketType;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.network.syncher.EntityDataSerializer;
@@ -1314,4 +1318,32 @@ public final class ForgeHooks {
return Optional.of(ret);
}
// This is similar to PacketUtils.ensureRunningOnSameThread, we can't use the normal LogicalSidedProvider.WORKQUEUE because it is processed after packets.
// So any vanilla packets that are received after this packet, will be processed before our enqueued packet.
public static <MSG> void enqueuePacket(BiConsumer<MSG, CustomPayloadEvent.Context> handler, MSG packet, CustomPayloadEvent.Context context) {
var processor = LogicalSidedProvider.PACKETS.get(context.isClientSide());
if (!processor.isSameThread())
processor.scheduleIfPossible(context.getConnection().getPacketListener(), new DummyPacket<>(handler, packet, context));
else
handler.accept(packet, context);
}
private static final record DummyPacket<MSG>(
BiConsumer<MSG, CustomPayloadEvent.Context> handler,
MSG packet,
CustomPayloadEvent.Context context
) implements Packet<PacketListener> {
private static final PacketType<DummyPacket<?>> TYPE = new PacketType<>(PacketFlow.CLIENTBOUND, Identifier.fromNamespaceAndPath("forge", "dummy_for_schedualing"));
@Override
public PacketType<DummyPacket<?>> type() {
return TYPE;
}
@Override
public void handle(PacketListener listener) {
handler.accept(packet, context);
}
}
}
@@ -37,6 +37,8 @@ import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.pathfinder.PathType;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.ForgeAtlasProvider;
import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.common.data.ForgeBiomeTagsProvider;
@@ -69,6 +71,7 @@ import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.event.lifecycle.*;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.registries.*;
import net.minecraftforge.registries.holdersets.AndHolderSet;
import net.minecraftforge.registries.holdersets.AnyHolderSet;
@@ -444,8 +447,11 @@ public class ForgeMod {
gen.addProvider(event.includeServer(), new ForgeBiomeTagsProvider(packOutput, lookupProvider, existingFileHelper));
gen.addProvider(event.includeServer(), new ForgeStructureTagsProvider(packOutput, lookupProvider, existingFileHelper));
gen.addProvider(event.includeClient(), new ForgeSpriteSourceProvider(packOutput, existingFileHelper));
gen.addProvider(event.includeClient(), new VanillaSoundDefinitionsProvider(packOutput, existingFileHelper));
// The provider uses Client only classes, so put a side guard on it.
if (FMLLoader.getDist() == Dist.CLIENT) {
gen.addProvider(event.includeClient(), new ForgeAtlasProvider(packOutput));
}
}
// done in an event instead of deferred to only enable if a mod requests it
@@ -12,11 +12,13 @@ import net.minecraft.tags.BlockTags;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemUseAnimation;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.Biome;
@@ -60,6 +62,12 @@ public class Tags {
// Forge only adopts `c` tags that are common across all loaders.
public static final TagKey<Block> BARRELS = cTag("barrels");
public static final TagKey<Block> BARRELS_WOODEN = cTag("barrels/wooden");
/**
* Equivalent to the "minecraft:bars" block tag.
*/
public static final TagKey<Block> BARS = cTag("bars");
public static final TagKey<Block> BARS_COPPER = cTag("bars/copper");
public static final TagKey<Block> BARS_IRON = cTag("bars/iron");
public static final TagKey<Block> BOOKSHELVES = cTag("bookshelves");
/**
* For blocks that are similar to amethyst where their budding block produces buds and cluster blocks
@@ -347,6 +355,12 @@ public class Tags {
// Forge only adopts `c` tags that are common across all loaders.
public static final TagKey<Item> BARRELS = cTag("barrels");
public static final TagKey<Item> BARRELS_WOODEN = cTag("barrels/wooden");
/**
* Equivalent to the "minecraft:bars" item tag.
*/
public static final TagKey<Item> BARS = cTag("bars");
public static final TagKey<Item> BARS_COPPER = cTag("bars/copper");
public static final TagKey<Item> BARS_IRON = cTag("bars/iron");
public static final TagKey<Item> BONES = cTag("bones");
public static final TagKey<Item> BOOKSHELVES = cTag("bookshelves");
public static final TagKey<Item> BRICKS = cTag("bricks");
@@ -415,15 +429,49 @@ public class Tags {
public static final TagKey<Item> DUSTS_REDSTONE = cTag("dusts/redstone");
public static final TagKey<Item> DUSTS_GLOWSTONE = cTag("dusts/glowstone");
/**
* Drinks are defined as (1) consumable items that (2) use the
* {@linkplain ItemUseAnimation#DRINK drink item use animation}, (3) can be consumed regardless of the
* player's current hunger.
*
* <p>Drinks may provide nutrition and saturation, but are not required to do so.
*
* <p>More specific types of drinks, such as Water, Milk, or Juice should be placed in a sub-tag, such as
* {@code #c:drinks/water}, {@code #c:drinks/milk}, and {@code #c:drinks/juice}.
*/
public static final TagKey<Item> DRINKS = cTag("drinks");
public static final TagKey<Item> DRINKS_HONEY = cTag("drinks/honey");
/**
* Plant based fruit and vegetable juices belong in this tag, for example apple juice and carrot juice.
*
* <p>If tags for specific types of juices are desired, they may go in a sub-tag, using their regular name such as
* {@code #c:drinks/apple_juice}.
*/
public static final TagKey<Item> DRINKS_JUICE = cTag("drinks/juice");
public static final TagKey<Item> DRINKS_MAGIC = cTag("drinks/magic");
public static final TagKey<Item> DRINKS_MILK = cTag("drinks/milk");
/**
* For drinks that always grant the {@linkplain MobEffects#BAD_OMEN Bad Omen} effect.
*/
public static final TagKey<Item> DRINKS_OMINOUS = cTag("drinks/ominous");
/**
* For consumable drinks that contain only water.
*/
public static final TagKey<Item> DRINKS_WATER = cTag("drinks/water");
/**
* For consumable drinks that are generally watery (such as potions).
*/
public static final TagKey<Item> DRINKS_WATERY = cTag("drinks/watery");
/**
* For non-empty bottles that are {@linkplain #DRINKS drinkable}.
*/
public static final TagKey<Item> DRINK_CONTAINING_BOTTLE = cTag("drink_containing/bottle");
/**
* For non-empty buckets that are {@linkplain #DRINKS drinkable}.
*/
public static final TagKey<Item> DRINK_CONTAINING_BUCKET = cTag("drink_containing/bucket");
/**
* Tag that holds all blocks and items that can be dyed a specific color.
* (Does not include color blending items like leather armor
@@ -512,6 +560,20 @@ public class Tags {
public static final TagKey<Item> FOODS_BERRY = cTag("foods/berry");
public static final TagKey<Item> FOODS_BREAD = cTag("foods/bread");
public static final TagKey<Item> FOODS_COOKIE = cTag("foods/cookie");
/**
* For all doughs regardless of type, specific types of dough should fall under their respective sub-tag.<br/>
* For example:<br/>
* - Wheat dough (which generally results in bread) would go in "#c:foods/dough/wheat"<br/>
* - Rye dough (which has rye as it's main ingredient) would go in "#c:foods/dough/rye"<br/>
* - Sub-tags should also be added to this tag, for example: "#c:foods/dough/wheat" should be added to "#c:foods/dough"<br/>
* <br/>
* There are some important assumptions that should be kept in mind.<br/>
* - It is assumed that "1 dough = result", which in the case of wheat dough would be "1 dough = 1 bread"<br/>
* - It is assumed that this dough can be baked into another item<br/>
* - It is *not* assumed that all doughs result in bread, there can be doughs in this tag that result in things like pizza, etc.
* This means that this tag should *not* be used for furnace recipes, mods should add their own dough to result recipes for their respective items.
*/
public static final TagKey<Item> FOODS_DOUGH = cTag("foods/dough");
public static final TagKey<Item> FOODS_RAW_MEAT = cTag("foods/raw_meat");
public static final TagKey<Item> FOODS_COOKED_MEAT = cTag("foods/cooked_meat");
public static final TagKey<Item> FOODS_RAW_FISH = cTag("foods/raw_fish");
@@ -28,6 +28,12 @@ public abstract class ForgeBlockItemTagsProvider extends BlockItemTagsProvider {
.addTag(Tags.Blocks.BARRELS_WOODEN);
tag(Tags.Blocks.BARRELS_WOODEN, Tags.Items.BARRELS_WOODEN)
.add(Blocks.BARREL);
tag(Tags.Blocks.BARS_COPPER, Tags.Items.BARS_COPPER)
.addAll(Blocks.COPPER_BARS.asList());
tag(Tags.Blocks.BARS_IRON, Tags.Items.BARS_IRON)
.add(Blocks.IRON_BARS);
tag(Tags.Blocks.BARS, Tags.Items.BARS)
.addTags(Tags.Blocks.BARS_COPPER, Tags.Blocks.BARS_IRON, BlockTags.BARS);
tag(Tags.Blocks.BOOKSHELVES, Tags.Items.BOOKSHELVES)
.add(Blocks.BOOKSHELF);
tag(Tags.Blocks.BUDDING_BLOCKS, Tags.Items.BUDDING_BLOCKS)
@@ -70,7 +70,7 @@ public final class ForgeBlockTagsProvider extends VanillaBlockTagsProvider {
private void addColored(TagKey<Block> group, String pattern) {
String prefix = group.location().getPath().toUpperCase(Locale.ENGLISH) + '_';
for (var color : DyeColor.values()) {
var key = Identifier.fromNamespaceAndPath("minecraft", pattern.replace("{color}", color.getName()));
var key = Identifier.withDefaultNamespace(pattern.replace("{color}", color.getName()));
TagKey<Block> tag = getTag(prefix + color.getName());
var block = ForgeRegistries.BLOCKS.getValue(key);
if (block == null || block == Blocks.AIR)
@@ -84,6 +84,10 @@ public final class ForgeItemTagsProvider extends VanillaItemTagsProvider {
tag(Tags.Items.CROPS_SUGAR_CANE).add(Items.SUGAR_CANE);
tag(Tags.Items.CROPS_WHEAT)
.add(Items.WHEAT);
tag(Tags.Items.DRINK_CONTAINING_BOTTLE)
.add(Items.POTION, Items.HONEY_BOTTLE, Items.OMINOUS_BOTTLE);
tag(Tags.Items.DRINK_CONTAINING_BUCKET)
.add(Items.MILK_BUCKET);
tag(Tags.Items.DRINKS)
.addTags(Tags.Items.DRINKS_HONEY, Tags.Items.DRINKS_JUICE, Tags.Items.DRINKS_MAGIC, Tags.Items.DRINKS_MILK,
Tags.Items.DRINKS_OMINOUS, Tags.Items.DRINKS_WATER, Tags.Items.DRINKS_WATERY);
@@ -131,6 +135,7 @@ public final class ForgeItemTagsProvider extends VanillaItemTagsProvider {
tag(Tags.Items.FOODS_BERRY).add(Items.SWEET_BERRIES, Items.GLOW_BERRIES);
tag(Tags.Items.FOODS_BREAD).add(Items.BREAD);
tag(Tags.Items.FOODS_COOKIE).add(Items.COOKIE);
tag(Tags.Items.FOODS_DOUGH);
tag(Tags.Items.FOODS_RAW_MEAT).add(Items.BEEF, Items.PORKCHOP, Items.CHICKEN, Items.RABBIT, Items.MUTTON);
tag(Tags.Items.FOODS_RAW_FISH).add(Items.COD, Items.SALMON, Items.TROPICAL_FISH, Items.PUFFERFISH);
tag(Tags.Items.FOODS_COOKED_MEAT).add(Items.COOKED_BEEF, Items.COOKED_PORKCHOP, Items.COOKED_CHICKEN, Items.COOKED_RABBIT, Items.COOKED_MUTTON);
@@ -11,6 +11,10 @@ import net.minecraft.resources.Identifier;
import java.util.Optional;
/**
* @deprecated Use Vanilla's {@link net.minecraft.client.data.AtlasProvider AtlasProvider}
*/
@Deprecated(forRemoval = true, since = "26.1.2")
public class ForgeSpriteSourceProvider extends SpriteSourceProvider
{
public ForgeSpriteSourceProvider(PackOutput output, ExistingFileHelper fileHelper)
@@ -24,7 +24,10 @@ import java.util.function.BiConsumer;
* allows adding additional textures to the atlas by adding {@link SpriteSource}s to the configuration.</p>
* <p>See {@link SpriteSources} for the available sources and the constants in this class for the
* atlases used in vanilla Minecraft</p>
*
* @deprecated Use Vanilla's {@link net.minecraft.client.data.AtlasProvider AtlasProvider}
*/
@Deprecated(forRemoval = true, since = "26.1.2")
public abstract class SpriteSourceProvider extends JsonCodecProvider<List<SpriteSource>>
{
protected static final Identifier BLOCKS_ATLAS = Identifier.withDefaultNamespace("blocks");
@@ -6,6 +6,7 @@
package net.minecraftforge.common.util;
import net.minecraft.client.Minecraft;
import net.minecraft.network.PacketProcessor;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.TickTask;
import net.minecraft.util.thread.BlockableEventLoop;
@@ -21,6 +22,7 @@ import org.jetbrains.annotations.ApiStatus;
public class LogicalSidedProvider<T> {
public static final LogicalSidedProvider<BlockableEventLoop<? super TickTask>> WORKQUEUE = new LogicalSidedProvider<>(Supplier::get, Supplier::get);
public static final LogicalSidedProvider<Optional<Level>> CLIENTWORLD = new LogicalSidedProvider<>((c)-> Optional.of(c.get().level), (s)->Optional.empty());
public static final LogicalSidedProvider<PacketProcessor> PACKETS = new LogicalSidedProvider<>(client -> client.get().packetProcessor(), server -> server.get().packetProcessor());
public T get(LogicalSide side) {
return side == LogicalSide.CLIENT ? clientSide.apply(client) : serverSide.apply(server);
@@ -21,6 +21,7 @@ import net.minecraft.core.component.DataComponentMap;
import net.minecraft.util.random.WeightedList;
import net.minecraft.world.entity.*;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraftforge.common.util.Result;
import org.jetbrains.annotations.ApiStatus;
@@ -354,8 +355,8 @@ public final class ForgeEventFactory {
return BlockEvent.FluidPlaceBlockEvent.BUS.fire(new BlockEvent.FluidPlaceBlockEvent(level, pos, liquidPos, state)).getNewState();
}
public static ItemTooltipEvent onItemTooltip(ItemStack itemStack, @Nullable Player entityPlayer, List<Component> list, TooltipFlag flags) {
return ItemTooltipEvent.BUS.fire(new ItemTooltipEvent(itemStack, entityPlayer, list, flags));
public static ItemTooltipEvent onItemTooltip(ItemStack itemStack, @Nullable Player entityPlayer, List<Component> list, TooltipFlag flags, Item.TooltipContext context, TooltipDisplay display) {
return ItemTooltipEvent.BUS.fire(new ItemTooltipEvent(itemStack, entityPlayer, list, flags, context, display));
}
public static SummonAidEvent fireZombieSummonAid(Zombie zombie, Level level, int x, int y, int z, LivingEntity attacker, double summonChance) {
@@ -8,12 +8,15 @@ package net.minecraftforge.event.entity.player;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.MutableEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,17 +28,38 @@ public final class ItemTooltipEvent extends MutableEvent implements PlayerEvent
@NotNull
private final ItemStack itemStack;
private final List<Component> toolTip;
private final Item.TooltipContext context;
private final TooltipDisplay display;
/**
* This event is fired in {@link ItemStack#getTooltipLines(Player, TooltipFlag)}, which in turn is called from its respective GUIContainer.
* This event is fired in {@link ItemStack#getTooltipLines(Item.TooltipContext, Player, TooltipFlag)}, which in turn is called from its respective GUIContainer.
* Tooltips are also gathered with a null player during startup by {@link Minecraft#createSearchTrees()}.
*/
public ItemTooltipEvent(@NotNull ItemStack itemStack, @Nullable Player player, List<Component> list, TooltipFlag flags)
@ApiStatus.Internal
public ItemTooltipEvent(@NotNull ItemStack itemStack, @Nullable Player player, List<Component> list, TooltipFlag flags, Item.TooltipContext context, TooltipDisplay display)
{
this.player = player;
this.itemStack = itemStack;
this.toolTip = list;
this.flags = flags;
this.context = context;
this.display = display;
}
/**
* The {@link net.minecraft.world.item.Item.TooltipContext} for this tooltip.
*/
public Item.TooltipContext getContext()
{
return context;
}
/**
* The {@link net.minecraft.world.item.component.TooltipDisplay} for this tooltip.
*/
public TooltipDisplay getDisplay()
{
return display;
}
/**
@@ -9,9 +9,11 @@ import com.mojang.logging.LogUtils;
import net.minecraft.IdentifierException;
import net.minecraft.network.Connection;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
import net.minecraftforge.common.ForgeConfig;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.network.ChannelRegistrationChangeEvent;
import net.minecraftforge.event.network.CustomPayloadEvent;
@@ -26,6 +28,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@ApiStatus.Internal // TODO: Decide what I want to have public, Right now it should just be add/remove channels.
public class ChannelListManager {
@@ -70,7 +73,7 @@ public class ChannelListManager {
}
private static void register(Register payload, CustomPayloadEvent.Context ctx) {
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.REGISTER);
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.REGISTER, ctx.isClientSide());
ctx.setPacketHandled(true);
// Send the client's channels to the server whenever asked. If we're in the login state then our login wrapper will unwrap for us.
if (ctx.isClientSide())
@@ -78,7 +81,7 @@ public class ChannelListManager {
}
private static void unregister(Unregister payload, CustomPayloadEvent.Context ctx) {
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.UNREGISTER);
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.UNREGISTER, ctx.isClientSide());
ctx.setPacketHandled(true);
}
@@ -145,7 +148,7 @@ public class ChannelListManager {
return Collections.unmodifiableList(channels);
}
private static void updateFrom(CustomPayloadEvent.Context source, List<String> channels, final ChannelRegistrationChangeEvent.Type changeType) {
private static void updateFrom(CustomPayloadEvent.Context source, List<String> channels, final ChannelRegistrationChangeEvent.Type changeType, boolean isClient) {
var changed = new HashSet<Identifier>();
for (var channel : channels) {
// It also says nothing about the format of channels so ignore bad channels.
@@ -162,11 +165,20 @@ public class ChannelListManager {
ForgeEventFactory.onChannelRegistrationChange(source.getConnection(), changeType, changed);
// It is a known issue, that I don't really care about, that we don't get the true in-memory size of each channel due to character encoding.
// A sane limit of 1KB will be enough to catch any malicious actors even with non-8-bit strings.
var list = NetworkContext.get(source.getConnection());
for (var channel : changed) {
boolean fire = changeType == ChannelRegistrationChangeEvent.Type.UNREGISTER
? list.remoteChannels.remove(channel)
: list.remoteChannels.add(channel);
boolean fire = false;
if (changeType == ChannelRegistrationChangeEvent.Type.UNREGISTER) {
fire = list.remoteChannels.remove(channel);
if (fire)
list.remoteChannelsSize -= channel.toString().length();
} else {
fire = list.remoteChannels.add(channel);
if (fire)
list.remoteChannelsSize += channel.toString().length();
}
if (fire) {
var target = NetworkRegistry.findTarget(channel);
@@ -174,5 +186,21 @@ public class ChannelListManager {
target.registrationChange(channel, changeType == ChannelRegistrationChangeEvent.Type.REGISTER);
}
}
if (list.remoteChannelsSize >= NetworkRegistry.getChannelMemorySize() && list.remoteChannelsSize >= ForgeConfig.COMMON.channelListLimit.get()) {
LOGGER.warn("Received too many network channels, {} channels using {} bytes, Limit is currently {}, to change edit the Forge {} in config\forge_common.toml",
list.remoteChannels.size(), list.remoteChannelsSize, ForgeConfig.COMMON.channelListLimit.get(), ForgeConfig.COMMON.channelListLimit.getPath().stream().collect(Collectors.joining("."))
);
if (isClient) {
// We don't want to allow the server to screw with us, so just remove the added channels
for (var channel : changed) {
if (list.remoteChannels.remove(channel))
list.remoteChannelsSize -= channel.toString().length();
}
} else {
source.getSender().connection.disconnect(Component.translatable("forge.server.channellistlimit.kick"));
}
}
}
}
@@ -111,6 +111,8 @@ public class NetworkContext {
@ApiStatus.Internal
Set<Identifier> remoteChannels = new HashSet<>();
@ApiStatus.Internal
long remoteChannelsSize = 0;
private Set<Identifier> remoteChannelsView = Collections.unmodifiableSet(this.remoteChannels);
@ApiStatus.Internal
@@ -43,6 +43,7 @@ public class NetworkRegistry {
private static Map<Identifier, NetworkInstance> instances = Collections.synchronizedMap(new HashMap<>());
private static Map<Identifier, NetworkInstance> byName = Collections.synchronizedMap(new HashMap<>());
private static long memorySize = 0;
public static boolean acceptsVanillaClientConnections() {
return listRejectedVanillaMods(n -> n.clientAcceptedVersions).isEmpty() && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty();
@@ -199,9 +200,14 @@ public class NetworkRegistry {
if (NetworkRegistry.byName.containsKey(name))
error("Payload name " + name + " already registered.");
memorySize += name.toString().length(); // This can fail if people don't use our api. So this is a best effort.
byName.put(name, instance);
}
static long getChannelMemorySize() {
return memorySize;
}
static void register(NetworkInstance instance) {
checkLock(instance);
@@ -28,7 +28,7 @@ public interface SimpleFlow<BUF extends FriendlyByteBuf, BASE> extends SimplePro
*/
default <MSG extends BASE> SimpleFlow<BUF, BASE> addMain(Class<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, CustomPayloadEvent.Context> handler) {
return add(type, codec, (msg, ctx) -> {
ctx.enqueueWork(() -> handler.accept(msg, ctx));
net.minecraftforge.common.ForgeHooks.enqueuePacket(handler, msg, ctx);
ctx.setPacketHandled(true);
});
}
@@ -350,7 +350,7 @@ public class GameData {
if (forgeRegistry != null)
forgeRegistry.freeze();
LOGGER.debug(REGISTRIES, "Applying holder lookups: {}", registryKey.identifier());
ObjectHolderRegistry.applyObjectHolders(registryKey.identifier()::equals);
ObjectHolderRegistry.applyObjectHolders(registryKey.identifier());
LOGGER.debug(REGISTRIES, "Holder lookups applied: {}", registryKey.identifier());
} catch (Throwable t) {
aggregate.addSuppressed(t);
@@ -5,10 +5,12 @@
package net.minecraftforge.registries;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.minecraft.resources.Identifier;
import org.apache.logging.log4j.LogManager;
@@ -16,32 +18,34 @@ import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.ApiStatus;
/**
* Internal registry for tracking {@link ObjectHolder} references
* Internal registry for tracking {@link RegistryObject} references
*/
@ApiStatus.Internal
class ObjectHolderRegistry {
/**
* Exposed to allow modders to register their own notification handlers.
* This runnable will be called after a registry snapshot has been injected and finalized.
* The internal list is backed by a HashSet so it is HIGHLY recommended you implement a proper equals
* and hashCode function to de-duplicate callers here.
* The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on.
* Registers a handler that is run after the named registry has been injected and finalized.
* Handlers are kept in a HashSet per registry, so the same instance is only ever registered once.
*/
static synchronized void addHandler(Consumer<Predicate<Identifier>> ref) {
objectHolders.add(ref);
static synchronized void addHandler(Identifier registry, Runnable ref) {
namedHandlers.computeIfAbsent(registry, _ -> new HashSet<>()).add(ref);
}
/**
* Removed the specified handler from the notification list.
*
* The internal list is backed by a hash set, and so proper hashCode and equals operations are required for success.
*
* The default @ObjectHolder implementation uses the hashCode/equals for the field the annotation is on.
* Removes a handler registered with {@link #addHandler(Identifier, Runnable)}.
*
* @return true if handler was matched and removed.
*/
static synchronized boolean removeHandler(Consumer<Predicate<Identifier>> ref) {
return objectHolders.remove(ref);
static synchronized boolean removeHandler(Identifier registry, Runnable ref) {
var handlers = namedHandlers.get(registry);
return handlers != null && handlers.remove(ref);
}
/**
* Registers a check that is run once, before the handlers of the next holder pass.
* Used to report holders that point at a registry that does not exist.
*/
static synchronized void addValidator(Runnable ref) {
validators.add(ref);
}
//==============================================================
@@ -49,12 +53,19 @@ class ObjectHolderRegistry {
//==============================================================
private static final Logger LOGGER = LogManager.getLogger();
private static final Set<Consumer<Predicate<Identifier>>> objectHolders = new HashSet<>();
// Order is not guaranteed, the old implementation kept every handler in one HashSet.
private static final Map<Identifier, Set<Runnable>> namedHandlers = new HashMap<>();
private static final List<Runnable> validators = new ArrayList<>();
/** Runs the handlers of every registry. */
static void applyObjectHolders() {
try {
LOGGER.debug(ForgeRegistry.REGISTRIES, "Applying holder lookups");
applyObjectHolders(key -> true);
var exceptions = new ArrayList<Exception>();
validate(exceptions);
for (var handlers : namedHandlers.values())
apply(handlers, exceptions);
throwIfNeeded(exceptions);
LOGGER.debug(ForgeRegistry.REGISTRIES, "Holder lookups applied");
} catch (RuntimeException e) {
// It is more important that the calling contexts continue without exception to prevent further cascading errors
@@ -62,17 +73,52 @@ class ObjectHolderRegistry {
}
}
static void applyObjectHolders(Predicate<Identifier> filter) {
RuntimeException aggregate = new RuntimeException("Failed to apply some object holders, see suppressed exceptions for details");
for (Consumer<Predicate<Identifier>> objectHolder : objectHolders) {
try {
objectHolder.accept(filter);
} catch (Exception e) {
aggregate.addSuppressed(e);
}
/** Runs the handlers of one registry. */
static void applyObjectHolders(Identifier registry) {
var exceptions = new ArrayList<Exception>();
validate(exceptions);
var handlers = namedHandlers.get(registry);
if (handlers != null)
apply(handlers, exceptions);
throwIfNeeded(exceptions);
}
// Each validator runs once, at the first holder pass after it was registered.
private static void validate(List<Exception> exceptions) {
List<Runnable> pending;
synchronized (ObjectHolderRegistry.class) {
if (validators.isEmpty())
return;
pending = new ArrayList<>(validators);
validators.clear();
}
if (aggregate.getSuppressed().length > 0)
throw aggregate;
for (var validator : pending) {
try {
validator.run();
} catch (Exception e) {
exceptions.add(e);
}
}
}
private static void apply(Set<Runnable> handlers, List<Exception> exceptions) {
for (var handler : handlers) {
try {
handler.run();
} catch (Exception e) {
exceptions.add(e);
}
}
}
private static void throwIfNeeded(List<Exception> exceptions) {
if (exceptions.isEmpty())
return;
var aggregate = new RuntimeException("Failed to apply some object holders, see suppressed exceptions for details");
for (var e : exceptions)
aggregate.addSuppressed(e);
throw aggregate;
}
}
@@ -27,7 +27,6 @@ public final class RegistryObject<T> implements Supplier<T> {
private final Identifier name;
@Nullable
private final ResourceKey<T> key;
private final boolean optionalRegistry;
@Nullable
private T value;
@Nullable
@@ -129,7 +128,6 @@ public final class RegistryObject<T> implements Supplier<T> {
private RegistryObject() {
this.name = null;
this.key = null;
this.optionalRegistry = false;
}
@SuppressWarnings("unchecked")
@@ -138,40 +136,21 @@ public final class RegistryObject<T> implements Supplier<T> {
throw new IllegalArgumentException("Invalid registry argument, must not be null");
this.name = name;
this.key = (ResourceKey<T>) ResourceKey.create(registry.getRegistryKey(), name);
this.optionalRegistry = false;
ObjectHolderRegistry.addHandler(pred -> {
if (pred.test(registry.getRegistryName()))
this.updateReference((IForgeRegistry<? extends T>) registry);
});
ObjectHolderRegistry.addHandler(registry.getRegistryName(), () -> this.updateReference((IForgeRegistry<? extends T>) registry));
this.updateReference((IForgeRegistry<? extends T>) registry);
}
private RegistryObject(final Identifier name, final Identifier registryName, final String modid, boolean optionalRegistry) {
this.name = name;
this.key = ResourceKey.create(ResourceKey.createRegistryKey(registryName), name);
this.optionalRegistry = optionalRegistry;
final Throwable callerStack = new Throwable("Calling Site from mod: " + modid);
ObjectHolderRegistry.addHandler(new Consumer<>() {
private boolean registryExists = false;
private boolean invalidRegistry = false;
@Override
public void accept(Predicate<Identifier> pred) {
if (invalidRegistry)
return;
if (!RegistryObject.this.optionalRegistry && !registryExists) {
if (!registryExists(registryName)) {
invalidRegistry = true;
throw new IllegalStateException("Unable to find registry with key " + registryName + " for mod \"" + modid + "\". Check the 'caused by' to see further stack.", callerStack);
}
registryExists = true;
}
if (pred.test(registryName))
RegistryObject.this.updateReference(registryName);
}
});
if (!optionalRegistry) {
final Throwable callerStack = new Throwable("Calling Site from mod: " + modid);
ObjectHolderRegistry.addValidator(() -> {
if (!registryExists(registryName))
throw new IllegalStateException("Unable to find registry with key " + registryName + " for mod \"" + modid + "\". Check the 'caused by' to see further stack.", callerStack);
});
}
ObjectHolderRegistry.addHandler(registryName, () -> this.updateReference(registryName));
this.updateReference(registryName);
}
@@ -181,7 +160,6 @@ public final class RegistryObject<T> implements Supplier<T> {
@SuppressWarnings("unchecked")
var typed = (ResourceKey<Registry<T>>)registry;
this.key = ResourceKey.create(typed, name);
this.optionalRegistry = true;
}
/**
@@ -481,8 +459,8 @@ public final class RegistryObject<T> implements Supplier<T> {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof RegistryObject o) {
return Objects.equals(o.name, name);
if (obj instanceof RegistryObject<?> o) {
return o.key == key && Objects.equals(o.name, name);
}
return false;
}
@@ -4,6 +4,21 @@ public net.minecraft.client.Minecraft textureManager
public-f net.minecraft.client.Options keyMappings
public net.minecraft.client.Options$FieldAccess
public net.minecraft.client.color.item.ItemTintSources ID_MAPPER
#group protected net.minecraft.client.data.AtlasProvider *()
protected net.minecraft.client.data.AtlasProvider armorTrims()Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider bannerPatterns()Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider blocksList()Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider extractAllMaterialAssets()Ljava/util/stream/Stream;
protected net.minecraft.client.data.AtlasProvider forMapper(Lnet/minecraft/client/renderer/SpriteMapper;)Lnet/minecraft/client/renderer/texture/atlas/SpriteSource;
protected net.minecraft.client.data.AtlasProvider forMaterial(Lnet/minecraft/client/resources/model/sprite/SpriteId;)Lnet/minecraft/client/renderer/texture/atlas/SpriteSource;
protected net.minecraft.client.data.AtlasProvider guiSprites()Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider itemsList()Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider noPrefixMapper(Ljava/lang/String;)Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider patternTextures()Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider shieldPatterns()Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider simpleMapper(Lnet/minecraft/client/renderer/SpriteMapper;)Ljava/util/List;
protected net.minecraft.client.data.AtlasProvider storeAtlas(Lnet/minecraft/data/CachedOutput;Lnet/minecraft/resources/Identifier;Ljava/util/List;)Ljava/util/concurrent/CompletableFuture;
#endgroup
#group protected net.minecraft.client.data.models.BlockModelGenerators *()
protected net.minecraft.client.data.models.BlockModelGenerators addBookSlotModel(Lnet/minecraft/client/data/models/blockstates/MultiPartGenerator;Lnet/minecraft/client/renderer/block/dispatch/multipart/Condition;Lnet/minecraft/client/renderer/block/dispatch/VariantMutator;Lnet/minecraft/world/level/block/state/properties/BooleanProperty;Lnet/minecraft/client/data/models/model/ModelTemplate;Z)V
protected net.minecraft.client.data.models.BlockModelGenerators addShelfPart(Lnet/minecraft/world/level/block/Block;Lnet/minecraft/client/data/models/model/TextureMapping;Lnet/minecraft/client/data/models/blockstates/MultiPartGenerator;Lnet/minecraft/client/data/models/model/ModelTemplate;Ljava/lang/Boolean;Lnet/minecraft/world/level/block/state/properties/SideChainPart;)V
@@ -150,6 +150,10 @@
"forge.configgui.removeErroringBlockEntities": "Remove Erroring Block Entities",
"forge.configgui.fullBoundingBoxLadders.tooltip": "Set this to true to check the entire entity's collision bounding box for ladders instead of just the block they are in. Causes noticeable differences in mechanics so default is vanilla behavior. Default: false.",
"forge.configgui.fullBoundingBoxLadders": "Full Bounding Box Ladders",
"forge.configgui.permissionHandler.tooltip": "The permission handler used by the server. Defaults to forge:default_handler if no such handler with that name is registered.",
"forge.configgui.permissionHandler": "Permission Handler",
"forge.configgui.advertiseDedicatedServerToLan.tooltip": "Set this to true to enable advertising the dedicated server to local LAN clients so that it shows up in the Multiplayer screen automatically.",
"forge.configgui.advertiseDedicatedServerToLan": "Advertise Dedicated Server to LAN",
"forge.configgui.zombieBaseSummonChance.tooltip": "Base zombie summoning spawn chance. Allows changing the bonus zombie summoning mechanic.",
"forge.configgui.zombieBaseSummonChance": "Zombie Summon Chance",
"forge.configgui.zombieBabyChance.tooltip": "Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic.",
@@ -200,6 +204,12 @@
"forge.configgui.stabilizeDirectionGetNearest": "Stabilize Direction Get Nearest",
"forge.configgui.stabilizeDirectionGetNearest.tooltip": "When enabled, a slightly biased Direction#getNearest calculation will be used to prevent normal fighting on 45 degree angle faces.",
"forge.configgui.channellistlimit": "Channel List Size Limit",
"forge.configgui.channellistlimit.tooltip": "Maximum size to use to store the channel list for a network connection. Some malicious actors may use this to use up all your memory. A sane default is 1KB (1024), but may need to be expanded for larger modpacks.",
"forge.configgui.migrationHelperMode": "Migration Helper Mode",
"forge.configgui.migrationHelperMode.tooltip": "A config option to help developers find known legacy modded tags that have common convention equivalents when running on integrated server. Defaults to OFF.",
"forge.server.channellistlimit.kick": "Received too many networking channels",
"forge.controlsgui.shift": "SHIFT + %s",
"forge.controlsgui.control": "CTRL + %s",
"forge.controlsgui.control.mac": "CMD + %s",
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more