From 2f9ba08d29930ff47c6083ff02465893456b13f6 Mon Sep 17 00:00:00 2001 From: phinner <62483793+phinner@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:48:51 +0100 Subject: [PATCH] feat: Added custom auto-save loading for server restarts --- .../imperium/mindustry/ImperiumPlugin.kt | 16 ++- .../imperium/mindustry/MindustryModule.kt | 2 +- .../command/MindustryCommandRegistry.kt | 15 +- .../imperium/mindustry/game/GameListener.kt | 131 ++++++++++++++++++ .../imperium/mindustry/world/MapListener.kt | 61 -------- 5 files changed, 148 insertions(+), 77 deletions(-) create mode 100644 imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/game/GameListener.kt 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 91515b37..b0c90d0c 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 @@ -39,6 +39,7 @@ import com.xpdustry.imperium.mindustry.chat.ChatMessageListener import com.xpdustry.imperium.mindustry.chat.ChatTranslatorListener import com.xpdustry.imperium.mindustry.command.HelpCommand import com.xpdustry.imperium.mindustry.config.ConventionListener +import com.xpdustry.imperium.mindustry.game.GameListener import com.xpdustry.imperium.mindustry.game.ImperiumLogicListener import com.xpdustry.imperium.mindustry.history.HistoryCommand import com.xpdustry.imperium.mindustry.misc.getMindustryVersion @@ -71,10 +72,8 @@ class ImperiumPlugin : AbstractMindustryPlugin() { private val application = MindustryImperiumApplication(this) override fun onInit() { - logger.info("Imperium plugin loaded!") - } + logger.info("Imperium plugin initialized!") - override fun onLoad() { val source = LocalizationSourceRegistry.create(application.instances.get().language) source.registerAll( @@ -119,7 +118,7 @@ class ImperiumPlugin : AbstractMindustryPlugin() { ResourceHudListener::class, ImperiumLogicListener::class, AntiEvadeListener::class, - )) { + GameListener::class)) { application.register(listener) } if (application.instances.get().gamemode == MindustryGamemode.HUB) { @@ -127,9 +126,6 @@ class ImperiumPlugin : AbstractMindustryPlugin() { } application.init() - val registry = application.instances.get() - application.listeners.forEach { registry.parse(it) } - // https://github.com/Anuken/Arc/pull/158 if (getMindustryVersion().build < 147 || getMindustryVersion().type == MindustryVersion.Type.BLEEDING_EDGE) { @@ -149,6 +145,12 @@ class ImperiumPlugin : AbstractMindustryPlugin() { } } + override fun onLoad() { + val registry = application.instances.get() + application.listeners.forEach { registry.parse(it) } + logger.info("Parsed Imperium commands!") + } + override fun onExit() { application.exit(ExitStatus.EXIT) } diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt index 7e5bb8df..571e6631 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/MindustryModule.kt @@ -64,7 +64,7 @@ fun MindustryModule(plugin: ImperiumPlugin) = ?: error("The current server configuration is not Mindustry") } - single { MindustryCommandRegistry(get(), get(), get(), get(), get()) } + single { MindustryCommandRegistry(get(), get(), get(), get()) } single>("discovery") { Supplier(::getMindustryServerInfo) } diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/MindustryCommandRegistry.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/MindustryCommandRegistry.kt index ff0ad3c5..d7e4fbbc 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/MindustryCommandRegistry.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/command/MindustryCommandRegistry.kt @@ -38,7 +38,6 @@ import com.xpdustry.imperium.common.command.name import com.xpdustry.imperium.common.config.ImperiumConfig import com.xpdustry.imperium.common.config.ServerConfig import com.xpdustry.imperium.common.content.MindustryGamemode -import com.xpdustry.imperium.common.message.Messenger import com.xpdustry.imperium.mindustry.command.annotation.ClientSide import com.xpdustry.imperium.mindustry.command.annotation.Scope import com.xpdustry.imperium.mindustry.command.annotation.ServerSide @@ -69,18 +68,18 @@ class MindustryCommandRegistry( plugin: MindustryPlugin, private val server: ServerConfig.Mindustry, private val config: ImperiumConfig, - private val accounts: AccountManager, - private val messenger: Messenger, + private val accounts: AccountManager ) : CommandRegistry, ImperiumApplication.Listener { private val clientCommandManager = createArcCommandManager(plugin) private val serverCommandManager = createArcCommandManager(plugin) - - override fun onImperiumInit() { - clientCommandManager.initialize(Vars.netServer.clientCommands) - serverCommandManager.initialize(ServerControl.instance.handler) - } + private var initialized = false override fun parse(container: Any) { + if (!initialized) { + clientCommandManager.initialize(Vars.netServer.clientCommands) + serverCommandManager.initialize(ServerControl.instance.handler) + initialized = true + } for (function in container::class.declaredMemberFunctions) { val annotation = function.findAnnotation() ?: continue function.isAccessible = true diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/game/GameListener.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/game/GameListener.kt new file mode 100644 index 00000000..c1411280 --- /dev/null +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/game/GameListener.kt @@ -0,0 +1,131 @@ +/* + * Imperium, the software collection powering the Xpdustry network. + * Copyright (C) 2023 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.imperium.common.application.ImperiumApplication +import com.xpdustry.imperium.common.async.ImperiumScope +import com.xpdustry.imperium.common.content.MindustryMapManager +import com.xpdustry.imperium.common.inject.InstanceManager +import com.xpdustry.imperium.common.inject.get +import com.xpdustry.imperium.common.misc.LoggerDelegate +import com.xpdustry.imperium.common.version.MindustryVersion +import com.xpdustry.imperium.mindustry.misc.getMindustryVersion +import com.xpdustry.imperium.mindustry.misc.runMindustryThread +import com.xpdustry.imperium.mindustry.misc.snowflake +import fr.xpdustry.distributor.api.event.EventHandler +import fr.xpdustry.distributor.api.util.Priority +import java.time.Instant +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import mindustry.Vars +import mindustry.core.GameState +import mindustry.game.EventType +import mindustry.io.SaveIO +import mindustry.maps.Map +import mindustry.net.Administration + +class GameListener(instances: InstanceManager) : ImperiumApplication.Listener { + private val maps = instances.get() + private val autoSave = Vars.saveDirectory.child("auto-save-imperium.${Vars.saveExtension}") + private val logger by LoggerDelegate() + + override fun onImperiumInit() { + ImperiumScope.MAIN.launch { + while (isActive) { + delay(5.seconds) + if (Vars.state.state != GameState.State.playing) continue + runMindustryThread { Vars.state.map.playtime += 5.seconds } + } + } + + // TODO Add better auto-save for anti-grief ? + ImperiumScope.MAIN.launch { + while (isActive) { + delay(1.minutes) + runMindustryThread { + if (Vars.state.state == GameState.State.playing && + Administration.Config.autosave.bool() && + getMindustryVersion().type != MindustryVersion.Type.BLEEDING_EDGE) { + SaveIO.save(autoSave) + logger.debug("Saved current game") + } + } + } + } + } + + @EventHandler(priority = Priority.LOW) + internal fun onServerLoadEvent(event: EventType.ServerLoadEvent) { + if (Vars.state.state == GameState.State.menu && + Administration.Config.autosave.bool() && + autoSave.exists()) { + // TODO Use MapLoader ? + SaveIO.load(autoSave) + Vars.state.set(GameState.State.playing) + Vars.netServer.openServer() + logger.info("Loaded Imperium AutoSave") + } + } + + @EventHandler + internal fun onGameBeginEvent(event: MenuToPlayEvent) { + if (Vars.state.map.start == null) { + Vars.state.map.start = Instant.now() + } + } + + // TODO Notify players when they beat the map world record + @EventHandler + internal fun onGameOverEvent(event: EventType.GameOverEvent) { + val playtime = Vars.state.map.playtime + val stats = Vars.state.stats + val waves = Vars.state.wave + val start = Vars.state.map.start ?: Instant.now() + val snowflake = Vars.state.map.snowflake ?: return + if (playtime < 1.minutes) return + ImperiumScope.MAIN.launch { + maps.addMapGame( + map = snowflake, + start = start, + playtime = playtime, + unitsCreated = stats.unitsCreated, + ennemiesKilled = stats.enemyUnitsDestroyed, + wavesLasted = waves.coerceAtLeast(0), + buildingsConstructed = stats.buildingsBuilt, + buildingsDeconstructed = stats.buildingsDeconstructed, + buildingsDestroyed = stats.buildingsDestroyed, + winner = event.winner.id.toUByte()) + } + } + + private var Map.start: Instant? + get() = tags.get("imperium-map-start")?.toLongOrNull()?.let(Instant::ofEpochMilli) + set(value) { + tags.put("imperium-map-start", value?.toEpochMilli()?.toString()) + } + + private var Map.playtime: Duration + get() = tags.get("imperium-map-playtime")?.toLongOrNull()?.seconds ?: Duration.ZERO + set(value) { + tags.put("imperium-map-playtime", value.inWholeSeconds.toString()) + } +} diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/world/MapListener.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/world/MapListener.kt index 96005614..307042cd 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/world/MapListener.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/world/MapListener.kt @@ -32,24 +32,14 @@ import com.xpdustry.imperium.common.message.consumer import com.xpdustry.imperium.common.misc.LoggerDelegate import com.xpdustry.imperium.common.misc.stripMindustryColors import com.xpdustry.imperium.mindustry.command.annotation.ServerSide -import com.xpdustry.imperium.mindustry.game.MenuToPlayEvent import com.xpdustry.imperium.mindustry.misc.runMindustryThread import com.xpdustry.imperium.mindustry.misc.snowflake -import fr.xpdustry.distributor.api.event.EventHandler import java.nio.file.Path -import java.time.Instant import kotlin.io.path.createDirectory import kotlin.io.path.notExists import kotlin.io.path.outputStream -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import mindustry.Vars -import mindustry.core.GameState -import mindustry.game.EventType import mindustry.io.MapIO import mindustry.maps.Map @@ -67,45 +57,6 @@ class MapListener(instances: InstanceManager) : ImperiumApplication.Listener { } reloadMaps() - - ImperiumScope.MAIN.launch { - while (isActive) { - delay(5.seconds) - if (Vars.state.state != GameState.State.playing) continue - runMindustryThread { Vars.state.map.playtime += 5.seconds } - } - } - } - - @EventHandler - internal fun onGameBeginEvent(event: MenuToPlayEvent) { - if (Vars.state.map.start == null) { - Vars.state.map.start = Instant.now() - } - } - - // TODO Notify players when they beat the map world record - @EventHandler - internal fun onGameOverEvent(event: EventType.GameOverEvent) { - val playtime = Vars.state.map.playtime - val stats = Vars.state.stats - val waves = Vars.state.wave - val start = Vars.state.map.start ?: Instant.now() - val snowflake = Vars.state.map.snowflake ?: return - if (playtime < 1.minutes) return - ImperiumScope.MAIN.launch { - maps.addMapGame( - map = snowflake, - start = start, - playtime = playtime, - unitsCreated = stats.unitsCreated, - ennemiesKilled = stats.enemyUnitsDestroyed, - wavesLasted = waves.coerceAtLeast(0), - buildingsConstructed = stats.buildingsBuilt, - buildingsDeconstructed = stats.buildingsDeconstructed, - buildingsDestroyed = stats.buildingsDestroyed, - winner = event.winner.id.toUByte()) - } } @Command(["reloadmaps"]) @@ -159,18 +110,6 @@ class MapListener(instances: InstanceManager) : ImperiumApplication.Listener { return MapIO.createMap(Fi(file.toFile()), true).also { it.snowflake = map.snowflake } } - private var Map.start: Instant? - get() = tags.get("imperium-map-start")?.toLongOrNull()?.let(Instant::ofEpochMilli) - set(value) { - tags.put("imperium-map-start", value?.toEpochMilli()?.toString()) - } - - private var Map.playtime: Duration - get() = tags.get("imperium-map-playtime")?.toLongOrNull()?.seconds ?: Duration.ZERO - set(value) { - tags.put("imperium-map-playtime", value.inWholeSeconds.toString()) - } - companion object { private val logger by LoggerDelegate() }