diff --git a/build.gradle b/build.gradle index fe8a2e7..b894d64 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -//version: 1675110695 +//version: 1692122114 /* DO NOT CHANGE THIS FILE! Also, you may replace this file at any time if there is an update available. @@ -6,27 +6,28 @@ */ -import com.diffplug.blowdryer.Blowdryer -import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import com.gtnewhorizons.retrofuturagradle.ObfuscationAttribute import com.gtnewhorizons.retrofuturagradle.mcp.ReobfuscatedJar +import com.gtnewhorizons.retrofuturagradle.minecraft.RunMinecraftTask +import com.gtnewhorizons.retrofuturagradle.util.Distribution import com.matthewprenger.cursegradle.CurseArtifact import com.matthewprenger.cursegradle.CurseRelation import com.modrinth.minotaur.dependencies.ModDependency import com.modrinth.minotaur.dependencies.VersionDependency import org.gradle.internal.logging.text.StyledTextOutput.Style import org.gradle.internal.logging.text.StyledTextOutputFactory -import org.jetbrains.gradle.ext.* +import org.gradle.internal.xml.XmlTransformer +import org.jetbrains.gradle.ext.Application +import org.jetbrains.gradle.ext.Gradle +import javax.inject.Inject import java.nio.file.Files import java.nio.file.Paths import java.util.concurrent.TimeUnit -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream buildscript { repositories { - mavenLocal() mavenCentral() maven { @@ -47,6 +48,8 @@ buildscript { name 'Scala CI dependencies' url 'https://repo1.maven.org/maven2/' } + + mavenLocal() } } plugins { @@ -58,22 +61,26 @@ plugins { id 'org.jetbrains.kotlin.jvm' version '1.8.0' apply false id 'org.jetbrains.kotlin.kapt' version '1.8.0' apply false id 'com.google.devtools.ksp' version '1.8.0-1.0.9' apply false - id 'org.ajoberstar.grgit' version '4.1.1' // 4.1.1 is the last jvm8 supporting version ,unused, available for addon.gradle - id 'com.github.johnrengelman.shadow' version '7.1.2' apply false - id 'com.palantir.git-version' version '0.13.0' apply false // 0.13.0 is the last jvm8 supporting version - id 'de.undercouch.download' version '5.3.0' + id 'org.ajoberstar.grgit' version '4.1.1' // 4.1.1 is the last jvm8 supporting version, unused, available for addon.gradle + id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id 'com.palantir.git-version' version '3.0.0' apply false + id 'de.undercouch.download' version '5.4.0' id 'com.github.gmazzo.buildconfig' version '3.1.0' apply false // Unused, available for addon.gradle - id 'com.diffplug.spotless' version '6.7.2' apply false + id 'com.diffplug.spotless' version '6.13.0' apply false // 6.13.0 is the last jvm8 supporting version id 'com.modrinth.minotaur' version '2.+' apply false id 'com.matthewprenger.cursegradle' version '1.4.0' apply false - id 'com.gtnewhorizons.retrofuturagradle' version '1.1.2' + id 'com.gtnewhorizons.retrofuturagradle' version '1.3.24' } + +print("You might want to check out './gradlew :faq' if your build fails.\n") + boolean settingsupdated = verifySettingsGradle() settingsupdated = verifyGitAttributes() || settingsupdated if (settingsupdated) throw new GradleException("Settings has been updated, please re-run task.") -if (project.file('.git/HEAD').isFile()) { +// In submodules, .git is a file pointing to the real git dir +if (project.file('.git/HEAD').isFile() || project.file('.git').isFile()) { apply plugin: 'com.palantir.git-version' } @@ -108,6 +115,8 @@ propertyDefaultIfUnset("usesMixinDebug", project.usesMixins) propertyDefaultIfUnset("forceEnableMixins", false) propertyDefaultIfUnset("channel", "stable") propertyDefaultIfUnset("mappingsVersion", "12") +propertyDefaultIfUnset("usesMavenPublishing", true) +propertyDefaultIfUnset("mavenPublishUrl", "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases") propertyDefaultIfUnset("modrinthProjectId", "") propertyDefaultIfUnset("modrinthRelations", "") propertyDefaultIfUnset("curseForgeProjectId", "") @@ -122,7 +131,10 @@ propertyDefaultIfUnset("gradleTokenGroupName", "") propertyDefaultIfUnset("enableModernJavaSyntax", false) // On by default for new projects only propertyDefaultIfUnset("enableGenericInjection", false) // On by default for new projects only -project.extensions.add(Blowdryer, "Blowdryer", Blowdryer) // Make blowdryer available in "apply from:" scripts +// this is meant to be set using the user wide property file. by default we do nothing. +propertyDefaultIfUnset("ideaOverrideBuildType", "") // Can be nothing, "gradle" or "idea" + +project.extensions.add(com.diffplug.blowdryer.Blowdryer, "Blowdryer", com.diffplug.blowdryer.Blowdryer) // Make blowdryer available in "apply from:" scripts if (!disableSpotless) { apply plugin: 'com.diffplug.spotless' apply from: Blowdryer.file('spotless.gradle') @@ -143,13 +155,21 @@ java { } else { languageVersion.set(projectJavaVersion) } - vendor.set(JvmVendorSpec.ADOPTIUM) + vendor.set(JvmVendorSpec.AZUL) } if (!noPublishedSources) { withSourcesJar() } } +tasks.withType(JavaCompile).configureEach { + options.encoding = "UTF-8" +} + +tasks.withType(ScalaCompile).configureEach { + options.encoding = "UTF-8" +} + pluginManager.withPlugin('org.jetbrains.kotlin.jvm') { // If Kotlin is enabled in the project kotlin { @@ -183,14 +203,34 @@ configurations { canBeConsumed = false canBeResolved = false } + + create("devOnlyNonPublishable") { + description = "Runtime and compiletime dependencies that are not published alongside the jar (compileOnly + runtimeOnlyNonPublishable)" + canBeConsumed = false + canBeResolved = false + } + compileOnly.extendsFrom(devOnlyNonPublishable) + runtimeOnlyNonPublishable.extendsFrom(devOnlyNonPublishable) } if (enableModernJavaSyntax.toBoolean()) { + repositories { + mavenCentral { + mavenContent { + includeGroup("me.eigenraven.java8unsupported") + } + } + } + dependencies { annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:1.0.0' + // workaround for https://github.com/bsideup/jabel/issues/174 + annotationProcessor 'net.java.dev.jna:jna-platform:5.13.0' compileOnly('com.github.bsideup.jabel:jabel-javac-plugin:1.0.0') { transitive = false // We only care about the 1 annotation class } + // Allow using jdk.unsupported classes like sun.misc.Unsafe in the compiled code, working around JDK-8206937. + patchedMinecraft('me.eigenraven.java8unsupported:java-8-unsupported-shim:1.0.0') } tasks.withType(JavaCompile).configureEach { @@ -202,7 +242,7 @@ if (enableModernJavaSyntax.toBoolean()) { javaCompiler.set(javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(17)) - vendor.set(JvmVendorSpec.ADOPTIUM) + vendor.set(JvmVendorSpec.AZUL) }) } } @@ -234,12 +274,14 @@ if (apiPackage) { } if (accessTransformersFile) { - String targetFile = "src/main/resources/META-INF/" + accessTransformersFile - if (!getFile(targetFile).exists()) { - throw new GradleException("Could not resolve \"accessTransformersFile\"! Could not find " + targetFile) + for (atFile in accessTransformersFile.split(",")) { + String targetFile = "src/main/resources/META-INF/" + atFile.trim() + if (!getFile(targetFile).exists()) { + throw new GradleException("Could not resolve \"accessTransformersFile\"! Could not find " + targetFile) + } + tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(targetFile) + tasks.srgifyBinpatchedJar.accessTransformerFiles.from(targetFile) } - tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(targetFile) - tasks.srgifyBinpatchedJar.accessTransformerFiles.from(targetFile) } else { boolean atsFound = false for (File at : sourceSets.getByName("main").resources.files) { @@ -317,7 +359,27 @@ catch (Exception ignored) { String identifiedVersion String versionOverride = System.getenv("VERSION") ?: null try { - identifiedVersion = versionOverride == null ? gitVersion() : versionOverride + // Produce a version based on the tag, or for branches something like 0.2.2-configurable-maven-and-extras.38+43090270b6-dirty + if (versionOverride == null) { + def gitDetails = versionDetails() + def isDirty = gitVersion().endsWith(".dirty") // No public API for this, isCleanTag has a different meaning + String branchName = gitDetails.branchName ?: (System.getenv('GIT_BRANCH') ?: 'git') + if (branchName.startsWith('origin/')) { + branchName = branchName.minus('origin/') + } + branchName = branchName.replaceAll("[^a-zA-Z0-9-]+", "-") // sanitize branch names for semver + identifiedVersion = gitDetails.lastTag ?: '${gitDetails.gitHash}' + if (gitDetails.commitDistance > 0) { + identifiedVersion += "-${branchName}.${gitDetails.commitDistance}+${gitDetails.gitHash}" + if (isDirty) { + identifiedVersion += "-dirty" + } + } else if (isDirty) { + identifiedVersion += "-${branchName}+${gitDetails.gitHash}-dirty" + } + } else { + identifiedVersion = versionOverride + } } catch (Exception ignored) { out.style(Style.Failure).text( @@ -339,9 +401,13 @@ if (identifiedVersion == versionOverride) { group = "com.github.GTNewHorizons" if (project.hasProperty("customArchiveBaseName") && customArchiveBaseName) { - archivesBaseName = customArchiveBaseName + base { + archivesName = customArchiveBaseName + } } else { - archivesBaseName = modId + base { + archivesName = modId + } } @@ -367,12 +433,14 @@ minecraft { injectMissingGenerics.set(true) } + username = developmentEnvironmentUserName.toString() + + lwjgl3Version = "3.3.2" + // Enable assertions in the current mod extraRunJvmArguments.add("-ea:${modGroup}") if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) { - extraTweakClasses.add("org.spongepowered.asm.launch.MixinTweaker") - if (usesMixinDebug.toBoolean()) { extraRunJvmArguments.addAll([ "-Dmixin.debug.countInjections=true", @@ -401,6 +469,16 @@ configurations.configureEach { } } } + def obfuscationAttr = it.attributes.getAttribute(ObfuscationAttribute.OBFUSCATION_ATTRIBUTE) + if (obfuscationAttr != null && obfuscationAttr.name == ObfuscationAttribute.SRG) { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + // Remap CoFH core cursemaven dev jar to the obfuscated version for runObfClient/Server + if (details.requested.group == 'curse.maven' && details.requested.name.endsWith('-69162') && details.requested.version == '2388751') { + details.useVersion '2388750' + details.because 'Pick obfuscated jar' + } + } + } } // Ensure tests have access to minecraft classes @@ -413,10 +491,19 @@ sourceSets { } } -if (file('addon.gradle').exists()) { +if (file('addon.gradle.kts').exists()) { + apply from: 'addon.gradle.kts' +} else if (file('addon.gradle').exists()) { apply from: 'addon.gradle' } +// File for local tweaks not commited to Git +if (file('addon.local.gradle.kts').exists()) { + apply from: 'addon.local.gradle.kts' +} else if (file('addon.local.gradle').exists()) { + apply from: 'addon.local.gradle' +} + // Allow unsafe repos but warn repositories.configureEach { repo -> if (repo instanceof org.gradle.api.artifacts.repositories.UrlArtifactRepository) { @@ -427,11 +514,19 @@ repositories.configureEach { repo -> } } -apply from: 'repositories.gradle' +if (file('repositories.gradle.kts').exists()) { + apply from: 'repositories.gradle.kts' +} else if (file('repositories.gradle').exists()) { + apply from: 'repositories.gradle' +} else { + logger.error("Neither repositories.gradle.kts nor repositories.gradle was found, make sure you extracted the full ExampleMod template.") + throw new RuntimeException("Missing repositories.gradle[.kts]") +} configurations { + runtimeClasspath.extendsFrom(runtimeOnlyNonPublishable) + testRuntimeClasspath.extendsFrom(runtimeOnlyNonPublishable) for (config in [compileClasspath, runtimeClasspath, testCompileClasspath, testRuntimeClasspath]) { - config.extendsFrom(runtimeOnlyNonPublishable) if (usesShadowedDependencies.toBoolean()) { config.extendsFrom(shadowImplementation) // TODO: remove Compile after all uses are refactored to Implementation @@ -467,31 +562,42 @@ repositories { maven { name 'Overmind forge repo mirror' url 'https://gregtech.overminddl1.com/' - mavenContent { - excludeGroup("net.minecraftforge") // missing the `universal` artefact - } } maven { name = "GTNH Maven" url = "http://jenkins.usrv.eu:8081/nexus/content/groups/public/" allowInsecureProtocol = true } - if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) { - if (usesMixinDebug.toBoolean()) { - maven { - name = "Fabric Maven" - url = "https://maven.fabricmc.net/" - } + maven { + name 'sonatype' + url 'https://oss.sonatype.org/content/repositories/snapshots/' + content { + includeGroup "org.lwjgl" } } if (includeWellKnownRepositories.toBoolean()) { - maven { - name "CurseMaven" - url "https://cursemaven.com" - content { + exclusiveContent { + forRepository { + maven { + name "CurseMaven" + url "https://cursemaven.com" + } + } + filter { includeGroup "curse.maven" } } + exclusiveContent { + forRepository { + maven { + name = "Modrinth" + url = "https://api.modrinth.com/maven" + } + } + filter { + includeGroup "maven.modrinth" + } + } maven { name = "ic2" url = "https://maven.ic2.player.to/" @@ -515,35 +621,78 @@ repositories { } } +def mixinProviderGroup = "io.github.legacymoddingmc" +def mixinProviderModule = "unimixins" +def mixinProviderVersion = "0.1.7.1" +def mixinProviderSpecNoClassifer = "${mixinProviderGroup}:${mixinProviderModule}:${mixinProviderVersion}" +def mixinProviderSpec = "${mixinProviderSpecNoClassifer}:dev" +ext.mixinProviderSpec = mixinProviderSpec + +def mixingConfigRefMap = 'mixins.' + modId + '.refmap.json' + dependencies { if (usesMixins.toBoolean()) { annotationProcessor('org.ow2.asm:asm-debug-all:5.0.3') annotationProcessor('com.google.guava:guava:24.1.1-jre') annotationProcessor('com.google.code.gson:gson:2.8.6') - annotationProcessor('com.gtnewhorizon:gtnhmixins:2.1.10:processor') + annotationProcessor(mixinProviderSpec) if (usesMixinDebug.toBoolean()) { runtimeOnlyNonPublishable('org.jetbrains:intellij-fernflower:1.2.1.16') } } - if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) { - implementation('com.gtnewhorizon:gtnhmixins:2.1.10') + if (usesMixins.toBoolean()) { + implementation(modUtils.enableMixins(mixinProviderSpec, mixingConfigRefMap)) + } else if (forceEnableMixins.toBoolean()) { + runtimeOnlyNonPublishable(mixinProviderSpec) } } pluginManager.withPlugin('org.jetbrains.kotlin.kapt') { if (usesMixins.toBoolean()) { dependencies { - kapt('com.gtnewhorizon:gtnhmixins:2.1.10:processor') + kapt(mixinProviderSpec) } } } -apply from: 'dependencies.gradle' +// Replace old mixin mods with unimixins +// https://docs.gradle.org/8.0.2/userguide/resolution_rules.html#sec:substitution_with_classifier +configurations.all { + resolutionStrategy.dependencySubstitution { + substitute module('com.gtnewhorizon:gtnhmixins') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('com.github.GTNewHorizons:Mixingasm') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('com.github.GTNewHorizons:SpongePoweredMixin') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('com.github.GTNewHorizons:SpongeMixins') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Unimixins replaces other mixin mods") + substitute module('io.github.legacymoddingmc:unimixins') using module(mixinProviderSpecNoClassifer) withClassifier("dev") because("Our previous unimixins upload was missing the dev classifier") + } +} -def mixingConfigRefMap = 'mixins.' + modId + '.refmap.json' -def mixinTmpDir = buildDir.path + File.separator + 'tmp' + File.separator + 'mixins' -def refMap = "${mixinTmpDir}" + File.separator + mixingConfigRefMap -def mixinSrg = "${mixinTmpDir}" + File.separator + "mixins.srg" +dependencies { + constraints { + def minGtnhLibVersion = "0.0.13" + implementation("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + runtimeOnly("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + devOnlyNonPublishable("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + runtimeOnlyNonPublishable("com.github.GTNewHorizons:GTNHLib:${minGtnhLibVersion}") { + because("fixes duplicate mod errors in java 17 configurations using old gtnhlib") + } + } +} + +if (file('dependencies.gradle.kts').exists()) { + apply from: 'dependencies.gradle.kts' +} else if (file('dependencies.gradle').exists()) { + apply from: 'dependencies.gradle' +} else { + logger.error("Neither dependencies.gradle.kts nor dependencies.gradle was found, make sure you extracted the full ExampleMod template.") + throw new RuntimeException("Missing dependencies.gradle[.kts]") +} tasks.register('generateAssets') { group = "GTNH Buildscript" @@ -575,46 +724,17 @@ tasks.register('generateAssets') { } if (usesMixins.toBoolean()) { - tasks.named("reobfJar", ReobfuscatedJar).configure { - extraSrgFiles.from(mixinSrg) - } - tasks.named("processResources").configure { dependsOn("generateAssets") } tasks.named("compileJava", JavaCompile).configure { - doFirst { - new File(mixinTmpDir).mkdirs() - } options.compilerArgs += [ - "-AreobfSrgFile=${tasks.reobfJar.srg.get().asFile}", - "-AoutSrgFile=${mixinSrg}", - "-AoutRefMapFile=${refMap}", // Elan: from what I understand they are just some linter configs so you get some warning on how to properly code "-XDenableSunApiLintControl", "-XDignore.symbol.file" ] } - - pluginManager.withPlugin('org.jetbrains.kotlin.kapt') { - kapt { - correctErrorTypes = true - javacOptions { - option("-AreobfSrgFile=${tasks.reobfJar.srg.get().asFile}") - option("-AoutSrgFile=$mixinSrg") - option("-AoutRefMapFile=$refMap") - } - } - tasks.configureEach { task -> - if (task.name == "kaptKotlin") { - task.doFirst { - new File(mixinTmpDir).mkdirs() - } - } - } - } - } tasks.named("processResources", ProcessResources).configure { @@ -632,10 +752,158 @@ tasks.named("processResources", ProcessResources).configure { } if (usesMixins.toBoolean()) { - from refMap + dependsOn("compileJava", "compileScala") } } +ext.java17Toolchain = (JavaToolchainSpec spec) -> { + spec.languageVersion.set(JavaLanguageVersion.of(17)) + spec.vendor.set(JvmVendorSpec.matching("jetbrains")) +} + +ext.java17DependenciesCfg = configurations.create("java17Dependencies") { + extendsFrom(configurations.getByName("runtimeClasspath")) // Ensure consistent transitive dependency resolution + canBeConsumed = false +} +ext.java17PatchDependenciesCfg = configurations.create("java17PatchDependencies") { + canBeConsumed = false +} + +dependencies { + def lwjgl3ifyVersion = '1.4.0' + def asmVersion = '9.4' + if (modId != 'lwjgl3ify') { + java17Dependencies("com.github.GTNewHorizons:lwjgl3ify:${lwjgl3ifyVersion}") + } + if (modId != 'hodgepodge') { + java17Dependencies('com.github.GTNewHorizons:Hodgepodge:2.2.26') + } + + java17PatchDependencies('net.minecraft:launchwrapper:1.17.2') {transitive = false} + java17PatchDependencies("org.ow2.asm:asm:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-commons:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-tree:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-analysis:${asmVersion}") + java17PatchDependencies("org.ow2.asm:asm-util:${asmVersion}") + java17PatchDependencies('org.ow2.asm:asm-deprecated:7.1') + java17PatchDependencies("org.apache.commons:commons-lang3:3.12.0") + java17PatchDependencies("com.github.GTNewHorizons:lwjgl3ify:${lwjgl3ifyVersion}:forgePatches") {transitive = false} +} + +ext.java17JvmArgs = [ + // Java 9+ support + "--illegal-access=warn", + "-Djava.security.manager=allow", + "-Dfile.encoding=UTF-8", + "--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED", + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/java.nio=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens", "java.base/java.text=ALL-UNNAMED", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.reflect=ALL-UNNAMED", + "--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens", "jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED,java.naming", + "--add-opens", "java.desktop/sun.awt.image=ALL-UNNAMED", + "--add-modules", "jdk.dynalink", + "--add-opens", "jdk.dynalink/jdk.dynalink.beans=ALL-UNNAMED", + "--add-modules", "java.sql.rowset", + "--add-opens", "java.sql.rowset/javax.sql.rowset.serial=ALL-UNNAMED" +] + +ext.hotswapJvmArgs = [ + // DCEVM advanced hot reload + "-XX:+AllowEnhancedClassRedefinition", + "-XX:HotswapAgent=fatjar" +] + +ext.setupHotswapAgentTask = tasks.register("setupHotswapAgent") { + group = "GTNH Buildscript" + description = "Installs a recent version of HotSwapAgent into the Java 17 JetBrains runtime directory" + def hsaUrl = 'https://github.com/HotswapProjects/HotswapAgent/releases/download/1.4.2-SNAPSHOT/hotswap-agent-1.4.2-SNAPSHOT.jar' + def targetFolderProvider = javaToolchains.launcherFor(java17Toolchain).map {it.metadata.installationPath.dir("lib/hotswap")} + def targetFilename = "hotswap-agent.jar" + onlyIf { + !targetFolderProvider.get().file(targetFilename).asFile.exists() + } + doLast { + def targetFolder = targetFolderProvider.get() + targetFolder.asFile.mkdirs() + download.run { + src hsaUrl + dest targetFolder.file(targetFilename).asFile + overwrite false + tempAndMove true + } + } +} + +public abstract class RunHotswappableMinecraftTask extends RunMinecraftTask { + // IntelliJ doesn't seem to allow commandline arguments so we also support an env variable + private boolean enableHotswap = Boolean.valueOf(System.getenv("HOTSWAP")); + + @Input + public boolean getEnableHotswap() { return enableHotswap } + @Option(option = "hotswap", description = "Enables HotSwapAgent for enhanced class reloading under a debugger") + public boolean setEnableHotswap(boolean enable) { enableHotswap = enable } + + @Inject + public RunHotswappableMinecraftTask(Distribution side, String superTask, org.gradle.api.invocation.Gradle gradle) { + super(side, gradle) + + this.lwjglVersion = 3 + this.javaLauncher = project.javaToolchains.launcherFor(project.java17Toolchain) + this.extraJvmArgs.addAll(project.java17JvmArgs) + this.extraJvmArgs.addAll(project.provider(() -> enableHotswap ? project.hotswapJvmArgs : [])) + + this.classpath(project.java17PatchDependenciesCfg) + if (side == Distribution.CLIENT) { + this.classpath(project.minecraftTasks.lwjgl3Configuration) + } + // Use a raw provider instead of map to not create a dependency on the task + this.classpath(project.provider(() -> project.tasks.named(superTask, RunMinecraftTask).get().classpath)) + this.classpath.filter { file -> + !file.path.contains("2.9.4-nightly-20150209") // Remove lwjgl2 + } + this.classpath(project.java17DependenciesCfg) + } + + public void setup(Project project) { + super.setup(project) + if (project.usesMixins.toBoolean()) { + this.extraJvmArgs.addAll(project.provider(() -> { + def mixinCfg = project.configurations.detachedConfiguration(project.dependencies.create(project.mixinProviderSpec)) + mixinCfg.canBeConsumed = false + mixinCfg.transitive = false + enableHotswap ? ["-javaagent:" + mixinCfg.singleFile.absolutePath] : [] + })) + } + } +} + +def runClient17Task = tasks.register("runClient17", RunHotswappableMinecraftTask, Distribution.CLIENT, "runClient") +runClient17Task.configure { + setup(project) + group = "Modded Minecraft" + description = "Runs the modded client using Java 17, lwjgl3ify and Hodgepodge" + dependsOn(setupHotswapAgentTask, mcpTasks.launcherSources.classesTaskName, minecraftTasks.taskDownloadVanillaAssets, mcpTasks.taskPackagePatchedMc, 'jar') + mainClass = "GradleStart" + username = minecraft.username + userUUID = minecraft.userUUID +} + +def runServer17Task = tasks.register("runServer17", RunHotswappableMinecraftTask, Distribution.DEDICATED_SERVER, "runServer") +runServer17Task.configure { + setup(project) + group = "Modded Minecraft" + description = "Runs the modded server using Java 17, lwjgl3ify and Hodgepodge" + dependsOn(setupHotswapAgentTask, mcpTasks.launcherSources.classesTaskName, minecraftTasks.taskDownloadVanillaAssets, mcpTasks.taskPackagePatchedMc, 'jar') + mainClass = "GradleStartServer" + extraArgs.add("nogui") +} + def getManifestAttributes() { def manifestAttributes = [:] if (!containsMixinsAndOrCoreModOnly.toBoolean() && (usesMixins.toBoolean() || coreModClass)) { @@ -667,11 +935,6 @@ tasks.named("jar", Jar).configure { } if (usesShadowedDependencies.toBoolean()) { - tasks.register('relocateShadowJar', ConfigureShadowRelocation) { - target = tasks.shadowJar - prefix = modGroup + ".shadow" - enabled = minimizeShadowedDependencies.toBoolean() - } tasks.named("shadowJar", ShadowJar).configure { manifest { attributes(getManifestAttributes()) @@ -686,8 +949,9 @@ if (usesShadowedDependencies.toBoolean()) { project.configurations.shadeCompile ] archiveClassifier.set('dev') - if (minimizeShadowedDependencies.toBoolean()) { - dependsOn(relocateShadowJar) + if (relocateShadowedDependencies.toBoolean()) { + relocationPrefix = modGroup + ".shadow" + enableRelocation = true } } configurations.runtimeElements.outgoing.artifacts.clear() @@ -705,7 +969,7 @@ if (usesShadowedDependencies.toBoolean()) { javaComponent.withVariantsFromConfiguration(configurations.shadowRuntimeElements) { skip() } - for (runTask in ["runClient", "runServer"]) { + for (runTask in ["runClient", "runServer", "runClient17", "runServer17"]) { tasks.named(runTask).configure { dependsOn("shadowJar") } @@ -743,16 +1007,47 @@ idea { module { downloadJavadoc = true downloadSources = true + inheritOutputDirs = true } project { settings { + if (ideaOverrideBuildType != "") { + delegateActions { + if ("gradle".equalsIgnoreCase(ideaOverrideBuildType)) { + delegateBuildRunToGradle = true + testRunner = org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.GRADLE + } else if ("idea".equalsIgnoreCase(ideaOverrideBuildType)) { + delegateBuildRunToGradle = false + testRunner = org.jetbrains.gradle.ext.ActionDelegationConfig.TestRunner.PLATFORM + } else { + throw GradleScriptException('Accepted value for ideaOverrideBuildType is one of gradle or idea.') + } + } + } runConfigurations { + "0. Build and Test"(Gradle) { + taskNames = ["build"] + } "1. Run Client"(Gradle) { taskNames = ["runClient"] } "2. Run Server"(Gradle) { taskNames = ["runServer"] } + "1a. Run Client (Java 17)"(Gradle) { + taskNames = ["runClient17"] + } + "2a. Run Server (Java 17)"(Gradle) { + taskNames = ["runServer17"] + } + "1b. Run Client (Java 17, Hotswap)"(Gradle) { + taskNames = ["runClient17"] + envs = ["HOTSWAP": "true"] + } + "2b. Run Server (Java 17, Hotswap)"(Gradle) { + taskNames = ["runServer17"] + envs = ["HOTSWAP": "true"] + } "3. Run Obfuscated Client"(Gradle) { taskNames = ["runObfClient"] } @@ -770,7 +1065,7 @@ idea { } "Run Client (IJ Native)"(Application) { mainClass = "GradleStart" - moduleName = project.name + ".main" + moduleName = project.name + ".ideVirtualMain" afterEvaluate { workingDirectory = tasks.runClient.workingDir.absolutePath programParameters = tasks.runClient.calculateArgs(project).collect { '"' + it + '"' }.join(' ') @@ -781,7 +1076,7 @@ idea { } "Run Server (IJ Native)"(Application) { mainClass = "GradleStartServer" - moduleName = project.name + ".main" + moduleName = project.name + ".ideVirtualMain" afterEvaluate { workingDirectory = tasks.runServer.workingDir.absolutePath programParameters = tasks.runServer.calculateArgs(project).collect { '"' + it + '"' }.join(' ') @@ -793,11 +1088,57 @@ idea { } compiler.javac { afterEvaluate { + javacAdditionalOptions = "-encoding utf8" moduleJavacAdditionalOptions = [ (project.name + ".main"): tasks.compileJava.options.compilerArgs.collect { '"' + it + '"' }.join(' ') ] } } + withIDEADir { File ideaDir -> + if (!ideaDir.path.contains(".idea")) { + // If an .ipr file exists, the project root directory is passed here instead of the .idea subdirectory + ideaDir = new File(ideaDir, ".idea") + } + if (ideaDir.isDirectory()) { + def miscFile = new File(ideaDir, "misc.xml") + if (miscFile.isFile()) { + boolean dirty = false + def miscTransformer = new XmlTransformer() + miscTransformer.addAction { root -> + Node rootNode = root.asNode() + def rootManager = rootNode + .component.find { it.@name == 'ProjectRootManager' } + if (!rootManager) { + rootManager = rootNode.appendNode('component', ['name': 'ProjectRootManager', 'version': '2']) + dirty = true + } + def output = rootManager.output + if (!output) { + output = rootManager.appendNode('output') + dirty = true + } + if (!output.@url) { + // Only modify the output url if it doesn't yet have one, or if the existing one is blank somehow. + // This is a sensible default for most setups + output.@url = 'file://$PROJECT_DIR$/build/ideaBuild' + dirty = true + } + } + def result = miscTransformer.transform(miscFile.text) + if (dirty) { + miscFile.write(result) + } + } else { + miscFile.text = """ + + + + + +""" + } + } + } } } } @@ -806,6 +1147,14 @@ tasks.named("processIdeaSettings").configure { dependsOn("injectTags") } +tasks.named("ideVirtualMainClasses").configure { + // Make IntelliJ "Build project" build the mod jars + dependsOn("jar", "reobfJar") + if (!disableSpotless) { + dependsOn("spotlessCheck") + } +} + // workaround variable hiding in pom processing def projectConfigs = project.configurations @@ -826,12 +1175,14 @@ publishing { } repositories { - maven { - url = "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases" - allowInsecureProtocol = true - credentials { - username = System.getenv("MAVEN_USER") ?: "NONE" - password = System.getenv("MAVEN_PASSWORD") ?: "NONE" + if (usesMavenPublishing.toBoolean()) { + maven { + url = mavenPublishUrl + allowInsecureProtocol = mavenPublishUrl.startsWith("http://") // Mostly for the GTNH maven + credentials { + username = System.getenv("MAVEN_USER") ?: "NONE" + password = System.getenv("MAVEN_PASSWORD") ?: "NONE" + } } } } @@ -867,7 +1218,7 @@ if (modrinthProjectId.size() != 0 && System.getenv("MODRINTH_TOKEN") != null) { } } if (usesMixins.toBoolean()) { - addModrinthDep("required", "project", "gtnhmixins") + addModrinthDep("required", "project", "unimixins") } tasks.modrinth.dependsOn(build) tasks.publish.dependsOn(tasks.modrinth) @@ -911,7 +1262,7 @@ if (curseForgeProjectId.size() != 0 && System.getenv("CURSEFORGE_TOKEN") != null } } if (usesMixins.toBoolean()) { - addCurseForgeRelation("requiredDependency", "gtnhmixins") + addCurseForgeRelation("requiredDependency", "unimixins") } tasks.curseforge.dependsOn(build) tasks.publish.dependsOn(tasks.curseforge) @@ -945,10 +1296,21 @@ def addCurseForgeRelation(String type, String name) { } // Updating + +def buildscriptGradleVersion = "8.2.1" + +tasks.named('wrapper', Wrapper).configure { + gradleVersion = buildscriptGradleVersion +} + tasks.register('updateBuildScript') { group = 'GTNH Buildscript' description = 'Updates the build script to the latest version' + if (gradle.gradleVersion != buildscriptGradleVersion && !Boolean.getBoolean('DISABLE_BUILDSCRIPT_GRADLE_UPDATE')) { + dependsOn('wrapper') + } + doLast { if (performBuildScriptUpdate()) return @@ -961,6 +1323,26 @@ if (!project.getGradle().startParameter.isOffline() && !Boolean.getBoolean('DISA performBuildScriptUpdate() } else { out.style(Style.SuccessHeader).println("Build script update available! Run 'gradle updateBuildScript'") + if (gradle.gradleVersion != buildscriptGradleVersion) { + out.style(Style.SuccessHeader).println("updateBuildScript can update gradle from ${gradle.gradleVersion} to ${buildscriptGradleVersion}\n") + } + } +} + +// If you want to add more cases to this task, implement them as arguments if total amount to print gets too large +tasks.register('faq') { + group = 'GTNH Buildscript' + description = 'Prints frequently asked questions about building a project' + + doLast { + print("If your build fails to fetch dependencies, run './gradlew updateDependencies'. " + + "Or you can manually check if the versions are still on the distributing sites - " + + "the links can be found in repositories.gradle and build.gradle:repositories, " + + "but not build.gradle:buildscript.repositories - those ones are for gradle plugin metadata.\n\n" + + "If your build fails to recognize the syntax of new Java versions, enable Jabel in your " + + "gradle.properties. See how it's done in GTNH ExampleMod/gradle.properties. " + + "However, keep in mind that Jabel enables only syntax features, but not APIs that were introduced in " + + "Java 9 or later.") } } @@ -1021,8 +1403,14 @@ boolean isNewBuildScriptVersionAvailable() { String currentBuildScript = getFile("build.gradle").getText() String currentBuildScriptHash = getVersionHash(currentBuildScript) - String availableBuildScript = availableBuildScriptUrl().newInputStream(parameters).getText() - String availableBuildScriptHash = getVersionHash(availableBuildScript) + String availableBuildScriptHash + try { + String availableBuildScript = availableBuildScriptUrl().newInputStream(parameters).getText() + availableBuildScriptHash = getVersionHash(availableBuildScript) + } catch (IOException e) { + logger.warn("Could not check for buildscript update availability: {}", e.message) + return false + } boolean isUpToDate = currentBuildScriptHash.empty || availableBuildScriptHash.empty || currentBuildScriptHash == availableBuildScriptHash return !isUpToDate @@ -1101,7 +1489,7 @@ static int replaceParams(File file, Map params) { return 0 } -// Dependency Deobfuscation +// Dependency Deobfuscation (Deprecated, use the new RFG API documented in dependencies.gradle) def deobf(String sourceURL) { try { @@ -1143,11 +1531,7 @@ def deobfMaven(String repoURL, String mavenDep) { } def deobfCurse(String curseDep) { - try { - return deobfMaven("https://www.cursemaven.com/", "curse.maven:$curseDep") - } catch (Exception ignored) { - out.style(Style.Failure).println("Failed to get $curseDep from cursemaven.") - } + return dependencies.rfg.deobf("curse.maven:$curseDep") } // The method above is to be preferred. Use this method if the filename is not at the end of the URL. @@ -1155,34 +1539,7 @@ def deobf(String sourceURL, String rawFileName) { String bon2Version = "2.5.1" String fileName = URLDecoder.decode(rawFileName, "UTF-8") String cacheDir = "$project.gradle.gradleUserHomeDir/caches" - String bon2Dir = "$cacheDir/forge_gradle/deobf" - String bon2File = "$bon2Dir/BON2-${bon2Version}.jar" String obfFile = "$cacheDir/modules-2/files-2.1/${fileName}.jar" - String deobfFile = "$cacheDir/modules-2/files-2.1/${fileName}-deobf.jar" - - if (file(deobfFile).exists()) { - return files(deobfFile) - } - - String mappingsVer - String remoteMappings = project.hasProperty('remoteMappings') ? project.remoteMappings : 'https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/' - if (remoteMappings) { - String id = "${forgeVersion.split("\\.")[3]}-$minecraftVersion" - String mappingsZIP = "$cacheDir/forge_gradle/maven_downloader/de/oceanlabs/mcp/mcp_snapshot_nodoc/$id/mcp_snapshot_nodoc-${id}.zip" - - zipMappings(mappingsZIP, remoteMappings, bon2Dir) - - mappingsVer = "snapshot_$id" - } else { - mappingsVer = "${channel}_$mappingsVersion" - } - - download.run { - src "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases/com/github/parker8283/BON2/$bon2Version-CUSTOM/BON2-$bon2Version-CUSTOM-all.jar" - dest bon2File - quiet true - overwrite false - } download.run { src sourceURL @@ -1190,50 +1547,8 @@ def deobf(String sourceURL, String rawFileName) { quiet true overwrite false } - - exec { - commandLine 'java', '-jar', bon2File, '--inputJar', obfFile, '--outputJar', deobfFile, '--mcVer', minecraftVersion, '--mappingsVer', mappingsVer, '--notch' - workingDir bon2Dir - standardOutput = new FileOutputStream("${deobfFile}.log") - } - - return files(deobfFile) -} - -def zipMappings(String zipPath, String url, String bon2Dir) { - File zipFile = new File(zipPath) - if (zipFile.exists()) { - return - } - - String fieldsCache = "$bon2Dir/data/fields.csv" - String methodsCache = "$bon2Dir/data/methods.csv" - - download.run { - src "${url}fields.csv" - dest fieldsCache - quiet true - } - download.run { - src "${url}methods.csv" - dest methodsCache - quiet true - } - - zipFile.getParentFile().mkdirs() - ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)) - - zos.putNextEntry(new ZipEntry("fields.csv")) - Files.copy(Paths.get(fieldsCache), zos) - zos.closeEntry() - - zos.putNextEntry(new ZipEntry("methods.csv")) - Files.copy(Paths.get(methodsCache), zos) - zos.closeEntry() - - zos.close() + return dependencies.rfg.deobf(files(obfFile)) } - // Helper methods def checkPropertyExists(String propertyName) { @@ -1260,3 +1575,17 @@ def getSecondaryArtifacts() { if (apiPackage) secondaryArtifacts += [apiJar] return secondaryArtifacts } + +// For easier scripting of things that require variables defined earlier in the buildscript +if (file('addon.late.gradle.kts').exists()) { + apply from: 'addon.late.gradle.kts' +} else if (file('addon.late.gradle').exists()) { + apply from: 'addon.late.gradle' +} + +// File for local tweaks not commited to Git +if (file('addon.late.local.gradle.kts').exists()) { + apply from: 'addon.late.local.gradle.kts' +} else if (file('addon.late.local.gradle').exists()) { + apply from: 'addon.late.local.gradle' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7..c1962a7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33..17a8ddc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java index 1866329..8714aa0 100644 --- a/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java +++ b/src/main/java/locusway/overloadedarmorbar/ConfigurationHandler.java @@ -1,25 +1,27 @@ package locusway.overloadedarmorbar; import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import net.minecraftforge.common.config.Configuration; import cpw.mods.fml.client.event.ConfigChangedEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import locusway.overloadedarmorbar.overlay.ArmorBarRenderer; public class ConfigurationHandler { public static Configuration config; public static String[] colorValues = new String[] { "#FFFFFF", "#FF5500", "#FFC747", "#27FFE3", "#00FF00", "#7F00FF" }; + public static int[] colorValuesI; public static boolean alwaysShowArmorBar = false; public static boolean showEmptyArmorIcons = false; - public static boolean offset = false; - public static void init(String configDir) { + public ConfigurationHandler(File configDir) { if (config == null) { - File path = new File(configDir + "/" + OverloadedArmorBar.MODID + ".cfg"); - config = new Configuration(path); + config = new Configuration(configDir); loadConfiguration(); } } @@ -32,15 +34,33 @@ private static void loadConfiguration() { Configuration.CATEGORY_GENERAL, new String[] { "#FFFFFF", "#FF5500", "#FFC747", "#27FFE3", "#00FF00", "#7F00FF" }, "Colors must be specified in #RRGGBB format"); - offset = config.getBoolean( - "Override for Armor shift", - Configuration.CATEGORY_GENERAL, - false, - "Set to true if the armor bar display's incorrectly"); showEmptyArmorIcons = config .getBoolean("Show empty armor icons?", Configuration.CATEGORY_GENERAL, false, "Show empty armor icons"); - if (config.hasChanged()) config.save(); + fillColorValuesI(); + + if (config.hasChanged()) { + config.save(); + ArmorBarRenderer.forceUpdate(); + } + } + + private static void fillColorValuesI() { + colorValuesI = new int[colorValues.length]; + for (int i = 0; i < colorValues.length; i++) { + colorValuesI[i] = parseColor(colorValues[i]); + } + } + + private static final Pattern colorPattern = Pattern.compile("^#[0-9A-Fa-f]{6}$"); + + private static int parseColor(String colorValue) { + final Matcher matcher = colorPattern.matcher(colorValue); + if (matcher.matches()) { + return Integer.parseInt(colorValue.substring(1, 7), 16); + } else { + return 0xFFFFFF; + } } @SubscribeEvent @@ -51,4 +71,5 @@ public void onConfigurationChangeEvent(ConfigChangedEvent.OnConfigChangedEvent e public static Configuration getConfig() { return config; } + } diff --git a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java index 9716af8..efe0f06 100644 --- a/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java +++ b/src/main/java/locusway/overloadedarmorbar/OverloadedArmorBar.java @@ -1,10 +1,12 @@ package locusway.overloadedarmorbar; -import locusway.overloadedarmorbar.proxy.CommonProxy; +import net.minecraftforge.common.MinecraftForge; + import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.Mod; -import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.relauncher.Side; +import locusway.overloadedarmorbar.overlay.ArmorBarRenderer; @Mod( modid = OverloadedArmorBar.MODID, @@ -19,20 +21,12 @@ public class OverloadedArmorBar { public static final String VERSION = "GRADLETOKEN_VERSION"; public static final String GUI_FACTORY_CLASS = "locusway.overloadedarmorbar.client.gui.GuiFactory"; - public static org.apache.logging.log4j.Logger logger; - - @SidedProxy( - modId = MODID, - clientSide = "locusway.overloadedarmorbar.proxy.ClientProxy", - serverSide = "locusway.overloadedarmorbar.proxy.CommonProxy") - public static CommonProxy proxy; - @Mod.EventHandler public void preInit(FMLPreInitializationEvent event) { - logger = event.getModLog(); - proxy.registerEvents(); - String configDir = event.getModConfigurationDirectory().toString(); - ConfigurationHandler.init(configDir); - FMLCommonHandler.instance().bus().register(new ConfigurationHandler()); + if (event.getSide() == Side.CLIENT) { + MinecraftForge.EVENT_BUS.register(new ArmorBarRenderer()); + FMLCommonHandler.instance().bus().register(new ConfigurationHandler(event.getSuggestedConfigurationFile())); + } } + } diff --git a/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java b/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java index da446d4..a4593e7 100644 --- a/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java +++ b/src/main/java/locusway/overloadedarmorbar/client/gui/GuiFactory.java @@ -7,6 +7,7 @@ import cpw.mods.fml.client.IModGuiFactory; +@SuppressWarnings("unused") public class GuiFactory implements IModGuiFactory { @Override diff --git a/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java b/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java index 1bb9bbf..3a2e837 100644 --- a/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java +++ b/src/main/java/locusway/overloadedarmorbar/client/gui/ModGUIConfig.java @@ -1,25 +1,24 @@ package locusway.overloadedarmorbar.client.gui; -import static locusway.overloadedarmorbar.ConfigurationHandler.getConfig; - -import locusway.overloadedarmorbar.OverloadedArmorBar; - import net.minecraft.client.gui.GuiScreen; import net.minecraftforge.common.config.ConfigElement; import net.minecraftforge.common.config.Configuration; import cpw.mods.fml.client.config.GuiConfig; +import locusway.overloadedarmorbar.ConfigurationHandler; +import locusway.overloadedarmorbar.OverloadedArmorBar; public class ModGUIConfig extends GuiConfig { public ModGUIConfig(GuiScreen guiScreen) { super( guiScreen, - new ConfigElement(getConfig().getCategory(Configuration.CATEGORY_GENERAL)).getChildElements(), + new ConfigElement<>(ConfigurationHandler.getConfig().getCategory(Configuration.CATEGORY_GENERAL)) + .getChildElements(), OverloadedArmorBar.MODID, false, false, - GuiConfig.getAbridgedConfigPath(getConfig().toString())); + GuiConfig.getAbridgedConfigPath(ConfigurationHandler.getConfig().toString())); } } diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBar.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBar.java deleted file mode 100644 index bbac1d9..0000000 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBar.java +++ /dev/null @@ -1,80 +0,0 @@ -package locusway.overloadedarmorbar.overlay; - -import locusway.overloadedarmorbar.ConfigurationHandler; - -/* - * Class manages the calculations required to determine the correct color(s) to use - */ -public class ArmorBar { - - private static void setArmorIconColor(ArmorIcon icon, String[] colors, int scale, int armorValue) { - int currentScale = scale; - int previousScale = scale - 1; - - // Force last color if we have run out of colors on the list - if (currentScale > colors.length - 1) { - currentScale = colors.length - 1; - } - if (previousScale > colors.length - 1) { - previousScale = colors.length - 1; - } - - // Previous scale is -1 between 0 and 20 points of armor, so reset to 0 for sane value - if (previousScale < 0) { - previousScale = 0; - } - - // Covers 2 (FULL) and 1 (HALF) - Primary Color - if (armorValue >= 1) { - // Should be current tier color - icon.primaryArmorIconColor.setColorFromHex(colors[currentScale]); - } - - // Covers 1 (HALF) - Secondary Color - if (armorValue == 1) { - // Should be previous tier color - icon.secondaryArmorIconColor.setColorFromHex(colors[previousScale]); - } - - if (armorValue == 0) { - // Should be previous tier color - icon.primaryArmorIconColor.setColorFromHex(colors[previousScale]); - } - } - - public static ArmorIcon[] calculateArmorIcons(int playerArmorValue) { - ArmorIcon[] armorIcons = new ArmorIcon[10]; - - // Calculate which color scale to use - int scale = playerArmorValue / 20; - - // Scale the value down for each position - int counter = playerArmorValue - (scale * 20); - - // Handle exact wrap around situation - if (scale > 0 && counter == 0) { - // Show we are maxed out at previous scale - scale -= 1; - counter = 20; - } - - for (int i = 0; i < 10; i++) { - armorIcons[i] = new ArmorIcon(); - setArmorIconColor(armorIcons[i], ConfigurationHandler.colorValues, scale, counter); - if (counter >= 2) { - // We have at least a full icon to show - armorIcons[i].armorIconType = ArmorIcon.Type.FULL; - counter -= 2; - } else if (counter == 1) { - // We have a half icon to show - armorIcons[i].armorIconType = ArmorIcon.Type.HALF; - counter -= 1; - } else { - // Empty icon - armorIcons[i].armorIconType = ArmorIcon.Type.NONE; - } - } - - return armorIcons; - } -} diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java new file mode 100644 index 0000000..704ef8f --- /dev/null +++ b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorBarRenderer.java @@ -0,0 +1,173 @@ +package locusway.overloadedarmorbar.overlay; + +import static net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType.ARMOR; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraftforge.client.GuiIngameForge; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.common.ForgeHooks; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.common.eventhandler.EventPriority; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import locusway.overloadedarmorbar.ConfigurationHandler; + +/** + * Class which handles the render event and hides the vanilla armor bar + */ +public class ArmorBarRenderer extends Gui { + + private static final int UNKNOWN_ARMOR_VALUE = -1; + private static final int ARMOR_ICON_SIZE = 9; + private static final int ARMOR_FIRST_HALF_WIDTH = 5; + private static final int ARMOR_SECOND_HALF_WIDTH = 4; + private static final ArmorIcon[] armorIcons = new ArmorIcon[10]; + private static final Minecraft mc = Minecraft.getMinecraft(); + private static int previousArmorValue = UNKNOWN_ARMOR_VALUE; + + public static void forceUpdate() { + previousArmorValue = UNKNOWN_ARMOR_VALUE; + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onRenderGameOverlayEvent(RenderGameOverlayEvent.Pre event) { + + if (event.type != ARMOR) return; + mc.mcProfiler.startSection("armor"); + GL11.glEnable(GL11.GL_BLEND); + + // uncomment to debug + // final int currentArmorValue = (int) ((System.currentTimeMillis() / 200L) % 140); + final int currentArmorValue = ForgeHooks.getTotalArmorValue(mc.thePlayer); + final int left = event.resolution.getScaledWidth() / 2 - 91; + final int top = event.resolution.getScaledHeight() - GuiIngameForge.left_height; + + // Save some CPU cycles by caching the armor bars to render + if (currentArmorValue != previousArmorValue) { + updateArmorIcons(currentArmorValue); + previousArmorValue = currentArmorValue; + } + + int colorState = 0xFFFFFF; + for (int i = 0; i < armorIcons.length; i++) { + final ArmorIcon icon = armorIcons[i]; + final int xPosition = left + i * 8; + switch (icon.iconType) { + case EMPTY: + colorState = setColor(icon.mainColor, colorState); + drawTexturedModalRect(xPosition, top, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + break; + case FULL: + colorState = setColor(icon.mainColor, colorState); + drawTexturedModalRect(xPosition, top, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); + break; + case HALF: + colorState = setColor(icon.mainColor, colorState); + drawTexturedModalRect(xPosition, top, 25, 9, ARMOR_FIRST_HALF_WIDTH, ARMOR_ICON_SIZE); + colorState = setColor(icon.secondaryColor, colorState); + // Draw the second half as full if the player has more than one bar of armor, empty otherwise + final int textureX = currentArmorValue > 20 ? 39 : 30; + drawTexturedModalRect(xPosition + 5, top, textureX, 9, ARMOR_SECOND_HALF_WIDTH, ARMOR_ICON_SIZE); + break; + case NONE: + default: + break; + } + } + if (colorState != 0xFFFFFF) { + GL11.glColor4f(1F, 1F, 1F, 1F); + } + GuiIngameForge.left_height += 10; + GL11.glDisable(GL11.GL_BLEND); + mc.mcProfiler.endSection(); + event.setCanceled(true); + + } + + private static int setColor(int color, int colorState) { + if (colorState == color) return colorState; + final float red = (float) (color >> 16 & 0xFF) / 255F; + final float green = (float) (color >> 8 & 0xFF) / 255F; + final float blue = (float) (color & 0xFF) / 255F; + GL11.glColor4f(red, green, blue, 1F); + return color; + } + + private static void updateArmorIcons(int armorPoints) { + // Calculate which color scale to use + int scale = armorPoints / 20; + // Scale the value down for each position + int counter = armorPoints - (scale * 20); + // Handle exact wrap around situation + if (scale > 0 && counter == 0) { + // Show we are maxed out at previous scale + scale -= 1; + counter = 20; + } + for (int i = 0; i < armorIcons.length; i++) { + if (armorIcons[i] == null) armorIcons[i] = new ArmorIcon(); + armorIcons[i].updateColors(scale, counter); + if (counter >= 2) { + armorIcons[i].iconType = ArmorIcon.Type.FULL; + counter -= 2; + } else if (counter == 1) { + armorIcons[i].iconType = ArmorIcon.Type.HALF; + counter -= 1; + } else { + if (armorPoints > 20) { + armorIcons[i].iconType = ArmorIcon.Type.FULL; + } else if (ConfigurationHandler.showEmptyArmorIcons && armorPoints > 0 + || ConfigurationHandler.alwaysShowArmorBar) { + armorIcons[i].iconType = ArmorIcon.Type.EMPTY; + } else { + armorIcons[i].iconType = ArmorIcon.Type.NONE; + } + } + } + } + + static class ArmorIcon { + + public Type iconType; + /** The main color of the icon */ + public int mainColor = 0xFFFFFF; + /** When of Type = HALF, this is the color of the right-hand side of the icon */ + public int secondaryColor = 0xFFFFFF; + + public void updateColors(int scale, int armorValue) { + int currentScale = scale; + int previousScale = scale - 1; + // Prevent array out of bounds exception + final int arrayLength = ConfigurationHandler.colorValuesI.length; + currentScale = Math.min(arrayLength - 1, currentScale); + previousScale = Math.min(arrayLength - 1, previousScale); + previousScale = Math.max(0, previousScale); + // Covers 2 (FULL) and 1 (HALF) - Primary Color + if (armorValue >= 1) { + // Icon should be of current tier color + this.mainColor = ConfigurationHandler.colorValuesI[currentScale]; + } + // Covers 1 (HALF) - Secondary Color + if (armorValue == 1) { + // Only right side of icon should be of previous tier color + this.secondaryColor = ConfigurationHandler.colorValuesI[previousScale]; + } + if (armorValue == 0) { + // Icon should be of previous tier color + this.mainColor = ConfigurationHandler.colorValuesI[previousScale]; + } + } + + /** The type of armor icon to render */ + public enum Type { + EMPTY, + FULL, + HALF, + NONE + } + + } + +} diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIcon.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIcon.java deleted file mode 100644 index 1b4752d..0000000 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIcon.java +++ /dev/null @@ -1,33 +0,0 @@ -package locusway.overloadedarmorbar.overlay; - -/* - * Class wraps the information required to draw an individual armor icon - */ -public class ArmorIcon { - - public Type armorIconType; - - /* - * Type = FULL, Type = NONE: The color of the icon Type = HALF: The color of the left-hand side of the icon - */ - public final ArmorIconColor primaryArmorIconColor; - /* - * Type = HALF: The color of the right-hand side of the icon - */ - public final ArmorIconColor secondaryArmorIconColor; - - public ArmorIcon() { - armorIconType = Type.NONE; - primaryArmorIconColor = new ArmorIconColor(); - secondaryArmorIconColor = new ArmorIconColor(); - } - - /* - * The type of armor icon to show. - */ - public enum Type { - NONE, - HALF, - FULL - } -} diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java b/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java deleted file mode 100644 index a4a10bf..0000000 --- a/src/main/java/locusway/overloadedarmorbar/overlay/ArmorIconColor.java +++ /dev/null @@ -1,33 +0,0 @@ -package locusway.overloadedarmorbar.overlay; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/* - * Class representing the color of the armor icon - */ -public class ArmorIconColor { - - public float Red; - public float Blue; - public float Green; - public final float Alpha; - public static final Pattern pattern = Pattern.compile("^#[0-9A-Fa-f]{6}$"); - - public ArmorIconColor() { - Red = Blue = Green = Alpha = 1.0f; - } - - /* - * Convert from #RRGGBB format. If string is not in correct format this function will set the color to black. - */ - public void setColorFromHex(String colorHex) { - // Check the color hex is valid otherwise default to white - Matcher matcher = pattern.matcher(colorHex); - if (matcher.matches()) { - Red = Integer.valueOf(colorHex.substring(1, 3), 16).floatValue() / 255; - Green = Integer.valueOf(colorHex.substring(3, 5), 16).floatValue() / 255; - Blue = Integer.valueOf(colorHex.substring(5, 7), 16).floatValue() / 255; - } - } -} diff --git a/src/main/java/locusway/overloadedarmorbar/overlay/OverlayEventHandler.java b/src/main/java/locusway/overloadedarmorbar/overlay/OverlayEventHandler.java deleted file mode 100644 index be6d79a..0000000 --- a/src/main/java/locusway/overloadedarmorbar/overlay/OverlayEventHandler.java +++ /dev/null @@ -1,158 +0,0 @@ -package locusway.overloadedarmorbar.overlay; - -import static locusway.overloadedarmorbar.ConfigurationHandler.alwaysShowArmorBar; - -import locusway.overloadedarmorbar.ConfigurationHandler; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.ScaledResolution; -import net.minecraft.entity.SharedMonsterAttributes; -import net.minecraft.entity.ai.attributes.IAttributeInstance; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.MathHelper; -import net.minecraftforge.client.event.RenderGameOverlayEvent; -import net.minecraftforge.common.ForgeHooks; - -import org.lwjgl.opengl.GL11; - -import cpw.mods.fml.common.eventhandler.SubscribeEvent; - -/* - * Class which handles the render event and hides the vanilla armor bar - */ -public class OverlayEventHandler { - - public static void drawTexturedModalRect(int x, int y, int textureX, int textureY, int width, int height) { - Minecraft.getMinecraft().ingameGUI.drawTexturedModalRect(x, y, textureX, textureY, width, height); - } - - public static final OverlayEventHandler INSTANCE = new OverlayEventHandler(); - - private OverlayEventHandler() {} - - // Class handles the drawing of the armor bar - private final static int UNKNOWN_ARMOR_VALUE = -1; - private int previousArmorValue = UNKNOWN_ARMOR_VALUE; - private final static int ARMOR_ICON_SIZE = 9; - private final static int ARMOR_SECOND_HALF_ICON_SIZE = 4; - - private final Minecraft mc = Minecraft.getMinecraft(); - private ArmorIcon[] armorIcons; - - @SubscribeEvent(receiveCanceled = true) - public void onRenderGameOverlayEventPre(RenderGameOverlayEvent event) { - if (event.type != RenderGameOverlayEvent.ElementType.ARMOR) return; - ScaledResolution scale = event.resolution; - int scaledWidth = scale.getScaledWidth(); - int scaledHeight = scale.getScaledHeight(); - /* Don't render the vanilla armor bar */ - event.setCanceled(true); - INSTANCE.renderArmorBar(scaledWidth, scaledHeight); - } - - private int calculateArmorValue() { - return ForgeHooks.getTotalArmorValue(mc.thePlayer); - } - - public void renderArmorBar(int screenWidth, int screenHeight) { - EntityPlayer player = mc.thePlayer; - int currentArmorValue = calculateArmorValue(); - int xStart = screenWidth / 2 - 91; - int yStart = screenHeight - 39; - - IAttributeInstance playerHealthAttribute = player.getEntityAttribute(SharedMonsterAttributes.maxHealth); - float playerHealth = (float) playerHealthAttribute.getAttributeValue(); - - // Fake that the player health only goes up to 20 so that it does not make the bar float above the health bar - if (!ConfigurationHandler.offset && playerHealth > 20) playerHealth = 20; - - float absorptionAmount = MathHelper.ceiling_float_int(player.getAbsorptionAmount()); - - // Clamp the absorption value to 20 so that it doesn't make the bar float above the health bar - if (!ConfigurationHandler.offset && absorptionAmount > 20) absorptionAmount = 20; - - int numberOfHealthBars = (int) Math.ceil(playerHealth / 20) + (int) Math.ceil(absorptionAmount / 20); - int i2 = Math.max(10 - (numberOfHealthBars - 2), 3); - int yPosition = yStart - (numberOfHealthBars - 1) * i2 - 10; - - // Save some CPU cycles by only recalculating armor when it changes - if (currentArmorValue != previousArmorValue) { - // Calculate here - armorIcons = ArmorBar.calculateArmorIcons(currentArmorValue); - // Save value for next cycle - previousArmorValue = currentArmorValue; - } - - // Push to avoid lasting changes - GL11.glPushMatrix(); - GL11.glEnable(3042); - - int armorIconCounter = 0; - for (ArmorIcon icon : armorIcons) { - int xPosition = xStart + armorIconCounter * 8; - switch (icon.armorIconType) { - case NONE: - ArmorIconColor color = icon.primaryArmorIconColor; - GL11.glColor4f(color.Red, color.Green, color.Blue, color.Alpha); - if (currentArmorValue > 20) { - // Draw the full icon as we have wrapped - drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); - } else { - if (ConfigurationHandler.showEmptyArmorIcons && (alwaysShowArmorBar || currentArmorValue > 0)) { - // Draw the empty armor icon - drawTexturedModalRect(xPosition, yPosition, 16, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); - } - } - break; - case HALF: - ArmorIconColor firstHalfColor = icon.primaryArmorIconColor; - ArmorIconColor secondHalfColor = icon.secondaryArmorIconColor; - - GL11.glColor4f(firstHalfColor.Red, firstHalfColor.Green, firstHalfColor.Blue, firstHalfColor.Alpha); - drawTexturedModalRect(xPosition, yPosition, 25, 9, 5, ARMOR_ICON_SIZE); - - GL11.glColor4f( - secondHalfColor.Red, - secondHalfColor.Green, - secondHalfColor.Blue, - secondHalfColor.Alpha); - if (currentArmorValue > 20) { - // Draw the second half as full as we have wrapped - drawTexturedModalRect( - xPosition + 5, - yPosition, - 39, - 9, - ARMOR_SECOND_HALF_ICON_SIZE, - ARMOR_ICON_SIZE); - } else { - // Draw the second half as empty - drawTexturedModalRect( - xPosition + 5, - yPosition, - 30, - 9, - ARMOR_SECOND_HALF_ICON_SIZE, - ARMOR_ICON_SIZE); - } - break; - case FULL: - ArmorIconColor fullColor = icon.primaryArmorIconColor; - GL11.glColor4f(fullColor.Red, fullColor.Green, fullColor.Blue, fullColor.Alpha); - drawTexturedModalRect(xPosition, yPosition, 34, 9, ARMOR_ICON_SIZE, ARMOR_ICON_SIZE); - break; - default: - break; - } - armorIconCounter++; - } - // Revert our state back - GL11.glColor4f(1, 1, 1, 1); - GL11.glPopMatrix(); - } - - public void forceUpdate() { - // Setting to unknown value will cause a refresh next render - INSTANCE.previousArmorValue = UNKNOWN_ARMOR_VALUE; - } -} diff --git a/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java b/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java deleted file mode 100644 index 50e2dea..0000000 --- a/src/main/java/locusway/overloadedarmorbar/proxy/ClientProxy.java +++ /dev/null @@ -1,13 +0,0 @@ -package locusway.overloadedarmorbar.proxy; - -import locusway.overloadedarmorbar.overlay.OverlayEventHandler; - -import net.minecraftforge.common.MinecraftForge; - -public class ClientProxy extends CommonProxy { - - @Override - public void registerEvents() { - MinecraftForge.EVENT_BUS.register(OverlayEventHandler.INSTANCE); - } -} diff --git a/src/main/java/locusway/overloadedarmorbar/proxy/CommonProxy.java b/src/main/java/locusway/overloadedarmorbar/proxy/CommonProxy.java deleted file mode 100644 index ddd0df5..0000000 --- a/src/main/java/locusway/overloadedarmorbar/proxy/CommonProxy.java +++ /dev/null @@ -1,6 +0,0 @@ -package locusway.overloadedarmorbar.proxy; - -public class CommonProxy { - - public void registerEvents() {} -}