diff --git a/.gitignore b/.gitignore index 7b2942d..de8db43 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ # User data local.properties +zdoc.version # Temporary .gitignored +serialize.lua # Libraries /lib/ diff --git a/README.md b/README.md index a90aba1..248b93c 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ ZomboidMod serves as an umbrella for these tools, connecting everything you need - Decompiles and packages game classes to expose game engine code. - Uses [ZomboidDoc](https://github.com/yooksi/pz-zdoc/) to compile a readable and always up-to-date modding Lua library. - Compact workspace that can be used straight out of the box. +- Fully automated project changelog generation. +- Create mod distributions with a click of a button. - Easy installation steps for enabling advanced features. - Comes pre-configured for use with Git. - Fully integrates with IntelliJ IDEA. @@ -128,6 +130,45 @@ You can rerun the task at any time if you accidentally delete the configuration ## How to use +### Search scopes + +IDEA [scopes](https://www.jetbrains.com/help/idea/settings-scopes.html) are sets of files you can search in various contexts. ZomboidMod generates custom search scopes to help you find code usages or references helpful in modding the game: + +- `mod-lua` - All Lua files in `media` directory. +- `mod-media` - All files in `media` directory. +- `pz-java` - Project Zomboid Java classes. +- `pz-lua` - Project Zomboid Lua classes. + +Learn more about [searching everywhere](https://www.jetbrains.com/help/idea/searching-everywhere.html) in IntelliJ IDEA. + +### Changelog + +> If you are not familiar with what a changelog is I recommend reading [keep a changelog](https://keepachangelog.com/en/1.0.0/). + +ZomboidMod uses [github-changelog-generator](https://github.com/github-changelog-generator/github-changelog-generator) to generate standardized changelogs. Your should generate a changelog after each release, when all issues on project Github repository have been closed with a merge commit. + +Before generating a changelog you need to do the following: + +- Make sure you have designated repository `url` in `mod.info` file. + +- Generate a [Github token](https://github.com/github-changelog-generator/github-changelog-generator#github-token) and store it in `local.properties`: + + ```properties + cg.token= + ``` + +That simply run `generateChangelog` task to generate project changelog. + +### Distribution + +Before others can download your mod you need to assemble and upload the mod distribution. + +Assembling distributions is a process of packaging everything your mod needs to run in production environment in compressed archives. Anything not needed in production environment (such as gradle files) needs to be excluded from distributions. + +ZomboidMod handles this for you. Just run `assembleModDist` and a distribution archive matching the current project version will be created in `build/distributions` directory. + +## List of tasks + ### Setup tasks - `createLaunchRunConfigs` - Create game launch run configurations. @@ -156,6 +197,7 @@ You can rerun the task at any time if you accidentally delete the configuration - `annotateZomboidLua` - Annotate vanilla Lua with EmmyLua. - `compileZomboidLua` - Compile Lua library from modding API. +- `updateZomboidLua` - Run ZomboidDoc to update compiled Lua library. - `decompileZomboid` - Decompile Project Zomboid classes. - `zomboidClasses` - Assembles Project Zomboid classes. - `zomboidJar` - Assembles a jar archive containing game classes. diff --git a/build.gradle b/build.gradle index 61b5c68..96f8915 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'distribution' } repositories { - jcenter() + mavenCentral() } java { diff --git a/dist/.gitignore b/dist/.gitignore index 92a87ab..b527179 100644 --- a/dist/.gitignore +++ b/dist/.gitignore @@ -1,5 +1,9 @@ # User data local.properties +zdoc.version + +# Temporary +serialize.lua # Libraries /lib/ diff --git a/dist/distribution.gradle b/dist/distribution.gradle index 767ec0a..ed3b78c 100644 --- a/dist/distribution.gradle +++ b/dist/distribution.gradle @@ -1,10 +1,6 @@ import java.nio.file.Files import java.nio.file.Paths -// set repository owner and name -project.ext.set('repo.owner', 'cocolabs') -project.ext.set('repo.name', 'pz-zmod') - // changelog.gradle apply from: 'https://git.io/JqJiC' diff --git a/mod.gradle b/mod.gradle index 6ffc0a4..5abb2c2 100644 --- a/mod.gradle +++ b/mod.gradle @@ -36,7 +36,9 @@ void validateModInfo() { else if (key == 'url') { try { - new URL(property) + if (new URL(property).host != 'github.com') { + throw new InvalidUserDataException("Mod URL \'${property}\' is not a valid Github repo URL") + } } catch (MalformedURLException e) { throw new InvalidUserDataException("Mod URL \'${property}\' is not a valid URL", e) @@ -109,7 +111,7 @@ tasks.register('initModInfo') { ant.input(message: 'Enter mod description: \n', addproperty: 'mod.description') modInfoProperties.setProperty('description', ant.properties.get('mod.description') as String) - ant.input(message: 'Enter mod repo/workshop URL: \n', addproperty: 'mod.url') + ant.input(message: 'Enter github repository URL: \n', addproperty: 'mod.url') modInfoProperties.setProperty('url', ant.properties.get('mod.url') as String) // starting mod version is 0.1.0 (sem-ver) @@ -134,6 +136,18 @@ task loadModInfo { logger.info("Loading property ${it.key}:${it.value}") project.ext.set("mod.${(it.key as String)}", it.value as String) }) + def sUrl = modInfoProperties.getProperty('url') + if (sUrl && !sUrl.isBlank()) + { + def url = new URL(sUrl) + def urlPath = url.path.startsWith('/') ? url.path.substring(1) : url.path + def pathElements = urlPath.split('/') + if (pathElements.length != 2) { + throw new InvalidUserDataException("Unexpected mod url format '${urlPath}'") + } + project.ext.set('repo.owner', pathElements[0]) + project.ext.set('repo.name', pathElements[1]) + } } else logger.warn('WARN: Unable to find mod.info file') } diff --git a/mod.info b/mod.info index 8b1646c..47f6720 100644 --- a/mod.info +++ b/mod.info @@ -3,5 +3,5 @@ poster=poster.png description=Compact mod development environment. id=pz-zmod url=https://github.com/cocolabs/pz-zmod -modversion=2.2.0 +modversion=2.3.0 pzversion=41.50-IWBUMS diff --git a/zdoc.gradle b/zdoc.gradle index f5e2369..44a3863 100644 --- a/zdoc.gradle +++ b/zdoc.gradle @@ -7,16 +7,13 @@ repositories { // try to find dependencies locally first mavenLocal() } -// Github Packages repository credentials -project.ext.cocoLabsRepo = 'pz-zdoc' -apply from: 'https://git.io/JtydE' configurations { zomboidDoc.extendsFrom zomboidRuntimeOnly } dependencies { - // https://github.com/orgs/cocolabs/packages?repo_name=pz-zdoc - zomboidDoc 'io.cocolabs:pz-zdoc:3.+' + // https://search.maven.org/artifact/io.github.cocolabs/pz-zdoc + zomboidDoc 'io.github.cocolabs:pz-zdoc:3.+' // ZomboidDoc compiled Lua library if (project.ext.has('mod.pzversion')) { @@ -65,21 +62,118 @@ tasks.register('zomboidVersion', JavaExec.class) { it.finalizedBy(tasks.getByName('saveModInfo')) } -tasks.register('annotateZomboidLua', JavaExec.class) { +/** + *

