Skip to content

Commit

Permalink
feat: Added custom auto-save loading for server restarts
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Dec 4, 2023
1 parent 374bcca commit 2f9ba08
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ImperiumConfig>().language)
source.registerAll(
Expand Down Expand Up @@ -119,17 +118,14 @@ class ImperiumPlugin : AbstractMindustryPlugin() {
ResourceHudListener::class,
ImperiumLogicListener::class,
AntiEvadeListener::class,
)) {
GameListener::class)) {
application.register(listener)
}
if (application.instances.get<ServerConfig.Mindustry>().gamemode == MindustryGamemode.HUB) {
application.register(HubListener::class)
}
application.init()

val registry = application.instances.get<CommandRegistry>()
application.listeners.forEach { registry.parse(it) }

// https://github.com/Anuken/Arc/pull/158
if (getMindustryVersion().build < 147 ||
getMindustryVersion().type == MindustryVersion.Type.BLEEDING_EDGE) {
Expand All @@ -149,6 +145,12 @@ class ImperiumPlugin : AbstractMindustryPlugin() {
}
}

override fun onLoad() {
val registry = application.instances.get<CommandRegistry>()
application.listeners.forEach { registry.parse(it) }
logger.info("Parsed Imperium commands!")
}

override fun onExit() {
application.exit(ExitStatus.EXIT)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fun MindustryModule(plugin: ImperiumPlugin) =
?: error("The current server configuration is not Mindustry")
}

single<CommandRegistry> { MindustryCommandRegistry(get(), get(), get(), get(), get()) }
single<CommandRegistry> { MindustryCommandRegistry(get(), get(), get(), get()) }

single<Supplier<Discovery.Data>>("discovery") { Supplier(::getMindustryServerInfo) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Command>() ?: continue
function.isAccessible = true
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<MindustryMapManager>()
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())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"])
Expand Down Expand Up @@ -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()
}
Expand Down

0 comments on commit 2f9ba08

Please sign in to comment.