├── src └── main │ ├── resources │ ├── pack.mcmeta │ ├── mod_logo.png │ └── META-INF │ │ └── mods.toml │ └── java │ └── net │ └── darkhax │ └── examplemod │ └── ExampleMod.java ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── build_number.gradle ├── java.gradle ├── git_changelog.gradle ├── minify_jsons.gradle ├── property_helper.gradle ├── property_loader.gradle ├── version_checker.gradle ├── maven.gradle ├── signing.gradle ├── patreon.gradle ├── dependencies.gradle ├── curseforge.gradle └── forge.gradle ├── .gitignore ├── gradle.properties ├── Jenkinsfile ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "pack_format": 6, 4 | "description": "${modDescription}" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/mod_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkhax-Minecraft/Minecraft-Modding-Template/HEAD/src/main/resources/mod_logo.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Darkhax-Minecraft/Minecraft-Modding-Template/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | .gradle/ 3 | bin/ 4 | build/ 5 | 6 | #Eclipse 7 | *.classpath 8 | *.project 9 | *.prefs 10 | *.launch 11 | 12 | # General 13 | run/ 14 | 15 | # Annotation Processing 16 | /.apt_generated_tests/ 17 | /Users/ 18 | .factorypath -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=net.darkhax.examplemod 2 | version=2.0 3 | 4 | mod_name=ExampleMod 5 | mod_author=Darkhax 6 | mod_id=examplemod 7 | mod_homepage=https://www.curseforge.com/minecraft/mc-mods/examplemod 8 | mod_source=https://github.com/Darkhax/Minecraft-Modding-Template 9 | mod_issues=https://github.com/Darkhax/Minecraft-Modding-Template/issues 10 | mod_description=An example Minecraft mod. 11 | 12 | minecraft_version=1.17.1 13 | forge_version=37.0.83 14 | 15 | # CurseForge properties (gradle/curseforge.gradle) 16 | curse_project=469122 -------------------------------------------------------------------------------- /gradle/build_number.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This module will automatically append the current build number to the end of 3 | the project's version. This build number is read from environmental variables 4 | provided by CI like Jenkins or Travis. If no number can be found the build 5 | number will be set to 0. 6 | */ 7 | def buildNumber = System.getenv('BUILD_NUMBER') ? System.getenv('BUILD_NUMBER') : System.getenv('TRAVIS_BUILD_NUMBER') ? System.getenv('TRAVIS_BUILD_NUMBER') : '0' 8 | project.version = "${project.version}.${buildNumber}".toString() 9 | project.logger.lifecycle("Appending build number to version. Version is now ${project.version}") -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[37,]" 3 | issueTrackerURL="https://github.com/Darkhax/Minecraft-Modding-Template/issues" 4 | license="LGPL v2.1" 5 | logoFile="mod_logo.png" 6 | logoBlur=false 7 | 8 | [[mods]] 9 | modId="examplemod" 10 | updateJSONURL="https://updates.blamejared.com/get?n=examplemod&gv=1.17.1" 11 | version="${file.jarVersion}" 12 | displayName="${modName}" 13 | displayURL="${modHomepage}" 14 | credits="${modCredits}" 15 | authors="${modAuthor}" 16 | description=''' 17 | ${modDescription} 18 | ''' 19 | itemIcon="minecraft:golden_apple" 20 | 21 | [[dependencies.examplemod]] 22 | modId="forge" 23 | mandatory=true 24 | versionRange="[37,]" 25 | ordering="NONE" 26 | side="BOTH" 27 | 28 | [[dependencies.examplemod]] 29 | modId="minecraft" 30 | mandatory=true 31 | versionRange="[1.17.1]" 32 | ordering="NONE" 33 | side="BOTH" -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | pipeline { 4 | 5 | agent any 6 | 7 | tools { 8 | jdk "jdk8u292-b10" 9 | } 10 | 11 | stages { 12 | 13 | stage('Setup') { 14 | 15 | steps { 16 | 17 | echo 'Setup Project' 18 | sh 'chmod +x gradlew' 19 | sh './gradlew clean' 20 | } 21 | } 22 | 23 | stage('Build') { 24 | 25 | steps { 26 | 27 | withCredentials([ 28 | file(credentialsId: 'build_secrets', variable: 'ORG_GRADLE_PROJECT_secretFile'), 29 | file(credentialsId: 'java_keystore', variable: 'ORG_GRADLE_PROJECT_keyStore'), 30 | file(credentialsId: 'gpg_key', variable: 'ORG_GRADLE_PROJECT_pgpKeyRing') 31 | ]) { 32 | 33 | echo 'Building project.' 34 | sh './gradlew build publish curseforge updateVersionTracker postTweet --stacktrace --warn' 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gradle/java.gradle: -------------------------------------------------------------------------------- 1 | // Sets the name of files to ModName-Forge-MCVersion. 2 | archivesBaseName = "${mod_name}-Forge-${minecraft_version}" 3 | 4 | java { 5 | 6 | // Generate a sources JAR. 7 | withSourcesJar() 8 | 9 | // Generate a JavaDoc JAR. 10 | withJavadocJar() 11 | 12 | // Set minimum language version. 13 | toolchain.languageVersion = JavaLanguageVersion.of(16) 14 | } 15 | 16 | javadoc { 17 | 18 | // Supress annoying warnings when generating JavaDoc files. 19 | options.addStringOption('Xdoclint:none', '-quiet') 20 | } 21 | 22 | jar { 23 | manifest { 24 | attributes([ 25 | 'Timestamp': System.currentTimeMillis(), 26 | 'Specification-Title': project.ext.modName, 27 | 'Specification-Vendor': project.ext.modAuthor, 28 | 'Specification-Version': project.version, 29 | 'Implementation-Title': project.ext.modName, 30 | 'Implementation-Version': project.version, 31 | 'Implementation-Vendor' : project.ext.modAuthor, 32 | 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), 33 | 'Built-On-Java': "${System.getProperty('java.vm.version')} (${System.getProperty('java.vm.vendor')})" 34 | ]) 35 | } 36 | } -------------------------------------------------------------------------------- /gradle/git_changelog.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This module will generate a changelog using the Git commit log. The log will 3 | be generated using all known commits since the last run. If no last run is 4 | known the current commit will be used. The changelog output is in markdown 5 | format and will contain the commit message and a link to the commit. 6 | */ 7 | apply from: 'gradle/property_helper.gradle' 8 | 9 | def gitRepo = project.ext.modSource ?: project.findProperty('gitRemote', getExecOutput(['git', 'remote', 'get-url', 'origin'])) 10 | def gitCommit = System.getenv('GIT_COMMIT') ?: getExecOutput(['git', 'log', '-n', '1', '--pretty=tformat:%h' ]) 11 | def gitPrevCommit = System.getenv('GIT_PREVIOUS_COMMIT') 12 | 13 | // If a full range is available use that range. 14 | if (gitCommit && gitPrevCommit) { 15 | 16 | project.ext.modChangelog += getExecOutput(['git', 'log', "--pretty=tformat:- %s [(%h)](${gitRepo}/commit/%h)", '' + gitPrevCommit + '..' + gitCommit]) 17 | project.logger.lifecycle("Appened Git changelog with commit ${gitPrevCommit} to ${gitCommit}.") 18 | } 19 | 20 | // If only one commit is available, use the last commit. 21 | else if (gitCommit) { 22 | 23 | project.ext.modChangelog += getExecOutput(['git', 'log', '' + "--pretty=tformat:- %s [(%h)](${gitRepo}/commit/%h)", '-1', '' + gitCommit]) 24 | project.logger.lifecycle("Appened Git changelog with commit ${gitCommit}.") 25 | } -------------------------------------------------------------------------------- /gradle/minify_jsons.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This module will automatically minify all JSON files as they are copied to the 3 | resulting artefacts. This is done by removing superflous new line and 4 | whitespace characters from the file. The raw source files remain unmodified. 5 | 6 | While the minified JSON files are not as visually appealing this technique can 7 | have a notable reduction in the final JAR size. This impact is most notable in 8 | mods with many recipes or other JSON datapack entries. 9 | 10 | Unminified Example 11 | { 12 | "key": "value" 13 | } 14 | 15 | Minified Example 16 | {"key":"value"} 17 | */ 18 | import groovy.json.JsonSlurper 19 | import groovy.json.JsonOutput 20 | 21 | processResources { 22 | 23 | doLast { 24 | 25 | def jsonMinifyStart = System.currentTimeMillis() 26 | def jsonMinified = 0 27 | def jsonBytesSaved = 0 28 | 29 | fileTree(dir: outputs.files.asPath, include: '**/*.json').each { 30 | 31 | def oldLength = it.length() 32 | it.text = JsonOutput.toJson(new JsonSlurper().parse(it)) 33 | jsonBytesSaved += oldLength - it.length() 34 | jsonMinified++ 35 | } 36 | 37 | project.logger.lifecycle("Minified ${jsonMinified} files. Saved ${jsonBytesSaved} bytes before compression. Took ${System.currentTimeMillis() - jsonMinifyStart}ms.") 38 | } 39 | } -------------------------------------------------------------------------------- /gradle/property_helper.gradle: -------------------------------------------------------------------------------- 1 | def String getString(name, defaultValue, warn = false) { 2 | 3 | return getProperty(name, defaultValue, warn).toString() 4 | } 5 | 6 | def getProperty(name, defaultValue, warn = false) { 7 | 8 | // Ensure it's a string, to handle Gradle Strings. 9 | name = name.toString(); 10 | 11 | if (project.hasProperty(name)) { 12 | 13 | return project.findProperty(name) 14 | } 15 | 16 | if (warn) { 17 | 18 | project.logger.warn("Property ${name} was not found. Defaulting to ${defaultValue}.") 19 | } 20 | 21 | return defaultValue 22 | } 23 | 24 | def getOptionalString(name) { 25 | 26 | return getString(name, '') 27 | } 28 | 29 | def getRequiredString(name) { 30 | 31 | return getRequiredProperty(name).toString() 32 | } 33 | 34 | def getRequiredProperty(name) { 35 | 36 | if (project.hasProperty(name)) { 37 | 38 | return project.findProperty(name) 39 | } 40 | 41 | project.logger.error("The ${name} property is required!") 42 | throw new RuntimeException("The ${name} property is required!") 43 | } 44 | 45 | def getExecOutput(commands) { 46 | 47 | def out = new ByteArrayOutputStream() 48 | 49 | exec { 50 | commandLine commands 51 | standardOutput out 52 | } 53 | 54 | return out.toString().trim(); 55 | } 56 | 57 | def getDefaultBoolean(name, defaultEnabled = true) { 58 | 59 | return project.hasProperty(name) ? project.findProperty(name).toBoolean() : defaultEnabled 60 | } 61 | 62 | ext { 63 | 64 | getDefaultString = this.&getString 65 | getDefaultProperty = this.&getProperty 66 | getRequiredString = this.&getRequiredString 67 | getRequiredProperty = this.&getRequiredProperty 68 | getExecOutput = this.&getExecOutput 69 | getOptionalString = this.&getOptionalString 70 | getDefaultBoolean = this.&getDefaultBoolean 71 | } -------------------------------------------------------------------------------- /gradle/property_loader.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This module can inject build properties from a JSON file. Each property in the 3 | JSON file will be mapped to a build property using the key of that property. 4 | Property keys ending with _comment will be skipped. 5 | 6 | If a secretFile property exists and points to a valid JSON file that file will 7 | be automatically loaded. You can manually load a file using the loadProperties 8 | method. 9 | */ 10 | import groovy.json.JsonSlurper 11 | 12 | // Auto detects a secret file and injects it. 13 | if (project.hasProperty('secretFile')) { 14 | 15 | final def secretsFile = file project.getProperty('secretFile') 16 | 17 | if (secretsFile.exists() && secretsFile.name.endsWith('.json')) { 18 | 19 | loadProperties(secretsFile) 20 | project.logger.lifecycle('Automatically loading properties from the secretFile') 21 | } 22 | } 23 | 24 | // Loads properties using a specified json file. 25 | def loadProperties(propertyFile) { 26 | 27 | if (propertyFile.exists()) { 28 | 29 | propertyFile.withReader { 30 | 31 | Map propMap = new JsonSlurper().parse it 32 | 33 | for (entry in propMap) { 34 | 35 | // Filter entries that use _comment in the key. 36 | if (!entry.key.endsWith('_comment')) { 37 | 38 | project.ext.set(entry.key, entry.value) 39 | } 40 | } 41 | 42 | project.logger.lifecycle('Successfully loaded ' + propMap.size() + ' properties') 43 | propMap.clear() 44 | } 45 | } 46 | 47 | else { 48 | 49 | project.logger.warn('The property file ' + propertyFile.getName() + ' could not be loaded. It does not exist.') 50 | } 51 | } 52 | 53 | // Allows other scripts to use these methods. 54 | ext { 55 | 56 | loadProperties = this.&loadProperties 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This mod provides an example Minecraft mod that can be referenced by other mod developers. Players may also use this mod to ensure that mods are being properly loaded. 2 | 3 | ## Project Goals 4 | 1) Provide build modules and related tools for mod developers. 5 | 2) Document the various tools, apis, plugins, and modules used by mod developers. 6 | 3) Test build tools through continuous integration. 7 | 8 | 9 | ## Player Information 10 | When loaded into a Minecraft environment the example mod will print it's name and version to the console. There are no additional requirements or functionality in game at this time. 11 | 12 | 13 | ## Developer Information 14 | This mod is generated using the example mod template on GitHub. This template contains many modules and plugins which can be used to maximize the visibility and presence of your mod while reducing the amount of work required by the developer. This template is licensed under Creative Commons 0 and may be freely used by anyone to create new Minecraft mods or enhance their existing projects. Credit is appreciated but not required. 15 | 16 | ## Current features include 17 | 18 | Appending build number from CI environment 19 | Generating Sources and Javadoc artefacts 20 | Common Java manifest properties 21 | Autodeploy build artefacts to CurseForge 22 | Post to Discord on CurseForge upload 23 | Generate Forge mod, including mixins, access transformers, and other properties 24 | Generate changelogs from git commit messages 25 | Deploy build artefacts to maven 26 | Minify JSON files at build time 27 | Build time properties from secret environmental variables 28 | Signing build artefacts with PGP signatures 29 | In-game update/version checker 30 | 31 | 32 | ## Future Updates 33 | This project will be updated with additional features, documentation, and optimizations as time permits. The immediate plans include additional documentation, Twitter integration, and Fabric support. 34 | -------------------------------------------------------------------------------- /gradle/version_checker.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This module adds a task that can update the latest version in Jared's update 3 | checker API. This is a private service which requires an API key to use. 4 | 5 | For further information contact Jared: https://twitter.com/jaredlll08 6 | */ 7 | import groovy.json.JsonSlurper 8 | import groovy.json.JsonOutput 9 | 10 | apply from: 'gradle/property_helper.gradle' 11 | 12 | task updateVersionTracker { 13 | 14 | if (!project.hasProperty('versionTrackerAPI') || !project.hasProperty('versionTrackerUsername')) { 15 | 16 | project.logger.warn('Skipping Version Checker update. Authentication is required!') 17 | } 18 | 19 | onlyIf { 20 | 21 | project.hasProperty('versionTrackerAPI') && project.hasProperty('versionTrackerUsername') 22 | } 23 | 24 | doLast { 25 | 26 | def username = getRequiredString('versionTrackerUsername') 27 | def apiKey = getRequiredString('versionTrackerKey') 28 | 29 | // Creates a Map that acts as the Json body of the API request. 30 | def body = [ 31 | 'author': username, 32 | 'projectName': project.ext.modId, 33 | 'gameVersion': project.ext.mcVersion, 34 | 'projectVersion': project.version, 35 | 'homepage': project.ext.modHomepage, 36 | 'uid': apiKey 37 | ] 38 | 39 | project.logger.lifecycle("Version Check: ${project.ext.modId} for ${project.version}") 40 | 41 | // Opens a connection to the version tracker API and writes the payload JSON. 42 | def req = new URL(project.findProperty('versionTrackerAPI')).openConnection() 43 | req.setRequestMethod('POST') 44 | req.setRequestProperty('Content-Type', 'application/json; charset=UTF-8') 45 | req.setRequestProperty('User-Agent', "${project.ext.modName} Tracker Gradle") 46 | req.setDoOutput(true) 47 | req.getOutputStream().write(JsonOutput.toJson(body).getBytes("UTF-8")) 48 | 49 | // For the request to be sent we need to read data from the stream. 50 | project.logger.lifecycle("Version Check: Status ${req.getResponseCode()}") 51 | project.logger.lifecycle("Version Check: Response ${req.getInputStream().getText()}") 52 | } 53 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /gradle/maven.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'gradle/property_helper.gradle' 2 | apply plugin: 'maven-publish' 3 | 4 | def canLoad = true 5 | 6 | if (!project.hasProperty('mavenURL')) { 7 | 8 | canLoad = false; 9 | project.logger.warn('Skipping maven module. mavenURL property not specified.') 10 | } 11 | 12 | if (!(project.hasProperty('group') || project.hasProperty('groupId'))) { 13 | 14 | canLoad = false; 15 | project.logger.warn('Skipping maven module. group property not specified.') 16 | } 17 | 18 | if (canLoad) { 19 | 20 | project.logger.lifecycle('Loading maven module.') 21 | 22 | project.publishing { 23 | 24 | tasks.publish.dependsOn 'build' 25 | 26 | publications { 27 | 28 | mavenJava(MavenPublication) { 29 | 30 | artifactId project.archivesBaseName 31 | 32 | // Base mod archive. 33 | artifact jar 34 | 35 | // Adds the sources as an artifact. 36 | artifact project.sourcesJar { 37 | classifier 'sources' 38 | } 39 | 40 | // Adds the javadocs as an artifact. 41 | artifact project.javadocJar { 42 | classifier 'javadoc' 43 | } 44 | } 45 | } 46 | 47 | repositories { 48 | 49 | maven { 50 | 51 | // Sets maven credentials if they are provided. This is generally 52 | // only used for external/remote uploads. 53 | if (project.hasProperty('mavenUsername') && project.hasProperty('mavenPassword')) { 54 | 55 | credentials { 56 | 57 | username findProperty('mavenUsername') 58 | password findProperty('mavenPassword') 59 | } 60 | } 61 | 62 | url getRequiredString('mavenURL') 63 | } 64 | } 65 | } 66 | } 67 | 68 | def getMavenCoordinateString() { 69 | 70 | return "group: '${project.group}', name: '${project.name}', version: '${project.version}'" 71 | } 72 | 73 | def getMavenCoordinateStringCompact() { 74 | 75 | return "${project.group}:${project.name}:${project.version}" 76 | } 77 | 78 | // Disables Gradle's custom module metadata from being published to maven. The 79 | // metadata includes mapped dependencies which are not reasonably consumable by 80 | // other mod developers. 81 | tasks.withType(GenerateModuleMetadata) { 82 | 83 | enabled = false 84 | } 85 | 86 | ext { 87 | 88 | getMavenCoordinateString = this.&getMavenCoordinateString 89 | getMavenCoordinateStringCompact = this.&getMavenCoordinateStringCompact 90 | } -------------------------------------------------------------------------------- /src/main/java/net/darkhax/examplemod/ExampleMod.java: -------------------------------------------------------------------------------- 1 | package net.darkhax.examplemod; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.Enumeration; 7 | import java.util.jar.JarEntry; 8 | import java.util.jar.JarFile; 9 | 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | 13 | import net.minecraftforge.fml.ModList; 14 | import net.minecraftforge.fml.common.Mod; 15 | import net.minecraftforge.forgespi.language.IModFileInfo; 16 | import net.minecraftforge.forgespi.language.IModInfo; 17 | 18 | @Mod(ExampleMod.MOD_ID) 19 | public class ExampleMod { 20 | 21 | public static final String MOD_ID = "examplemod"; 22 | public static final Logger LOGGER = LogManager.getLogger("Example Mod"); 23 | 24 | public ExampleMod() { 25 | 26 | final IModFileInfo modEntry = ModList.get().getModFileById(MOD_ID); 27 | 28 | for (final IModInfo modInfo : modEntry.getMods()) { 29 | 30 | LOGGER.info("Loaded {} v{} from '{}'", modInfo.getModId(), modInfo.getVersion(), modEntry.getFile().getFilePath()); 31 | 32 | if (!verifyJarSignature(modEntry.getFile().getFilePath().toFile())) { 33 | 34 | LOGGER.error("Could not verify the mod's signature! modId={} version={} path={}", modInfo.getModId(), modInfo.getVersion(), modEntry.getFile().getFilePath()); 35 | } 36 | } 37 | } 38 | 39 | private static boolean verifyJarSignature (File file) { 40 | 41 | // Only JAR file signatures can be verified here. 42 | if (file.exists() && !file.isDirectory()) { 43 | 44 | try (JarFile jar = new JarFile(file)) { 45 | 46 | boolean hasFailed = false; 47 | final Enumeration entries = jar.entries(); 48 | 49 | while (entries.hasMoreElements()) { 50 | 51 | final JarEntry entry = entries.nextElement(); 52 | 53 | try (final InputStream is = jar.getInputStream(entry)) { 54 | 55 | final byte[] buffer = new byte[8192]; 56 | 57 | while (is.read(buffer, 0, buffer.length) != -1) { 58 | 59 | // In Java 8+ we need to read the data if we actually want the code 60 | // signers to be verified. Invalid signatures will throw errors 61 | // when read which are caught. 62 | } 63 | } 64 | 65 | // This exception is raised when the contents of a file do not match the 66 | // expected signature. We don't hard fail right away to allow all 67 | // violations to be logged. 68 | catch (SecurityException e) { 69 | 70 | hasFailed = true; 71 | LOGGER.catching(e); 72 | } 73 | } 74 | 75 | return !hasFailed; 76 | } 77 | 78 | catch (final IOException e) { 79 | 80 | e.printStackTrace(); 81 | return false; 82 | } 83 | } 84 | 85 | return false; 86 | } 87 | } -------------------------------------------------------------------------------- /gradle/signing.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This module will load Gradle's built in signing plugin. When the required 3 | properties are present the plugin will use PGP to sign all published 4 | artefacts and produce detached armoured ASCII signature files (ASC). These 5 | files can be used to verify the authenticity and integrity of the published 6 | artefacts. This is primarily used by tools accessing a Maven. 7 | 8 | The required properties are as follows 9 | 10 | | Name | Type | Description | Example | 11 | |---------------------------|--------|------------------------------------------------|--------------| 12 | | signing.secretKeyRingFile | File | A container file for holding PGP keys. | *.gpg | 13 | | signing.keyId | String | The last 8 characters of the specific key ID. | 39280BAE | 14 | | signing.password | String | The password used when generated the keys. | 8r+v!$*uaR4K | 15 | 16 | Generating the key ring and signing key can be done from the command line 17 | using GPG. This can be done using two commands. 18 | 19 | 1) gpg --no-default-keyring --keyring ./mod_signing.gpg --full-generate-key 20 | 21 | This command will generate a new keyring file in the working directory. 22 | It will then prompt you to generate a new key. My personal recomendation is 23 | an "RSA and RSA" key type with 4096 bits. I also recommend no expiration 24 | date for Minecraft mods. The password used when generating the key is used 25 | as the value for signing.password. 26 | 27 | Once you have generated the key, make sure to copy down the public key ID. 28 | This can be found under the pub section at the end of the command output. 29 | With the recommended settings this will be a 40 char hex string. The last 30 | eight characters of this ID is the value for signing.keyId. 31 | 32 | 2) gpg --no-default-keyring --keyring ./mod_signing.gpg --export-secret-keys -o mod_key_ring.gpg 33 | 34 | This command will export the keys from the keyring file into a new keyring 35 | file that Gradle can read. The newly created keyring file will be used for 36 | the value of signing.secretKeyRingFile. 37 | */ 38 | 39 | def canLoad = true 40 | 41 | // 42 | if (!project.hasProperty('signing.secretKeyRingFile') && project.hasProperty('pgpKeyRing')) { 43 | 44 | final def keyRing = file project.getProperty('pgpKeyRing') 45 | 46 | if (keyRing.exists() && keyRing.name.endsWith('.gpg')) { 47 | 48 | project.ext.set('signing.secretKeyRingFile', keyRing.getAbsolutePath()) 49 | project.logger.lifecycle('Loaded PGP keyring from fallback property.') 50 | } 51 | 52 | else { 53 | 54 | project.logger.warn('Failed to load PGP keyring from pgpKeyRing fallback property.') 55 | } 56 | } 57 | 58 | if (!project.hasProperty('signing.secretKeyRingFile')) { 59 | 60 | project.logger.warn('Skipping PGP signing. No signing.secretKeyRingFile provided.') 61 | canLoad = false 62 | } 63 | 64 | if (!project.hasProperty('signing.keyId')) { 65 | 66 | project.logger.warn('Skipping PGP signing. No signing.keyId provided.') 67 | canLoad = false 68 | } 69 | 70 | if (!project.hasProperty('signing.password')) { 71 | 72 | project.logger.warn('Skipping PGP signing. No signing.password provided.') 73 | canLoad = false 74 | } 75 | 76 | if (canLoad) { 77 | 78 | apply plugin: 'signing' 79 | 80 | signing { 81 | 82 | project.logger.lifecycle('Artefacts will be signed using PGP.') 83 | sign publishing.publications 84 | } 85 | } -------------------------------------------------------------------------------- /gradle/patreon.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This gradle module allows your build script to pull in information about who has pledged to your campaign. 3 | */ 4 | import groovy.json.JsonSlurper 5 | 6 | if (project.hasProperty('patreon_campaign_id') && findProperty('patreon_auth_token')) { 7 | 8 | def defaultCampaign = project.findProperty('patreon_campaign_id') 9 | def defaultAuth = project.findProperty('patreon_auth_token') 10 | 11 | project.ext.patreon = [ 12 | campaign: defaultCampaign, 13 | pledgeLog: getPledgeLog(defaultCampaign, defaultAuth), 14 | pledges: getPledges(defaultCampaign, defaultAuth) 15 | ] 16 | 17 | if (project.hasProperty('patreon_campaign_url')) { 18 | 19 | project.ext.patreon.campaignUrl = project.getProperty('patreon_campaign_url') 20 | project.ext.patreon.campaignUrlTracked = "${project.ext.patreon.campaignUrl}?${project.ext.modId}" 21 | } 22 | 23 | project.logger.lifecycle("Loading plede data for default campaign ${defaultCampaign}.") 24 | } 25 | 26 | /* 27 | Gets a list of pledges for a specified campaign using a specified auth token. 28 | */ 29 | def getPledges(campaignId, authToken) { 30 | 31 | // Connect to Patreon's API using the provided auth info. 32 | def connection = new URL('https://www.patreon.com/api/oauth2/api/campaigns/' + campaignId + '/pledges').openConnection() as HttpURLConnection 33 | connection.setRequestProperty('User-Agent', 'Patreon-Groovy, platform ' + System.properties['os.name'] + ' ' + System.properties['os.version']) 34 | connection.setRequestProperty('Authorization', 'Bearer ' + authToken) 35 | connection.setRequestProperty('Accept', 'application/json') 36 | 37 | // Map containing all pledges. If the connection fails this will be empty. 38 | Map pledges = new HashMap() 39 | 40 | // Check if connection was valid. 41 | if (connection.responseCode == 200) { 42 | 43 | // Parse the response into an ambiguous json object. 44 | def json = connection.inputStream.withCloseable {inStream -> new JsonSlurper().parse(inStream as InputStream)} 45 | 46 | // Iterate all the pledge entries 47 | for (pledgeInfo in json.data) { 48 | 49 | // Create new pledge entry, and set pledge specific info. 50 | def pledge = new Pledge() 51 | pledge.id = pledgeInfo.relationships.patron.data.id 52 | pledge.amountInCents = pledgeInfo.attributes.amount_cents 53 | pledge.declined = pledgeInfo.attributes.declined_since 54 | pledges.put(pledge.id, pledge) 55 | } 56 | 57 | // Iterate all the user entries 58 | for (pledgeInfo in json.included) { 59 | 60 | // Get pledge by user ID 61 | def pledge = pledges.get(pledgeInfo.id) 62 | 63 | // If the pledge exists, set the user data. 64 | if (pledge != null) { 65 | 66 | def info = pledgeInfo.attributes; 67 | 68 | pledge.email = info.email 69 | pledge.name = info.full_name 70 | pledge.vanityName = info.vanity 71 | pledge.imgUrl = info.thumb_url 72 | pledge.twitter = info.twitter 73 | pledge.twitchUrl = info.twitch 74 | pledge.youtubeUrl = info.youtube 75 | } 76 | } 77 | } 78 | 79 | return pledges; 80 | } 81 | 82 | /* 83 | Gets a list of pledge names for the specified campaign, using the specified auth token. 84 | */ 85 | def getPledgeLog(campaignId, authToken) { 86 | 87 | def pledgeLog = '' 88 | 89 | for (entry in getPledges(campaignId, authToken)) { 90 | 91 | def pledge = entry.value; 92 | 93 | if (pledge.isValid()) { 94 | 95 | pledgeLog += '- ' + pledge.getDisplayName() + '\n' 96 | } 97 | } 98 | 99 | return pledgeLog 100 | } 101 | 102 | class Pledge { 103 | 104 | // The ID for this user in Patreon's system. 105 | def id 106 | 107 | // The amount this user is currently paying in USD cents. 108 | def amountInCents 109 | 110 | // The date they declined. This will be null if they haven't declined. 111 | def declined 112 | 113 | // The email of the user. 114 | def email 115 | 116 | // The full name of the user. 117 | def name 118 | 119 | // The vanity name of the user, like a display name. 120 | def vanityName 121 | 122 | // A url to the users profile image. 123 | def imgUrl 124 | 125 | // The user's twitter handle. 126 | def twitter 127 | 128 | // The user's twitch channel. 129 | def twitchUrl 130 | 131 | // The user's youtube channel. 132 | def youtubeUrl 133 | 134 | /* 135 | Checks if the user is valid, and is paying. 136 | */ 137 | def isValid() { 138 | 139 | return declined == null && amountInCents > 0; 140 | } 141 | 142 | /* 143 | Gets the display name for the user. Defaults to full name if no vanity name is specified by the user. 144 | */ 145 | def getDisplayName() { 146 | 147 | return vanityName != null ? vanityName : name; 148 | } 149 | } 150 | 151 | // Makes these methods accessible to the project using this module. 152 | ext { 153 | getPledges = this.&getPledges 154 | getPledgeLog = this.&getPledgeLog 155 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradle/dependencies.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'gradle/property_helper.gradle' 2 | 3 | repositories { 4 | 5 | // This maven repository hosts files for hundreds of mods. Some notable 6 | // mods include Bookshelf, Botania, BotanyPots, CraftTweaker, DarkUtils, 7 | // GameStages, Patchouli, Psi, and Quark. 8 | maven { 9 | 10 | url 'https://maven.blamejared.com' 11 | } 12 | 13 | // This maven repository hosts files for dozens of mods. Some notable 14 | // mods include JEI, TinkersConstruct, and ChiselAndBits. 15 | maven { 16 | 17 | url 'https://dvs1.progwml6.com/files/maven' 18 | } 19 | 20 | // This maven repository hosts files for mods by TheIllusiveC4. This 21 | // includes Curios, Caelus, and other APIs. 22 | maven { 23 | 24 | url = 'https://maven.theillusivec4.top/' 25 | } 26 | 27 | // This maven repository proxies requests to the CurseForge API. This will 28 | // allow mods without traditional mavens to be used in a dev environment. 29 | // They use the pattern curse.maven:-: for 30 | // maven coordinates. 31 | maven { 32 | 33 | url = 'https://www.cursemaven.com' 34 | 35 | // Improve performance by only querying for their specific group. 36 | content { 37 | 38 | includeGroup 'curse.maven' 39 | } 40 | } 41 | } 42 | 43 | dependencies { 44 | 45 | // This section includes mods that are commonly compiled against. It is 46 | // recommended to add support for these mods if it makes sense for the 47 | // content of your mod. 48 | if (getDefaultBoolean('dependencies_compile_enabled')) { 49 | 50 | } 51 | 52 | // This section includes mods that are commonly used by players and meet a 53 | // minimum level of quality and performance. These mods are only available 54 | // at runtime by default meaning you may not directly access their code. 55 | if (getDefaultBoolean('dependencies_runtime_enabled')) { 56 | 57 | // Replaces the config GUI with a shiny new one. Mods can enable a 58 | // custom menu icon by defining additional properties in the mods.toml 59 | // file of their mod. 60 | deobfRuntime('catalogue', 'curse.maven', 'catalogue-459701', '3399552') 61 | 62 | // Controlling overhauls the keybind menu by adding a search bar and 63 | // other QoL features. 64 | deobfRuntime('controlling', 'com.blamejared.controlling', 'Controlling', '8.0.0') 65 | } 66 | } 67 | 68 | /** 69 | * Creates a deobfuscated compile dependency. These are available at compile 70 | * time and runtime. This means you can reference code from this dependency 71 | * in your mod. 72 | * 73 | * See deobfDep for more info. 74 | */ 75 | def deobfCompile(modid, defaultGroup, defaultName, defaultVersion) { 76 | 77 | deobfDep(modid, 'implementation', defaultGroup, defaultName, defaultVersion) 78 | } 79 | 80 | /** 81 | * Creates a deobfuscated runtime dependency. These are only available during 82 | * runtime and not compile time. This means the dependency will show up when 83 | * you run the game but you can not directly reference it's code in your mod. 84 | * 85 | * See deobfDep for more info. 86 | */ 87 | def deobfRuntime(modid, defaultGroup, defaultName, defaultVersion) { 88 | 89 | deobfDep(modid, 'runtimeOnly', defaultGroup, defaultName, defaultVersion) 90 | } 91 | 92 | /** 93 | * Creates a new deobfuscated project dependency that is configured using 94 | * properties. This allows for greater flexability and additional logging. 95 | * 96 | * | Property | Description | Example | 97 | * |---------------|---------------------------------------------------|-----------------------| 98 | * | modid_enabled | When set to false the dependency will be skipped. | true/false, y/n, 1/0 | 99 | * | modid_deptype | The type of dependency to create. | compile, runtimeOnly | 100 | * | modid_group | The maven group for the dependency. | net.darkhax.bookshelf | 101 | * | modid_name | The name of the dependency. | Bookshelf-1.16.5 | 102 | * | modid_version | The artefact version of the dependency. | 10.0.7 | 103 | * 104 | * @param modid An ID used in logging and the names of properties. 105 | * @param defaultType The default type of dependency to create. 106 | * @param defaultGroup The default group for the maven coordinate. modid_group 107 | * @param defaultName The default name for the maven coordinate. modid_name 108 | * @param defaultVersion the default version for the maven coordinate. modid_version 109 | */ 110 | def deobfDep(modid, defaultType, defaultGroup, defaultName, defaultVersion) { 111 | 112 | if (getDefaultBoolean("${modid}_enabled")) { 113 | 114 | def depType = getDefaultString("${modid}_deptype", defaultType) 115 | def group = getDefaultString("${modid}_group", defaultGroup) 116 | def name = getDefaultString("${modid}_name", defaultName) 117 | def version = getDefaultString("${modid}_version", defaultVersion) 118 | 119 | project.logger.lifecycle("Dependency ${modid} added. ${depType} \'${group}:${name}:${version}\'") 120 | project.getDependencies().add(depType, fg.deobf("${group}:${name}:${version}")) 121 | } 122 | 123 | else { 124 | 125 | project.logger.warn("Dependency ${modid} has been disabled for this build.") 126 | } 127 | } 128 | 129 | // Gradle introduced a new metadata format that will override data from other 130 | // formats like Maven. When publishing Forge mods this metadata will include 131 | // dependency entries for mapped versions of Forge and other mods used in the 132 | // userdev environment. Gradle will try and fail to resolve these dependencies. 133 | // The simplest fix is for those publishing artefacts to disable the publishing 134 | // of this metadata or to strip the mapped dependencies however we can not rely 135 | // on them to do that. This code will tell Gradle to ignore that metadata. 136 | project.repositories.each { 137 | 138 | if (it.hasProperty('metadataSources')) { 139 | 140 | it.metadataSources.ignoreGradleMetadataRedirection() 141 | } 142 | } 143 | 144 | ext { 145 | 146 | deobfDep = this.&deobDep 147 | deobfRuntime = this.&deobRuntime 148 | deobfCompile = this.&deobCompile 149 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /gradle/curseforge.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | This module will upload build artefacts to CurseForge when the required 3 | properties are present and the curseforge task is ran. The upload will include 4 | the mod mod binary as well as sources and javadocs which are included as 5 | additional files. 6 | 7 | The following properties can be used to configure uploading. 8 | 9 | | Name | Required | Type | Description | Example | 10 | |--------------------|----------|--------|-----------------------------------------------------------------------------------|--------------------------------------| 11 | | curse_project | true | String | The project ID to upload to. | 238222 | 12 | | curse_auth | true | String | An API token for CurseForge's API. | 25352f25-02fd-4714-9d8a-9a138f96a3bf | 13 | | curse_release_type | false | String | The release type. Accepts alpha, beta, and release. Defaults to alpha. | beta | 14 | | curse_versions | false | String | A list of game versions separated by ", ". Has limited support for autodetection. | 1.12.2, 1.16.5, forge, Java 14 | 15 | | curse_requirements | false | String | A list of required projects by ID separated by ", ". | 238222, 324333 | 16 | | curse_optionals | false | String | A list of optional projects by ID separated by ", ". | 238222, 324333 | 17 | 18 | Additionally this module can post a notification in Discord using channel 19 | webhooks. This message will include the name of the mod, the version of the 20 | release, and the game version. 21 | 22 | If curse_page is specified, or mod_homepage is a valid CurseForge project page 23 | a download link to the file will be included in the changelog. 24 | 25 | If a changelog exists via project.ext.changelog the first 240 characters will 26 | also be included in the message. 27 | 28 | | Name | Required | Type | Description | Example | 29 | |-----------------------|----------|--------|----------------------------------------------------------|---------------------------------------------------------| 30 | | curse_project | true | String | The project ID to upload to. | 238222 | 31 | | curse_discord_webhook | true | String | The webhook URL for the channel to post in. | https://discordapp.com/api/webhooks/***** | 32 | | curse_page | false | String | A link to the project on Curse, used for download links. | https://www.curseforge.com/minecraft/mc-mods/examplemod | 33 | */ 34 | buildscript { 35 | 36 | repositories { 37 | 38 | mavenCentral() 39 | maven { 40 | 41 | url 'https://plugins.gradle.org/m2/' 42 | } 43 | } 44 | 45 | dependencies { 46 | 47 | classpath group: 'gradle.plugin.com.matthewprenger', name: 'CurseGradle', version: '1.4.0' 48 | classpath group: 'com.diluv.schoomp', name: 'Schoomp', version: '1.1.0' 49 | classpath group: 'net.darkhax.tweedle', name: 'Tweedle', version: '1.0.5' 50 | } 51 | } 52 | 53 | import com.diluv.schoomp.Webhook 54 | import com.diluv.schoomp.message.Message 55 | import com.diluv.schoomp.message.embed.Embed 56 | import com.diluv.schoomp.message.embed.Footer 57 | 58 | apply from: 'gradle/property_helper.gradle' 59 | apply plugin: com.matthewprenger.cursegradle.CurseGradlePlugin 60 | 61 | def projectPage = project.findProperty('curse_page') 62 | 63 | if (!projectPage && project.ext.modHomepage && project.ext.modHomepage.startsWith('https://www.curseforge.com/minecraft/mc-mods/')) { 64 | 65 | projectPage = project.ext.modHomepage 66 | } 67 | 68 | curseforge { 69 | 70 | if (project.hasProperty('curse_project') && project.hasProperty('curse_auth')) { 71 | 72 | apiKey = findProperty('curse_auth') 73 | 74 | project { 75 | 76 | id = "${curse_project}" 77 | releaseType = getDefaultString('curse_release_type', 'alpha') 78 | changelog = project.ext.modChangelog 79 | 80 | if (project.ext.patreon && project.ext.patreon.pledgeLog) { 81 | 82 | def patreonText = project.ext.patreon.campaignUrlTracked ? "[Patreon](${project.ext.patreon.campaignUrlTracked})" : 'Patreon' 83 | changelog += "\n\nThis mod was made possible by ${patreonText} support from players like you. Thank you!\n\n${project.ext.patreon.pledgeLog}" 84 | } 85 | 86 | changelogType = 'markdown' 87 | 88 | if (project.hasProperty('curse_versions')) { 89 | 90 | for (String version : getOptionalString('curse_versions').split(', ')) { 91 | 92 | addGameVersion version 93 | } 94 | } 95 | 96 | else { 97 | 98 | addGameVersion project.ext.mcVersion 99 | addGameVersion 'Forge' 100 | addGameVersion 'Java 16' 101 | } 102 | 103 | mainArtifact(jar) { 104 | 105 | if (project.hasProperty('curse_requirements') || project.hasProperty('curse_optionals')) { 106 | 107 | relations { 108 | 109 | if (project.hasProperty('curse_requirements')) { 110 | 111 | for (String req : project.findProperty('curse_requirements').split(', ')) { 112 | 113 | requiredLibrary req 114 | } 115 | } 116 | 117 | if (project.hasProperty('curse_optionals')) { 118 | 119 | for (String req : project.findProperty('curse_optionals').split(', ')) { 120 | 121 | optionalLibrary req 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | else { 131 | 132 | project.logger.lifecycle('Skipping CurseForge module. No auth info provided.') 133 | } 134 | } 135 | 136 | if (project.hasProperty('curse_discord_webhook')) { 137 | 138 | tasks.getByName("curseforge").doLast { 139 | 140 | try { 141 | 142 | // Reads the file ID given to us by CurseForge 143 | def newFileId = tasks.getByName("curseforge${curse_project}").property('mainArtifact').fileID 144 | 145 | // Create a new webhook instance for Discord 146 | def webhook = new Webhook(findProperty('curse_discord_webhook'), "${project.ext.modName} CurseForge Gradle Upload") 147 | 148 | // Craft a message to send to Discord using the webhook. 149 | def message = new Message() 150 | message.setUsername(project.ext.modName) 151 | message.setContent("${project.ext.modName} ${project.version} for Minecraft ${project.ext.mcVersion} has been published!") 152 | 153 | def embed = new Embed(); 154 | 155 | // Add Curseforge DL link if available. 156 | if (projectPage) { 157 | 158 | embed.addField('Download', "${projectPage}/files/${newFileId}", false) 159 | } 160 | 161 | // Add a changelog field if a changelog exists. 162 | if (project.ext.modChangelog && !project.ext.modChangelog.isEmpty()) { 163 | 164 | // Only include the first 240 chars of the changelog to 165 | // avoid their size limits. 166 | embed.addField('Changelog', project.ext.modChangelog.take(500), false) 167 | } 168 | 169 | embed.setColor(0xFF8000) 170 | message.addEmbed(embed) 171 | 172 | webhook.sendMessage(message) 173 | } 174 | 175 | catch (IOException e) { 176 | 177 | project.logger.error('Failed to push CF Discord webhook.') 178 | } 179 | } 180 | } 181 | 182 | def canTwitter = project.hasProperty('twitter_api_key') && project.hasProperty('twitter_api_key_secret') && project.hasProperty('twitter_access_token') && project.hasProperty('twitter_access_token_secret') 183 | 184 | task postTweet (type: net.darkhax.tweedle.TaskPostTweet){ 185 | 186 | onlyIf { 187 | 188 | canTwitter 189 | } 190 | 191 | if (canTwitter) { 192 | 193 | addClient(project.findProperty('twitter_api_key'), project.findProperty('twitter_api_key_secret'), project.findProperty('twitter_access_token'), project.findProperty('twitter_access_token_secret')) 194 | } 195 | 196 | doLast { 197 | 198 | // Reads the file ID given to us by CurseForge 199 | def newFileId = tasks.getByName("curseforge${curse_project}").property('mainArtifact').fileID 200 | def tweetBody = "${project.ext.modName} ${project.version} for Minecraft ${project.ext.mcVersion} has been released. #${project.ext.modName.toLowerCase().replaceAll("[^a-zA-Z]", "")}" 201 | 202 | if (projectPage && newFileId) { 203 | 204 | tweetBody += " ${projectPage}/files/${newFileId}" 205 | } 206 | 207 | sendTweet(tweetBody) 208 | } 209 | } 210 | 211 | if (!canTwitter) { 212 | 213 | project.logger.warn("Skipping Twitter plugin. APIKey: ${project.hasProperty('twitter_api_key')} ${project.hasProperty('twitter_api_key_secret')} AccessToken: ${project.hasProperty('twitter_access_token')} ${project.hasProperty('twitter_access_token_secret')}") 214 | } -------------------------------------------------------------------------------- /gradle/forge.gradle: -------------------------------------------------------------------------------- 1 | project.logger.lifecycle("Loading Forge project module. Using Mixins: ${project.hasProperty('mixin_id')}") 2 | 3 | buildscript { 4 | 5 | repositories { 6 | 7 | maven { 8 | 9 | name 'Official Forge Maven' 10 | url 'https://maven.minecraftforge.net' 11 | } 12 | 13 | maven { 14 | 15 | name 'Sponge / Mixin Maven' 16 | url 'https://repo.spongepowered.org/repository/maven-public' 17 | } 18 | 19 | maven { 20 | 21 | name 'Official Parchment Maven' 22 | url = 'https://maven.parchmentmc.org' 23 | } 24 | 25 | mavenCentral() 26 | } 27 | 28 | dependencies { 29 | 30 | classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true 31 | classpath group: 'org.parchmentmc', name: 'librarian', version: '1+' 32 | classpath group: 'org.spongepowered', name: 'mixingradle', version: '0.7-SNAPSHOT' 33 | } 34 | } 35 | 36 | apply plugin: net.minecraftforge.gradle.userdev.UserDevPlugin 37 | apply from: 'gradle/property_helper.gradle' 38 | 39 | // Configurable Forge Properties 40 | def forgeVersion = getRequiredString('forge_version') 41 | def clientRunDir = getDefaultString('forge_client_dir', 'run') 42 | def serverRunDir = getDefaultString('forge_server_dir', 'run') 43 | def loggingLevel = getDefaultString('forge_log_level', 'debug') 44 | def loggingANSI = getDefaultProperty('forge_log_ansi', true).asBoolean() 45 | def mappingChannel = getDefaultString('forge_mapping_channel', 'official') 46 | def mappingVersion = getDefaultString('forge_mapping_version', project.ext.mcVersion) 47 | 48 | // Mixin Options 49 | def remapRefmap = getDefaultBoolean('mixin_remap_refmap') 50 | def remapRefmapFile = getDefaultString('mixin_remap_refmap_file', "${projectDir}/build/createSrgToMcp/output.srg") 51 | 52 | // Parchment & Librarian Options 53 | if (getDefaultBoolean('parchment_enabled')) { 54 | 55 | def parchmentVersion = getDefaultString('parchment_mapping_version', '2021.09.05-1.17.1') 56 | 57 | project.logger.lifecycle("Running with Parchment enabled. forge_mapping_channel is now 'parchment'. forge_mapping_version is now '${parchmentVersion}'") 58 | 59 | apply plugin: org.parchmentmc.librarian.forgegradle.LibrarianForgeGradlePlugin 60 | mappingChannel = 'parchment' 61 | mappingVersion = parchmentVersion 62 | } 63 | 64 | // Makes sure jar files are reobfuscated by Forge when built. 65 | jar.finalizedBy('reobfJar') 66 | 67 | minecraft { 68 | 69 | // Loads mojmaps for the current MC version. 70 | mappings channel: mappingChannel, version: mappingVersion 71 | 72 | // Loads access transofrmers if the user has specified them. 73 | if (project.hasProperty('forge_at')) { 74 | 75 | accessTransformer = file(project.findProperty('forge_at')) 76 | } 77 | 78 | runs { 79 | 80 | client { 81 | 82 | // Sets the folder the client runs in. 83 | workingDirectory project.file(clientRunDir) 84 | 85 | // Sets the name of the run to include the mod name. 86 | taskName modName.replaceAll("\\s","") + 'Client' 87 | 88 | // Sets Forge's logging level on the client. 89 | property 'forge.logging.console.level', loggingLevel 90 | 91 | // Tells mixin to remap refmap files to the values used in the 92 | // development environment. This allows mods with mixins to be 93 | // used as devtime dependencies. 94 | if (remapRefmap) { 95 | 96 | property 'mixin.env.remapRefMap', 'true' 97 | property 'mixin.env.refMapRemappingFile', remapRefmapFile 98 | } 99 | 100 | // Enables ANSI color codes in the logs. 101 | if (loggingANSI) { 102 | 103 | property 'terminal.ansi', 'true' 104 | } 105 | 106 | // Enable mixins on the client. 107 | if (project.hasProperty('mixin_id')) { 108 | 109 | arg "-mixin.config=" + project.findProperty('mixin_id') + ".mixins.json" 110 | } 111 | 112 | mods { 113 | 114 | clientRun { 115 | 116 | source sourceSets.main 117 | } 118 | } 119 | } 120 | 121 | server { 122 | 123 | // Sets the folder the server runs in. 124 | workingDirectory project.file(serverRunDir) 125 | 126 | // Sets the name of the run to include the mod name. 127 | taskName modName.replaceAll("\\s","") + 'Server' 128 | 129 | // Sets Forge's logging level on the server. 130 | property 'forge.logging.console.level', loggingLevel 131 | 132 | // Tells mixin to remap refmap files to the values used in the 133 | // development environment. This allows mods with mixins to be 134 | // used as devtime dependencies. 135 | if (remapRefmap) { 136 | 137 | property 'mixin.env.remapRefMap', 'true' 138 | property 'mixin.env.refMapRemappingFile', remapRefmapFile 139 | } 140 | 141 | // Enables ANSI color codes in the logs. 142 | if (loggingANSI) { 143 | 144 | property 'terminal.ansi', 'true' 145 | } 146 | 147 | // Enable mixins on the server. 148 | if (project.hasProperty('mixin_id')) { 149 | 150 | arg "-mixin.config=" + project.findProperty('mixin_id') + ".mixins.json" 151 | } 152 | 153 | mods { 154 | 155 | serverRun { 156 | 157 | source sourceSets.main 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | dependencies { 165 | 166 | // Sets Minecraft and Forge as a dependency for the project. 167 | minecraft "net.minecraftforge:forge:${project.ext.mcVersion}-${forgeVersion}" 168 | 169 | // Loads mixin dependencies when needed. 170 | if (project.hasProperty('mixin_id')) { 171 | 172 | // Gradle 5.0+ no longer auto-loads annotation processors. Mixin relies 173 | // on these to generate the refmap file. 174 | annotationProcessor 'org.spongepowered:mixin:0.8:processor' 175 | } 176 | } 177 | 178 | processResources { 179 | 180 | def buildProps = project.properties.clone() 181 | 182 | // When patreon data is available add credits and supporter info. 183 | if (project.ext.has('patreon') && project.ext.patreon.pledges && !project.ext.patreon.pledges.isEmpty()) { 184 | 185 | def supporters = new ArrayList() 186 | 187 | for (entry in project.ext.patreon.pledges) { 188 | 189 | def pledge = entry.getValue(); 190 | 191 | if (pledge.isValid()) { 192 | 193 | supporters.add(pledge.getDisplayName()) 194 | } 195 | } 196 | 197 | buildProps.put('modCredits', "${project.ext.modCredits} and the supporters on Patreon: ${supporters.join(', ')}") 198 | } 199 | 200 | // Replaces FML's magic file.jarVersion string with the correct version at 201 | // build time. 202 | buildProps.put('file', [jarVersion: project.version]) 203 | 204 | filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { 205 | 206 | expand buildProps 207 | } 208 | } 209 | 210 | jar { 211 | 212 | manifest { 213 | 214 | def newProps = [:] 215 | 216 | // Adds build time info to the manifest. This has no intended runtime 217 | // functionality. 218 | newProps['ModLoader'] = "forge-${forgeVersion}" 219 | newProps['Built-On-Minecraft'] = project.ext.mcVersion 220 | newProps['Built-On-Mapping'] = "${mappingChannel}-${mappingVersion}" 221 | 222 | // Adds the mixin config to the manifest when mixins are used. This is 223 | // required for your mixins to run. 224 | if (project.hasProperty('mixin_id')) { 225 | 226 | newProps['MixinConfigs'] = "${mixin_id}.mixins.json" 227 | } 228 | 229 | attributes(newProps) 230 | } 231 | } 232 | 233 | if (project.hasProperty('mixin_id')) { 234 | 235 | apply plugin: org.spongepowered.asm.gradle.plugins.MixinGradlePlugin 236 | 237 | mixin { 238 | 239 | // Tells the mixin plugin where to put the generated refmap file. 240 | add sourceSets.main, "${mixin_id}.refmap.json" 241 | } 242 | } 243 | 244 | // Forge normally generates this task, but by giving the run config a unique 245 | // name for multi-project workspaces we also change the name of the generated 246 | // task. Some 3rd party tools depend on these tasks existing so we recreate 247 | // them here. 248 | task runClient() { 249 | 250 | description = 'Runs the game client in developer/debug mode.' 251 | dependsOn modName.replaceAll("\\s","") + 'Client' 252 | } 253 | 254 | task runServer() { 255 | 256 | description = 'Runs the game server in developer/debug mode.' 257 | dependsOn modName.replaceAll("\\s","") + 'Server' 258 | } 259 | 260 | // Forge's Jar Signer 261 | def canSignJar = project.hasProperty('keyStore') && project.hasProperty('keyStorePass') && project.hasProperty('keyStoreKeyPass') && project.hasProperty('keyStoreAlias') 262 | 263 | task signJar(type: net.minecraftforge.gradle.common.tasks.SignJar, dependsOn: jar) { 264 | 265 | onlyIf { 266 | 267 | canSignJar 268 | } 269 | 270 | if (canSignJar) { 271 | 272 | keyStore = project.findProperty('keyStore') 273 | alias = project.findProperty('keyStoreAlias') 274 | storePass = project.findProperty('keyStorePass') 275 | keyPass = project.findProperty('keyStoreKeyPass') 276 | inputFile = jar.archivePath 277 | outputFile = jar.archivePath 278 | 279 | build.dependsOn signJar 280 | } 281 | 282 | else { 283 | 284 | project.logger.warn('Jar signing is disabled for this build. One or more keyStore properties are not specified.') 285 | } 286 | } 287 | 288 | task setupDebugRunConfigs() { 289 | 290 | description = 'Creates basic configs used to debug mods in game' 291 | 292 | dependsOn 'configServerProps' 293 | dependsOn 'configEula' 294 | } 295 | 296 | task configServerProps(type: WriteProperties) { 297 | 298 | description = 'Configures the server with default properties. Eg. Disables online mode.' 299 | 300 | outputFile = file("${serverRunDir}/server.properties") 301 | 302 | property('online-mode', getDefaultString('server_online_mode', 'false')) 303 | property('motd', getDefaultString('server_motd', "Testing server for ${project.modName}")) 304 | property('server-ip', getDefaultString('server_ip', '')) 305 | property('server-port', getDefaultString('server_port', '25565')) 306 | property('snooper-enabled', getDefaultString('server_snooper_enabled', 'false')) 307 | property('spawn-protection', getDefaultString('server_spawn_protection', '0')) 308 | property('enable-command-block', getDefaultString('enable_command_block', 'true')) 309 | 310 | property('level-name', getDefaultString('level_name', project.modName)) 311 | property('level-seed', getDefaultString('level_seed', project.modName)) 312 | property('level-type', getDefaultString('level_type', 'default')) 313 | } 314 | 315 | task configEula(type: WriteProperties) { 316 | 317 | description = 'Automatically creates and agrees to the Mojang account EULA.' 318 | 319 | outputFile = file("${serverRunDir}/eula.txt") 320 | 321 | property 'eula', true 322 | 323 | doLast { 324 | 325 | project.logger.warn("You have agreed to Mojang's EULA! https://account.mojang.com/documents/minecraft_eula"); 326 | } 327 | } --------------------------------------------------------------------------------