Returns {@code File} instance of {@code zdoc.version} file.

+ * If the file does not exist it will be created. + */ +File getZDocVersionFile() { + + def file = file('zdoc.version') + if (!file.exists() && !file.createNewFile()) { + throw new IOException("Unable to create zdoc.version file") + } + return file +} + +/** + * Compare the given versions and return a comparator resulting value. + * + * @param versionA semantic version to compare against. + * @param versionB semantic version to compare with. + * @return {@code -1} if {@code versionA} is lower then {@code versionB}, {@code 0} if + * versions are equal and {@code 1} if {@code versionA} is higher then {@code versionB} + */ +static int compareSemanticVersions(Integer[] versionA, Integer[] versionB) { + + if (versionA.length != 3 || versionB.length != 3) { + throw new GradleException("Not valid semantic versions [${versionA}, ${versionB}]") + } + for (int i = 0; i < 3; i++) + { + // first version is higher then second + if (versionA[i] > versionB[i]) { + return 1 + } + // second version is higher then first + else if (versionA[i] < versionB[i]) { + return -1 + } + } + // semantic versions are equal + return 0 +} + +/** + * Returns a series of integers representing {@code ZomboidDoc} dependency version. + */ +@SuppressWarnings('UnnecessaryQualifiedReference') +Integer[] getZDocDependencyVersion() { + + def pattern = java.util.regex.Pattern.compile('[\\w\\-]+(\\d+)\\.(\\d+)\\.(\\d+)') + def dependency = configurations.zomboidDoc.files.stream() + .filter({it.name.startsWith('pz-zdoc')}).findFirst() + if (!dependency.present) { + throw new RuntimeException('Unable to find ZomboidDoc dependency in configuration') + } + def filename = dependency.get().name + java.util.regex.Matcher matcher = pattern.matcher(filename) + if (matcher.find()) + { + def result = new Integer[3] + for (int i = 0; i < 3; i++) { + result[i] = Integer.valueOf(matcher.group(i + 1)) + } + return result + } + else throw new RuntimeException("Malformed zdoc dependency name ${filename}") +} + +/** + * Returns a series of integers representing last {@code ZomboidDoc} dependency version. + * + * @param versionFile {@code file} to read the version information from. + * @param currentVersion series of integers representing current {@code ZomboidDoc} version. + * @return last {@code ZomboidDoc} dependency version or {@code null} if no version found. + */ +Integer[] getZDocLastVersion(versionFile, currentVersion) { + + def content = versionFile.readLines() + if (!content.empty) + { + String[] elements = content.get(0).split('\\.') + if (elements.length == 3) + { + Integer[] result = new Integer[elements.length] + for (int i = 0; i < elements.length; i++) { + result[i] = Integer.valueOf(elements[i]) + } + return result + } + else logger.warn("WARN: Malformed semantic version found '${elements}'") + } + versionFile.text = currentVersion.join('.') + return null +} + +def annotateZomboidLua = tasks.register('annotateZomboidLua', JavaExec.class) { it.description 'Annotate vanilla Lua with EmmyLua.' - it.setGroup 'zomboid' + it.group 'zomboid' it.main = 'io.cocolabs.pz.zdoc.Main' it.classpath = configurations.zomboidDoc it.args('annotate', '-i', "${project.ext.gameDir}/media/lua", '-o', "$zDocLuaDir/media/lua") it.dependsOn(tasks.getByName('zomboidClasses')) + it.doLast { + def versionFile = getZDocVersionFile() + versionFile.text = getZDocDependencyVersion().join('.') + } } -tasks.register('compileZomboidLua', JavaExec.class) { +def compileZomboidLua = tasks.register('compileZomboidLua', JavaExec.class) { it.description 'Compile Lua library from modding API.' - it.setGroup 'zomboid' + it.group 'zomboid' //noinspection GroovyAssignabilityCheck,GroovyAccessibility it.javaLauncher = javaToolchains.launcherFor { @@ -89,4 +183,33 @@ tasks.register('compileZomboidLua', JavaExec.class) { it.classpath = configurations.zomboidDoc it.args('compile', '-i', "$gameDir", '-o', "$zDocLuaDir/media/lua/shared/Library") it.dependsOn(tasks.getByName('zomboidClasses')) + it.shouldRunAfter(annotateZomboidLua) + it.doLast { + def versionFile = getZDocVersionFile() + versionFile.text = getZDocDependencyVersion().join('.') + } } + +def updateZomboidLua = tasks.register('updateZomboidLua') { + + it.description 'Run ZomboidDoc to update compiled Lua library.' + it.group 'zomboid' + + def versionFile = getZDocVersionFile() + def currentVersion = getZDocDependencyVersion() + def lastVersion = getZDocLastVersion(versionFile, currentVersion) + def compareResult = lastVersion != null ? compareSemanticVersions(lastVersion, currentVersion) : -1 + it.onlyIf { + compareResult == -1 + } + // current version is higher then last version + if (compareResult == -1) { + it.dependsOn(annotateZomboidLua, compileZomboidLua) + } + it.doLast { + // update zdoc.version data + versionFile.text = currentVersion.join('.') + } +} +classes.dependsOn(updateZomboidLua) +