diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 3df3ec61..de2f4f8f 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -19,7 +19,6 @@ plugins { `kotlin-dsl` - `groovy-gradle-plugin` } repositories { @@ -27,13 +26,6 @@ repositories { gradlePluginPortal() } -sourceSets { - main { - groovy.srcDirs("src/main/groovy") - kotlin.srcDirs("src/main/kotlin") - } -} - dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21") } diff --git a/buildSrc/src/main/groovy/steamwar.devserver.gradle b/buildSrc/src/main/groovy/steamwar.devserver.gradle deleted file mode 100644 index 590c5d8c..00000000 --- a/buildSrc/src/main/groovy/steamwar.devserver.gradle +++ /dev/null @@ -1,348 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2026 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import java.security.MessageDigest -import java.util.stream.Collectors - - -plugins { -} - -class DevServer extends DefaultTask { - - @Input - boolean debug = false - - @Input - String template = null - - @Input - @Optional - String plugins = null - - @Input - @Optional - Integer port = null - - @Input - @Optional - String jar = null - - @Input - @Optional - Map dParams = new HashMap<>() - - @Input - @Optional - String jvmArgs = null - - @Input - @Optional - String checkpointFolder = null - - @Input - @Optional - Boolean profile = null - - @Input - @Optional - Boolean forceUpgrade = null - - @Input - @Optional - String worldName = null - - DevServer() { - super() - doFirst { - if (checkpointFolder != null) dParams.put("checkpoint", checkpointFolder) - - List projects = [] - projects.add(project) - while (projects.first.parent != null) { - projects.add(0, projects.first.parent) - } - - def properties = new Properties() - projects.forEach { - def file = new File(it.projectDir, "steamwar.properties") - if (file.exists()) { - properties.load(new FileInputStream(file)) - } - } - - if (template.startsWith("Bau")) { - if (properties.containsKey("worldName")) { - worldName = properties.get("worldName") - } else { - throw new GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!") - } - } - host = properties.get("host") - debugPort = new Random().nextInt(5001, 10000) - - if (host == null) { - throw new GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!") - } - } - doLast { - setupTemplate(template) - uploadDependencies() - if (debug) startDebugPort() - startDevServer() - } - finalizedBy(new Finalizer()) - } - - @Internal - BufferedWriter processInput - - @Internal - String host - - @Internal - int debugPort - - @Internal - Boolean running = true - - class Finalizer extends DefaultTask { - - Finalizer() { - super() - doLast { - if (processInput != null) { - processInput.write(template.endsWith("Velocity") ? "end\n" : "stop\n") - processInput.flush() - } - running = false - } - } - } - - private Process run(String... args) { - List arguments = new ArrayList<>(); - arguments.add("ssh") - arguments.add(host) - arguments.add("-T") - arguments.addAll(Arrays.asList(args)) - def process = new ProcessBuilder(arguments).start() - process.waitFor() - return process - } - - private boolean checkFileOnRemote(String path) { - def process = run("[ -e \"$path\" ] && echo \"true\"") - process.errorStream.close() - process.outputStream.close() - try (def reader = new BufferedReader(new InputStreamReader(process.inputStream))) { - return reader.lines().count() > 0 - } - } - - private static void closeProcess(Process process) { - process.outputStream.close() - process.inputStream.close() - process.errorStream.close() - } - - void setupTemplate(String template) { - if (checkFileOnRemote("$template")) return - if (checkFileOnRemote("/configs/GameModes/${template}.yml")) { - println("GameMode Config exists") - def process = run("cat /configs/GameModes/${template}.yml | grep \"Folder: \"") - String serverTemplateName = new BufferedReader(new InputStreamReader(process.inputStream)).lines().collect(Collectors.joining("\n")) - .trim() - .substring("Folder: ".length()) - DevServer.closeProcess(process) - setupTemplate(serverTemplateName) - run("ln -s $serverTemplateName $template") - return - } - if (!checkFileOnRemote("/servers/$template")) { - throw new GradleException("Used template ($template) is not in /servers/ directory of the given host $host") - } - DevServer.closeProcess(run("cp -r /servers/$template $template")) - DevServer.closeProcess(run("chmod u+w $template")) - DevServer.closeProcess(run("rm -r $template/plugins/*WorldEdit/")) - DevServer.closeProcess(run("rm $template/log4j2.xml")) - } - - void uploadDependencies() { - def base = plugins == null ? "$template/plugins" : plugins - println("Uploading to ~/$base") - this.dependsOn.forEach { - Project resolved - AbstractArchiveTask archiveTask - if (it instanceof String) { - resolved = project.findProject(it.substring(0, it.lastIndexOf(':'))) - archiveTask = (AbstractArchiveTask) resolved.tasks.findByName(it.substring(it.lastIndexOf(':') + 1)) - } else { - throw new GradleException("Illegal argument for uploading dependencies") - } - - def archive = archiveTask.archiveFile.get().asFile - - Process process = new ProcessBuilder("ssh", host, "-T", "sha1sum $base/${archive.name.replace("-all", "")}").start() - byte[] bytes = MessageDigest.getInstance("sha1").digest(archive.bytes) - StringBuilder sb = new StringBuilder() - for (byte b : bytes) { - sb.append(String.format("%02X", b)) - } - boolean same = false - process.inputStream.readLines().forEach { - same |= it.startsWith(sb.toString().toLowerCase()) - } - DevServer.closeProcess(process) - if (same) { - println("Skipping $archive") - return - } - - println("Uploading $archive") - process = new ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start() - process.waitFor() - DevServer.closeProcess(process) - - process = new ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start(); - process.waitFor() - DevServer.closeProcess(process) - println("Uploaded $archive") - } - } - - void startDebugPort() { - def process = new ProcessBuilder("ssh", host, "-L", "5005:localhost:$debugPort").start() - def processOutput = new BufferedReader(new InputStreamReader(process.inputStream)) - new Thread({ - while (running) { - } - processOutput.close() - process.errorStream.close() - }).start() - } - - void startDevServer() { - def devPy = new StringBuilder().append("sw dev") - if (port != null) devPy.append(" --port $port") - if (worldName != null) devPy.append(" -w $template/$worldName") - if (plugins != null) devPy.append(" -p $plugins") - if (profile != null) devPy.append(" --profile") - if (forceUpgrade != null) devPy.append(" --forceUpgrade") - if (jar != null) devPy.append(" --jar $jar") - devPy.append(" $template") - for (Map.Entry dParam : dParams.entrySet()) { - devPy.append(" -D${dParam.key}=${dParam.value}") - } - devPy.append(" -Dpaper.disablePluginRemapping=true") - if (debug) devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort") - devPy.append(" -javaagent:/jars/AccessWidener.jar=start") - if (jvmArgs != null) devPy.append(" $jvmArgs") - println("Starting $template with command ${devPy.toString()}") - - def process = new ProcessBuilder("ssh", host, "-T", devPy.toString()).start() - def processOutput = new BufferedReader(new InputStreamReader(process.inputStream)) - new Thread({ - while (running) { - if (processOutput.ready()) { - println(processOutput.readLine()) - } - } - processOutput.close() - process.errorStream.close() - }).start() - - processInput = new BufferedWriter(new OutputStreamWriter(process.outputStream)) - def input = new BufferedReader(new InputStreamReader(System.in)) - new Thread({ - while (running) { - def text = input.readLine() - if (text == null) break - processInput.write(text) - processInput.newLine() - processInput.flush() - } - }).start() - - process.waitFor() - if (processInput != null) { - processInput.close() - } - processInput = null - running = false - } -} - -class VelocityServer extends DevServer { - @Input - @Optional - Boolean packetDecodeLogging = false - - VelocityServer() { - super() - doFirst { - if (packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true") - } - } -} - -class FightServer extends DevServer { - - @Input - @Optional - Integer checkSchemID = 0 - - @Input - @Optional - Integer prepareSchemID = 0 - - @Input - @Optional - Integer replay = 0 - - @Input - @Optional - String config = null - - @Input - @Optional - // Property: fightID - Integer eventKampfID = 0 - - @Input - @Optional - UUID blueLeader = null - - @Input - @Optional - UUID redLeader = null - - FightServer() { - super() - doFirst { - if (checkSchemID != 0) dParams.put("checkSchemID", "$checkSchemID") - if (prepareSchemID != 0) dParams.put("prepareSchemID", "$prepareSchemID") - if (replay != 0) dParams.put("replay", "$replay") - if (eventKampfID != 0) dParams.put("fightID", "$eventKampfID") - if (blueLeader != null) dParams.put("blueLeader", blueLeader.toString()) - if (redLeader != null) dParams.put("redLeader", redLeader.toString()) - if (config != null) dParams.put("config", config) - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/DevServer.kt b/buildSrc/src/main/kotlin/DevServer.kt new file mode 100644 index 00000000..c5529473 --- /dev/null +++ b/buildSrc/src/main/kotlin/DevServer.kt @@ -0,0 +1,290 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.File +import java.io.FileInputStream +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.security.MessageDigest +import java.util.Properties +import java.util.Random +import java.util.stream.Collectors + +open class DevServer : DefaultTask() { + + @get:Input + var debug: Boolean = false + + @get:Input + var template: String? = null + + @get:Input + @get:Optional + var plugins: String? = null + + @get:Input + @get:Optional + var port: Int? = null + + @get:Input + @get:Optional + var jar: String? = null + + @get:Input + @get:Optional + var dParams: MutableMap = HashMap() + + @get:Input + @get:Optional + var jvmArgs: String? = null + + @get:Input + @get:Optional + var checkpointFolder: String? = null + + @get:Input + @get:Optional + var profile: Boolean? = null + + @get:Input + @get:Optional + var forceUpgrade: Boolean? = null + + @get:Input + @get:Optional + var worldName: String? = null + + @get:Internal + var processInput: BufferedWriter? = null + + @get:Internal + var host: String? = null + + @get:Internal + var debugPort: Int = 0 + + @get:Internal + var running: Boolean = true + + init { + doFirst { + checkpointFolder?.let { dParams.put("checkpoint", it) } + + val projects = mutableListOf() + projects.add(project) + while (projects[0].parent != null) { + projects.add(0, projects[0].parent!!) + } + + val properties = Properties() + projects.forEach { + val file = File(it.projectDir, "steamwar.properties") + if (file.exists()) { + FileInputStream(file).use { input -> properties.load(input) } + } + } + + if (template!!.startsWith("Bau")) { + if (properties.containsKey("worldName")) { + worldName = properties.getProperty("worldName") + } else { + throw GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!") + } + } + host = properties.getProperty("host") + debugPort = Random().nextInt(5001, 10000) + + if (host == null) { + throw GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!") + } + } + doLast { + setupTemplate(template!!) + uploadDependencies() + if (debug) startDebugPort() + startDevServer() + } + finalizedBy(Finalizer()) + } + + inner class Finalizer : DefaultTask() { + init { + doLast { + processInput?.let { + it.write(if (template!!.endsWith("Velocity")) "end\n" else "stop\n") + it.flush() + } + running = false + } + } + } + + private fun run(vararg args: String): Process { + val arguments = mutableListOf("ssh", host, "-T") + arguments.addAll(args) + val process = ProcessBuilder(arguments).start() + process.waitFor() + return process + } + + private fun checkFileOnRemote(path: String): Boolean { + val process = run("[ -e \"$path\" ] && echo \"true\"") + process.errorStream.close() + process.outputStream.close() + return BufferedReader(InputStreamReader(process.inputStream)).use { reader -> + reader.lines().count() > 0 + } + } + + private fun closeProcess(process: Process) { + process.outputStream.close() + process.inputStream.close() + process.errorStream.close() + } + + fun setupTemplate(template: String) { + if (checkFileOnRemote(template)) return + if (checkFileOnRemote("/configs/GameModes/$template.yml")) { + println("GameMode Config exists") + val process = run("cat /configs/GameModes/$template.yml | grep \"Folder: \"") + val serverTemplateName = BufferedReader(InputStreamReader(process.inputStream)).lines() + .collect(Collectors.joining("\n")) + .trim() + .substring("Folder: ".length) + closeProcess(process) + setupTemplate(serverTemplateName) + run("ln -s $serverTemplateName $template") + return + } + if (!checkFileOnRemote("/servers/$template")) { + throw GradleException("Used template ($template) is not in /servers/ directory of the given host $host") + } + closeProcess(run("cp -r /servers/$template $template")) + closeProcess(run("chmod u+w $template")) + closeProcess(run("rm -r $template/plugins/*WorldEdit/")) + closeProcess(run("rm $template/log4j2.xml")) + } + + fun uploadDependencies() { + val base = if (plugins == null) "$template/plugins" else plugins + println("Uploading to ~/$base") + this.dependsOn.forEach { + if (it !is String) { + throw GradleException("Illegal argument for uploading dependencies") + } + val resolved = project.findProject(it.substring(0, it.lastIndexOf(':')))!! + val archiveTask = resolved.tasks.findByName(it.substring(it.lastIndexOf(':') + 1)) as AbstractArchiveTask + + val archive = archiveTask.archiveFile.get().asFile + + var process = ProcessBuilder("ssh", host, "-T", "sha1sum $base/${archive.name.replace("-all", "")}").start() + val bytes = MessageDigest.getInstance("sha1").digest(archive.readBytes()) + val sb = StringBuilder() + for (b in bytes) { + sb.append(String.format("%02X", b)) + } + var same = false + process.inputStream.bufferedReader().readLines().forEach { line -> + same = same || line.startsWith(sb.toString().lowercase()) + } + closeProcess(process) + if (same) { + println("Skipping $archive") + return@forEach + } + + println("Uploading $archive") + process = ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start() + process.waitFor() + closeProcess(process) + + process = ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start() + process.waitFor() + closeProcess(process) + println("Uploaded $archive") + } + } + + fun startDebugPort() { + val process = ProcessBuilder("ssh", host, "-L", "5005:localhost:$debugPort").start() + val processOutput = BufferedReader(InputStreamReader(process.inputStream)) + Thread { + while (running) { + } + processOutput.close() + process.errorStream.close() + }.start() + } + + fun startDevServer() { + val devPy = StringBuilder().append("sw dev") + if (port != null) devPy.append(" --port $port") + if (worldName != null) devPy.append(" -w $template/$worldName") + if (plugins != null) devPy.append(" -p $plugins") + if (profile != null) devPy.append(" --profile") + if (forceUpgrade != null) devPy.append(" --forceUpgrade") + if (jar != null) devPy.append(" --jar $jar") + devPy.append(" $template") + for ((key, value) in dParams) { + devPy.append(" -D$key=$value") + } + devPy.append(" -Dpaper.disablePluginRemapping=true") + if (debug) devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort") + devPy.append(" -javaagent:/jars/AccessWidener.jar=start") + if (jvmArgs != null) devPy.append(" $jvmArgs") + println("Starting $template with command $devPy") + + val process = ProcessBuilder("ssh", host, "-T", devPy.toString()).start() + val processOutput = BufferedReader(InputStreamReader(process.inputStream)) + Thread { + while (running) { + if (processOutput.ready()) { + println(processOutput.readLine()) + } + } + processOutput.close() + process.errorStream.close() + }.start() + + processInput = BufferedWriter(OutputStreamWriter(process.outputStream)) + val input = BufferedReader(InputStreamReader(System.`in`)) + Thread { + while (running) { + val text = input.readLine() ?: break + processInput?.write(text) + processInput?.newLine() + processInput?.flush() + } + }.start() + + process.waitFor() + processInput?.close() + processInput = null + running = false + } +} diff --git a/buildSrc/src/main/kotlin/FightServer.kt b/buildSrc/src/main/kotlin/FightServer.kt new file mode 100644 index 00000000..0788da17 --- /dev/null +++ b/buildSrc/src/main/kotlin/FightServer.kt @@ -0,0 +1,66 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import java.util.UUID + +open class FightServer : DevServer() { + + @get:Input + @get:Optional + var checkSchemID: Int = 0 + + @get:Input + @get:Optional + var prepareSchemID: Int = 0 + + @get:Input + @get:Optional + var replay: Int = 0 + + @get:Input + @get:Optional + var config: String? = null + + // Property: fightID + @get:Input + @get:Optional + var eventKampfID: Int = 0 + + @get:Input + @get:Optional + var blueLeader: UUID? = null + + @get:Input + @get:Optional + var redLeader: UUID? = null + + init { + doFirst { + if (checkSchemID != 0) dParams.put("checkSchemID", "$checkSchemID") + if (prepareSchemID != 0) dParams.put("prepareSchemID", "$prepareSchemID") + if (replay != 0) dParams.put("replay", "$replay") + if (eventKampfID != 0) dParams.put("fightID", "$eventKampfID") + blueLeader?.let { dParams.put("blueLeader", it.toString()) } + redLeader?.let { dParams.put("redLeader", it.toString()) } + config?.let { dParams.put("config", it) } + } + } +} diff --git a/buildSrc/src/main/kotlin/VelocityServer.kt b/buildSrc/src/main/kotlin/VelocityServer.kt new file mode 100644 index 00000000..b314981e --- /dev/null +++ b/buildSrc/src/main/kotlin/VelocityServer.kt @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional + +open class VelocityServer : DevServer() { + + @get:Input + @get:Optional + var packetDecodeLogging: Boolean = false + + init { + doFirst { + if (packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true") + } + } +} diff --git a/buildSrc/src/main/groovy/steamwar.java.gradle b/buildSrc/src/main/kotlin/steamwar.java.gradle.kts similarity index 83% rename from buildSrc/src/main/groovy/steamwar.java.gradle rename to buildSrc/src/main/kotlin/steamwar.java.gradle.kts index c6bb0a35..a60f4149 100644 --- a/buildSrc/src/main/groovy/steamwar.java.gradle +++ b/buildSrc/src/main/kotlin/steamwar.java.gradle.kts @@ -18,16 +18,18 @@ */ plugins { - id 'java-library' + `java-library` } +val libs = the().named("libs") + java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } tasks.compileJava { - options.encoding "UTF-8" + options.encoding = "UTF-8" } sourceSets { @@ -54,8 +56,9 @@ sourceSets { } dependencies { - annotationProcessor libs.lombok - compileOnly libs.lombok - testCompileOnly libs.lombok - testAnnotationProcessor libs.lombok -} \ No newline at end of file + val lombok = libs.findLibrary("lombok").get() + annotationProcessor(lombok) + compileOnly(lombok) + testCompileOnly(lombok) + testAnnotationProcessor(lombok) +} diff --git a/buildSrc/src/main/groovy/steamwar.kotlin.gradle b/buildSrc/src/main/kotlin/steamwar.kotlin.gradle.kts similarity index 81% rename from buildSrc/src/main/groovy/steamwar.kotlin.gradle rename to buildSrc/src/main/kotlin/steamwar.kotlin.gradle.kts index f672713d..8f47691c 100644 --- a/buildSrc/src/main/groovy/steamwar.kotlin.gradle +++ b/buildSrc/src/main/kotlin/steamwar.kotlin.gradle.kts @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * Copyright (C) 2026 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -18,10 +18,12 @@ */ plugins { - id 'java-library' - id "org.jetbrains.kotlin.jvm" + `java-library` + id("org.jetbrains.kotlin.jvm") } +val libs = the().named("libs") + kotlin { jvmToolchain(21) } @@ -32,7 +34,7 @@ java { } tasks.compileJava { - options.encoding "UTF-8" + options.encoding = "UTF-8" } sourceSets { @@ -67,8 +69,9 @@ sourceSets { } dependencies { - annotationProcessor libs.lombok - compileOnly libs.lombok - testCompileOnly libs.lombok - testAnnotationProcessor libs.lombok -} \ No newline at end of file + val lombok = libs.findLibrary("lombok").get() + annotationProcessor(lombok) + compileOnly(lombok) + testCompileOnly(lombok) + testAnnotationProcessor(lombok) +}