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 b0c90d0c..49f8a696 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 @@ -42,6 +42,7 @@ 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.ImperiumMetadataChunkReader import com.xpdustry.imperium.mindustry.misc.getMindustryVersion import com.xpdustry.imperium.mindustry.security.AdminRequestListener import com.xpdustry.imperium.mindustry.security.AntiEvadeListener @@ -67,12 +68,14 @@ import fr.xpdustry.distributor.api.plugin.AbstractMindustryPlugin import java.util.Locale import kotlin.system.exitProcess import kotlinx.coroutines.runBlocking +import mindustry.io.SaveVersion class ImperiumPlugin : AbstractMindustryPlugin() { private val application = MindustryImperiumApplication(this) override fun onInit() { logger.info("Imperium plugin initialized!") + SaveVersion.addCustomChunk("imperium", ImperiumMetadataChunkReader) val source = LocalizationSourceRegistry.create(application.instances.get().language) 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 index c1411280..846cec15 100644 --- 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 @@ -25,12 +25,14 @@ 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.playtime import com.xpdustry.imperium.mindustry.misc.runMindustryThread import com.xpdustry.imperium.mindustry.misc.snowflake +import com.xpdustry.imperium.mindustry.misc.start 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.ZERO import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay @@ -40,7 +42,6 @@ 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 { @@ -52,7 +53,7 @@ class GameListener(instances: InstanceManager) : ImperiumApplication.Listener { ImperiumScope.MAIN.launch { while (isActive) { delay(5.seconds) - if (Vars.state.state != GameState.State.playing) continue + if (Vars.state.state != GameState.State.playing || Vars.state.gameOver) continue runMindustryThread { Vars.state.map.playtime += 5.seconds } } } @@ -115,17 +116,7 @@ class GameListener(instances: InstanceManager) : ImperiumApplication.Listener { buildingsDestroyed = stats.buildingsDestroyed, winner = event.winner.id.toUByte()) } + Vars.state.map.playtime = ZERO + Vars.state.map.start = null } - - 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/misc/MapMetadataHelper.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/misc/MapMetadataHelper.kt new file mode 100644 index 00000000..687ee2ca --- /dev/null +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/misc/MapMetadataHelper.kt @@ -0,0 +1,80 @@ +/* + * 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.misc + +import com.xpdustry.imperium.common.misc.LoggerDelegate +import com.xpdustry.imperium.common.snowflake.Snowflake +import java.io.DataInput +import java.io.DataOutput +import java.time.Instant +import kotlin.time.Duration +import kotlin.time.Duration.Companion.ZERO +import kotlin.time.Duration.Companion.seconds +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import kotlinx.serialization.json.put +import mindustry.Vars +import mindustry.io.SaveFileReader.CustomChunk +import mindustry.maps.Map + +private const val MAP_SNOWFLAKE = "imperium-map-snowflake" +var Map.snowflake: Snowflake? + get() = tags.get(MAP_SNOWFLAKE)?.toLongOrNull() + set(value) { + tags.put(MAP_SNOWFLAKE, value.toString()) + } + +private const val MAP_START = "imperium-map-start" +var Map.start: Instant? + get() = tags.get(MAP_START)?.toLongOrNull()?.let(Instant::ofEpochMilli) + set(value) { + tags.put(MAP_START, value?.toEpochMilli().toString()) + } + +private const val MAP_PLAYTIME = "imperium-map-playtime" +var Map.playtime: Duration + get() = tags.get(MAP_PLAYTIME)?.toLongOrNull()?.seconds ?: ZERO + set(value) { + tags.put(MAP_PLAYTIME, value.inWholeSeconds.toString()) + } + +object ImperiumMetadataChunkReader : CustomChunk { + + private val logger by LoggerDelegate() + + override fun write(stream: DataOutput) { + val json = buildJsonObject { + Vars.state.map.snowflake?.let { put(MAP_SNOWFLAKE, it) } + Vars.state.map.start?.toEpochMilli()?.let { put(MAP_START, it) } + put(MAP_PLAYTIME, Vars.state.map.playtime.inWholeSeconds.toString()) + } + stream.writeUTF(json.toString()) + logger.debug("Written imperium metadata: {}", json.toString()) + } + + override fun read(stream: DataInput) { + val json = Json.parseToJsonElement(stream.readUTF()).jsonObject + Vars.state.map.snowflake = json[MAP_SNOWFLAKE]?.jsonPrimitive?.long + Vars.state.map.start = json[MAP_START]?.jsonPrimitive?.long?.let(Instant::ofEpochMilli) + Vars.state.map.playtime = json[MAP_PLAYTIME]?.jsonPrimitive?.long?.seconds ?: ZERO + logger.debug("Read imperium metadata: {}", json.toString()) + } +} diff --git a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/misc/MindustryExtensions.kt b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/misc/MindustryExtensions.kt index 7b06d6ab..ff22d7c9 100644 --- a/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/misc/MindustryExtensions.kt +++ b/imperium-mindustry/src/main/kotlin/com/xpdustry/imperium/mindustry/misc/MindustryExtensions.kt @@ -19,7 +19,6 @@ package com.xpdustry.imperium.mindustry.misc import com.xpdustry.imperium.common.misc.toInetAddress import com.xpdustry.imperium.common.network.Discovery -import com.xpdustry.imperium.common.snowflake.Snowflake import com.xpdustry.imperium.common.version.MindustryVersion import java.net.InetAddress import mindustry.Vars @@ -28,12 +27,6 @@ import mindustry.core.Version import mindustry.game.Gamemode import mindustry.net.Administration -var mindustry.maps.Map.snowflake: Snowflake? - get() = tags.get("imperium-map-id")?.toLongOrNull() - set(value) { - tags.put("imperium-map-id", value.toString()) - } - fun getMindustryServerInfo(): Discovery.Data.Mindustry = Discovery.Data.Mindustry( Administration.Config.serverName.string(), 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 307042cd..0cc02331 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 @@ -56,47 +56,46 @@ class MapListener(instances: InstanceManager) : ImperiumApplication.Listener { if (config.gamemode in it.gamemodes || it.gamemodes.isEmpty()) reloadMaps() } - reloadMaps() + ImperiumScope.MAIN.launch { reloadMaps() } } @Command(["reloadmaps"]) @ServerSide - private fun onMapReloadCommand() { + private suspend fun onMapReloadCommand() { reloadMaps() } - private fun reloadMaps() = - ImperiumScope.MAIN.launch { - val old = runMindustryThread { - val before = Vars.maps.all().map { it.name().stripMindustryColors() }.toMutableSet() - Vars.maps.reload() - return@runMindustryThread before - } + private suspend fun reloadMaps() { + val old = runMindustryThread { + val before = Vars.maps.all().map { it.name().stripMindustryColors() }.toMutableSet() + Vars.maps.reload() + return@runMindustryThread before + } - val pool = - maps.findAllMapsByGamemode(config.gamemode).mapNotNull { - try { - downloadMapFromPool(it) - } catch (e: Exception) { - logger.error( - "Failed to load map from server pool, falling back to local maps.", e) - null - } + val pool = + maps.findAllMapsByGamemode(config.gamemode).mapNotNull { + try { + downloadMapFromPool(it) + } catch (e: Exception) { + logger.error( + "Failed to load map from server pool, falling back to local maps.", e) + null } - if (pool.isEmpty()) { - logger.warn("No maps found in server pool, falling back to local maps.") } + if (pool.isEmpty()) { + logger.warn("No maps found in server pool, falling back to local maps.") + } - runMindustryThread { - Vars.maps.all().addAll(pool) - val now = Vars.maps.all().map { it.name().stripMindustryColors() }.toMutableSet() - logger.info( - "Reloaded {} maps (added={}, removed={}).", - now.size, - (now - old).size, - (old - now).size) - } + runMindustryThread { + Vars.maps.all().addAll(pool) + val now = Vars.maps.all().map { it.name().stripMindustryColors() }.toMutableSet() + logger.info( + "Reloaded {} maps (added={}, removed={}).", + now.size, + (now - old).size, + (old - now).size) } + } private suspend fun downloadMapFromPool(map: MindustryMap): Map { val file = cache.resolve("${map.snowflake}_${map.lastUpdate.toEpochMilli()}.msav")