diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39cfe8fd..09ecae38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,9 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v4 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index a0b14375..de41974c 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -10,6 +10,9 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 90ea548a..e302bb59 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,6 +12,9 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v4 diff --git a/imperium-build-logic/src/main/kotlin/GenerateImperiumChangelog.kt b/imperium-build-logic/src/main/kotlin/GenerateImperiumChangelog.kt new file mode 100644 index 00000000..a13b5b80 --- /dev/null +++ b/imperium-build-logic/src/main/kotlin/GenerateImperiumChangelog.kt @@ -0,0 +1,40 @@ +import java.util.regex.Pattern +import net.kyori.indra.git.IndraGitExtension +import org.eclipse.jgit.lib.Constants +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.getByType + +@CacheableTask +open class GenerateImperiumChangelog : DefaultTask() { + + @OutputFile + val target: RegularFileProperty = project.objects.fileProperty() + + @TaskAction + fun generate() { + val git = project.rootProject.extensions.getByType().git()!! + val latest = git.repository.resolve("v" + project.rootProject.file("VERSION.txt").readText().trim())!! + val head = git.repository.resolve(Constants.HEAD)!! + target.get().asFile.writer().buffered().use { writer -> + git.log().addRange(latest, head).call().forEach { commit -> + val message = commit.shortMessage ?: return@forEach + val matcher = ACCEPTED_SUFFIX.matcher(message) + if (!matcher.find()) return@forEach + writer.write(matcher.group("verb").lowercase()) + writer.write("/") + writer.write(matcher.group("scope")?.lowercase() ?: "common") + writer.write("/") + writer.write(message.split(':', limit = 2)[1].trim()) + writer.newLine() + } + } + } + + companion object { + private val ACCEPTED_SUFFIX = Pattern.compile("^(?feat|fix)(\\((?mindustry|discord)\\))?:", Pattern.CASE_INSENSITIVE) + } +} \ No newline at end of file diff --git a/imperium-build-logic/src/main/kotlin/imperium.parent-conventions.gradle.kts b/imperium-build-logic/src/main/kotlin/imperium.parent-conventions.gradle.kts index d6607747..8c6db914 100644 --- a/imperium-build-logic/src/main/kotlin/imperium.parent-conventions.gradle.kts +++ b/imperium-build-logic/src/main/kotlin/imperium.parent-conventions.gradle.kts @@ -1,3 +1,7 @@ +plugins { + id("net.kyori.indra.git") +} + tasks.register("incrementVersionFile") { doLast { file("VERSION.txt").writeText(project.version.toString()) } } diff --git a/imperium-mindustry/build.gradle.kts b/imperium-mindustry/build.gradle.kts index fcc81adf..0d695525 100644 --- a/imperium-mindustry/build.gradle.kts +++ b/imperium-mindustry/build.gradle.kts @@ -47,11 +47,15 @@ val generateResources by tasks.registering { } } +val generateChangelog by tasks.registering(GenerateImperiumChangelog::class) { + target = temporaryDir.resolve("imperium-changelog.txt") +} + tasks.shadowJar { archiveFileName.set("imperium-mindustry.jar") archiveClassifier.set("plugin") - from(generateResources) + from(generateResources, generateChangelog) from(rootProject.file("LICENSE.md")) { into("META-INF") diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/ImperiumPlugin.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/ImperiumPlugin.kt index f279cd49..c3a2b86f 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/ImperiumPlugin.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/ImperiumPlugin.kt @@ -55,6 +55,7 @@ import com.xpdustry.imperium.mindustry.control.ControlListener import com.xpdustry.imperium.mindustry.event.EventListener import com.xpdustry.imperium.mindustry.game.AlertListener import com.xpdustry.imperium.mindustry.game.AntiGriefListener +import com.xpdustry.imperium.mindustry.game.ChangelogCommand import com.xpdustry.imperium.mindustry.game.GameListener import com.xpdustry.imperium.mindustry.game.ImperiumLogicListener import com.xpdustry.imperium.mindustry.game.LogicListener @@ -190,7 +191,8 @@ class ImperiumPlugin : AbstractMindustryPlugin() { SaveCommand::class, AntiGriefListener::class, FlexListener::class, - MetricsListener::class) + MetricsListener::class, + ChangelogCommand::class) .forEach(application::register) val gamemode = application.instances.get().mindustry.gamemode diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/CommandAnnotationScanner.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/CommandAnnotationScanner.kt index 80ebc7a3..8042745e 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/CommandAnnotationScanner.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/CommandAnnotationScanner.kt @@ -108,28 +108,28 @@ class CommandAnnotationScanner( annotation: ImperiumCommand ) { var names = annotation.name.toNameWithAliases() - val base = mutableListOf(names.first) - + val path = mutableListOf(names.first) var builder = manager - .commandBuilder(names.first, createLiteralDescription(base), *names.second) + .commandBuilder(names.first, createLiteralDescription(path), *names.second) .permission(createPermission(function, annotation)) - .commandDescription(createLiteralDescription(base)) for (rest in annotation.path.drop(1)) { names = rest.toNameWithAliases() - base += names.first - builder = builder.literal(names.first, createLiteralDescription(base), *names.second) + path += names.first + builder = builder.literal(names.first, createLiteralDescription(path), *names.second) } + builder = builder.commandDescription(createLiteralDescription(path)) + for (parameter in function.parameters.drop(1)) { if (parameter.type.classifier == CommandSender::class) continue val flag = parameter.findAnnotation() builder = if (flag == null) { - builder.argument(createCommandComponent(manager, parameter, base)) + builder.argument(createCommandComponent(manager, parameter, path)) } else { - builder.flag(createFlagComponent(manager, parameter, base, flag)) + builder.flag(createFlagComponent(manager, parameter, path, flag)) } } @@ -178,7 +178,7 @@ class CommandAnnotationScanner( private fun createCommandComponent( manager: MindustryCommandManager, parameter: KParameter, - base: List + path: List ): TypedCommandComponent { val token = TypeToken.get(parameter.type.javaType) as TypeToken val parameters = manager.parserRegistry().parseAnnotations(token, parameter.annotations) @@ -189,7 +189,7 @@ class CommandAnnotationScanner( ?: error("No parser found for type: ${parameter.type.javaType}")) .valueType(token) .required(!parameter.isOptional) - .description(createArgumentDescription(base, parameter.name!!)) + .description(createArgumentDescription(path, parameter.name!!)) .commandManager(manager) .build() } @@ -198,7 +198,7 @@ class CommandAnnotationScanner( private fun createFlagComponent( manager: MindustryCommandManager, parameter: KParameter, - base: List, + path: List, flag: Flag ): CommandFlag { if (parameter.type.classifier != Boolean::class && !parameter.isOptional) { @@ -207,11 +207,11 @@ class CommandAnnotationScanner( val builder = CommandFlag.builder(parameter.name!!) .withAliases(if (flag.alias.isNotBlank()) listOf(flag.alias) else emptyList()) - .withDescription(createArgumentDescription(base, parameter.name!!)) + .withDescription(createArgumentDescription(path, parameter.name!!)) return if (parameter.type.classifier == Boolean::class && !parameter.isOptional) { builder.build() as CommandFlag } else { - builder.withComponent(createCommandComponent(manager, parameter, base)).build() + builder.withComponent(createCommandComponent(manager, parameter, path)).build() } } diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/game/ChangelogCommand.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/game/ChangelogCommand.kt new file mode 100644 index 00000000..8a9b7217 --- /dev/null +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/game/ChangelogCommand.kt @@ -0,0 +1,84 @@ +/* + * Imperium, the software collection powering the Chaotic Neutral network. + * Copyright (C) 2024 Xpdustry + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.xpdustry.imperium.mindustry.game + +import com.xpdustry.distributor.api.command.CommandSender +import com.xpdustry.distributor.api.component.Component +import com.xpdustry.distributor.api.component.ListComponent.components +import com.xpdustry.distributor.api.component.TextComponent.newline +import com.xpdustry.distributor.api.component.TextComponent.text +import com.xpdustry.distributor.api.component.style.ComponentColor +import com.xpdustry.imperium.common.application.ImperiumApplication +import com.xpdustry.imperium.common.command.ImperiumCommand +import com.xpdustry.imperium.mindustry.command.annotation.ClientSide +import com.xpdustry.imperium.mindustry.command.annotation.ServerSide +import com.xpdustry.imperium.mindustry.translation.GRAY +import java.io.Reader + +class ChangelogCommand : ImperiumApplication.Listener { + + private val changelog: Component + + init { + val features = ArrayList() + val bugfixes = ArrayList() + javaClass.classLoader + .getResourceAsStream("imperium-changelog.txt")!! + .reader() + .use(Reader::readLines) + .forEach { entry -> + val (verb, scope, message) = entry.split('/', limit = 3) + if (scope == "common" || scope == "mindustry") { + when (verb) { + "feat" -> features += message + "fix" -> bugfixes += message + } + } + } + + val builder = components() + val none = text("None", GRAY) + builder.append(newline()) + builder.append(text("Features and Changes", ComponentColor.ACCENT)) + builder.append(newline()) + if (features.isNotEmpty()) { + features.forEach { feature -> builder.append(text(" - "), text(feature), newline()) } + } else { + builder.append(none) + } + builder.append(text("Bugfixes", ComponentColor.ACCENT)) + builder.append(newline()) + if (bugfixes.isNotEmpty()) { + bugfixes.forEach { fix -> builder.append(text(" - "), text(fix), newline()) } + } else { + builder.append(none) + } + changelog = builder.build() + } + + @ImperiumCommand(["changelog"]) + @ClientSide + @ServerSide + fun onChangelogCommand(sender: CommandSender) { + if (sender.isPlayer) { + sender.audience.sendAnnouncement(changelog) + } else { + sender.audience.sendMessage(changelog) + } + } +} diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/security/GatekeeperListener.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/security/GatekeeperListener.kt index 8fe3cc30..9e4a1861 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/security/GatekeeperListener.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/security/GatekeeperListener.kt @@ -22,6 +22,9 @@ import arc.util.Time import arc.util.io.Writes import com.google.common.net.InetAddresses import com.xpdustry.distributor.api.Distributor +import com.xpdustry.distributor.api.component.TextComponent.text +import com.xpdustry.distributor.api.component.style.ComponentColor +import com.xpdustry.distributor.api.player.MUUID import com.xpdustry.distributor.api.util.Priority import com.xpdustry.imperium.common.application.ImperiumApplication import com.xpdustry.imperium.common.async.ImperiumScope @@ -129,6 +132,16 @@ private fun interceptPlayerConnection( return } + if (packet.uuid == null || packet.usid == null) { + audience.kick(KickReason.idInUse, Duration.ZERO, false) + return + } + + if (!MUUID.isUuid(packet.uuid) || !MUUID.isUsid(packet.usid)) { + audience.kick(text("Invalid uuid or usid", ComponentColor.RED), Duration.ZERO, false) + return + } + // We do not want to save the data of DDOSers, so we postpone the saving of the player info val info = Vars.netServer.admins.getInfoOptional(packet.uuid) @@ -137,11 +150,6 @@ private fun interceptPlayerConnection( con.hasBegunConnecting = true con.mobile = packet.mobile - if (packet.uuid == null || packet.usid == null) { - audience.kick(KickReason.idInUse, Duration.ZERO, false) - return - } - if (Vars.netServer.admins.isIDBanned(packet.uuid)) { con.kick(KickReason.banned) return diff --git a/imperium-mindustry/src/main/resources/com/xpdustry/imperium/mindustry/bundles/bundle_en.properties b/imperium-mindustry/src/main/resources/com/xpdustry/imperium/mindustry/bundles/bundle_en.properties index 46afc18b..db98ad09 100644 --- a/imperium-mindustry/src/main/resources/com/xpdustry/imperium/mindustry/bundles/bundle_en.properties +++ b/imperium-mindustry/src/main/resources/com/xpdustry/imperium/mindustry/bundles/bundle_en.properties @@ -114,6 +114,7 @@ imperium.command.[ws.cancel].description=Cancels a wave skip vote imperium.command.[ws.n].description=Vote to not skip waves imperium.command.[ws.y].description=Vote to skip waves imperium.command.[ws].description=Participate in the wave skip vote +imperium.command.[changelog].description=Shows the recent changes of Imperium imperium.gui.close=Close imperium.gui.back=Back imperium.gui.submit=Submit