Implement world reassembly from a Heimdall backup.

This commit is contained in:
2023-02-07 09:01:43 -05:00
parent 192c6cb511
commit e8084d7283
11 changed files with 209 additions and 6 deletions

View File

@ -30,6 +30,7 @@ class GjallarhornCommand : CliktCommand(invokeWithoutSubcommand = true) {
maximumPoolSize = dbPoolSize
idleTimeout = Duration.ofMinutes(5).toMillis()
maxLifetime = Duration.ofMinutes(10).toMillis()
schema = "heimdall"
})
val db = Database.connect(pool)
currentContext.findOrSetObject { db }

View File

@ -0,0 +1,50 @@
package gay.pizza.foundation.heimdall.tool.commands
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.foundation.heimdall.export.ExportedBlock
import gay.pizza.foundation.heimdall.load.WorldLoadFormat
import gay.pizza.foundation.heimdall.load.WorldLoadWorld
import gay.pizza.foundation.heimdall.table.WorldChangeTable
import gay.pizza.foundation.heimdall.tool.state.BlockChangelog
import gay.pizza.foundation.heimdall.tool.state.BlockLogTracker
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory
import kotlin.io.path.createFile
import kotlin.io.path.deleteExisting
import kotlin.io.path.deleteIfExists
import kotlin.io.path.outputStream
class GenerateWorldLoadFile : CliktCommand(name = "generate-world-load", help = "Generate World Load File") {
private val db by requireObject<Database>()
val path by argument("load-format-file").path()
override fun run() {
val worlds = mutableMapOf<String, WorldLoadWorld>()
val worldChangelogs = BlockChangelog.query(db).splitBy { it.world }
val worldNames = transaction(db) {
WorldChangeTable.selectAll()
.associate { it[WorldChangeTable.toWorld] to it[WorldChangeTable.toWorldName] }
}
for ((id, changelog) in worldChangelogs) {
val tracker = BlockLogTracker()
tracker.replay(changelog)
val sparse = tracker.buildBlockMap { ExportedBlock(it.type) }
val blocks = sparse.blocks
worlds[id.toString().lowercase()] = WorldLoadWorld(
worldNames[id] ?: "unknown_$id",
blocks
)
}
val format = WorldLoadFormat(worlds)
path.deleteIfExists()
Json.encodeToStream(format, path.outputStream())
}
}

View File

@ -1,14 +1,12 @@
package gay.pizza.foundation.heimdall.tool
import gay.pizza.foundation.heimdall.tool.commands.BlockChangeTimelapseCommand
import gay.pizza.foundation.heimdall.tool.commands.ChunkExportLoaderCommand
import gay.pizza.foundation.heimdall.tool.commands.PlayerPositionExport
import gay.pizza.foundation.heimdall.tool.commands.PlayerSessionExport
import com.github.ajalt.clikt.core.subcommands
import gay.pizza.foundation.heimdall.tool.commands.*
fun main(args: Array<String>) = GjallarhornCommand().subcommands(
BlockChangeTimelapseCommand(),
PlayerSessionExport(),
PlayerPositionExport(),
ChunkExportLoaderCommand()
ChunkExportLoaderCommand(),
GenerateWorldLoadFile()
).main(args)

View File

@ -1,9 +1,11 @@
package gay.pizza.foundation.heimdall.tool.state
import java.time.Instant
import java.util.UUID
data class BlockChange(
val time: Instant,
val world: UUID,
val type: BlockChangeType,
val location: BlockCoordinate,
val from: BlockState,

View File

@ -63,10 +63,11 @@ class BlockChangelog(
BlockChangelog(BlockChangeView.select(filter).orderBy(BlockChangeView.time).map { row ->
val time = row[BlockChangeView.time]
val changeIsBreak = row[BlockChangeView.isBreak]
val world = row[BlockChangeView.world]
val x = row[BlockChangeView.x]
val y = row[BlockChangeView.y]
val z = row[BlockChangeView.z]
val block = row[gay.pizza.foundation.heimdall.view.BlockChangeView.block]
val block = row[BlockChangeView.block]
val location = BlockCoordinate(x.toLong(), y.toLong(), z.toLong())
val fromBlock = if (changeIsBreak) {
@ -83,6 +84,7 @@ class BlockChangelog(
BlockChange(
time,
world,
if (changeIsBreak) BlockChangeType.Break else BlockChangeType.Place,
location,
fromBlock,
@ -91,4 +93,18 @@ class BlockChangelog(
})
}
}
fun <T> splitBy(key: (BlockChange) -> T): Map<T, BlockChangelog> {
val logs = mutableMapOf<T, MutableList<BlockChange>>()
for (change in changes) {
val k = key(change)
var log = logs[k]
if (log == null) {
log = mutableListOf()
logs[k] = log
}
log.add(change)
}
return logs.mapValues { BlockChangelog(it.value) }
}
}

View File

@ -50,6 +50,15 @@ class BlockLogTracker(private val mode: BlockTrackMode = BlockTrackMode.RemoveOn
return map
}
fun <T> buildBlockMap(offset: BlockCoordinate = BlockCoordinate.zero, value: (BlockState) -> T): BlockCoordinateSparseMap<T> {
val map = BlockCoordinateSparseMap<T>()
blocks.forEach { (position, state) ->
val realPosition = offset.applyAsOffset(position)
map.put(realPosition, value(state))
}
return map
}
fun replay(changelog: BlockChangelog) = changelog.changes.forEach { change ->
if (change.type == BlockChangeType.Break) {
delete(change.location)