mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 19:44:05 +09:00
more chunk pool and savegame migration codes
This commit is contained in:
75
src/net/torvald/terrarum/modulebasegame/SavegameConverter.kt
Normal file
75
src/net/torvald/terrarum/modulebasegame/SavegameConverter.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
package net.torvald.terrarum.modulebasegame
|
||||
|
||||
import net.torvald.reflection.extortField
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.ClusteredFormatDOM
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.archivers.Clustfile
|
||||
import net.torvald.terrarum.savegame.*
|
||||
import net.torvald.terrarum.serialise.Common
|
||||
import net.torvald.terrarum.serialise.toBig64
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2024-10-08.
|
||||
*/
|
||||
object SavegameConverter {
|
||||
|
||||
fun type254toType11(infile: File, outFile: File) {
|
||||
val type254DOM = VDUtil.readDiskArchive(infile)
|
||||
type254DOMtoType11Disk(type254DOM, outFile)
|
||||
}
|
||||
|
||||
private val getGenver = Regex("""(?<="genver" ?: ?)[0-9]+""")
|
||||
|
||||
private fun type254DOMtoType11Disk(type254DOM: VirtualDisk, outFile: File) {
|
||||
val version: Long = when (type254DOM.saveKind) {
|
||||
VDSaveKind.PLAYER_DATA, VDSaveKind.WORLD_DATA -> {
|
||||
val savegameInfo = ByteArray64Reader(type254DOM.getFile(VDFileID.SAVEGAMEINFO)!!.bytes, Common.CHARSET)
|
||||
CharArray(128).let {
|
||||
savegameInfo.read(it, 0, 128)
|
||||
getGenver.find(String(it))?.value?.toLong()!!
|
||||
}
|
||||
}
|
||||
else -> 0L
|
||||
}
|
||||
|
||||
val newDisk = ClusteredFormatDOM.createNewArchive(
|
||||
outFile,
|
||||
Common.CHARSET,
|
||||
type254DOM.getDiskName(Common.CHARSET),
|
||||
ClusteredFormatDOM.MAX_CAPA_IN_SECTORS,
|
||||
byteArrayOf(
|
||||
-1, // pad
|
||||
byteArrayOf(15, -1)[type254DOM.saveOrigin.ushr(4).and(1)], // imported/native
|
||||
byteArrayOf(0x00, 0x50, 0x57)[type254DOM.saveKind], // player/world
|
||||
byteArrayOf(0x6d, 0x6d, 0x61, 0x61)[type254DOM.saveMode], // manual/auto
|
||||
type254DOM.extraInfoBytes[4], type254DOM.extraInfoBytes[5], // snapshot info
|
||||
0, 0,// reserved
|
||||
) + version.toBig64() // VERSION_RAW in big-endian
|
||||
)
|
||||
val DOM = ClusteredFormatDOM(newDisk)
|
||||
val root = DOM.getRootFile()
|
||||
|
||||
// do filecopy
|
||||
type254DOM.entries.filter { it.key != 0L }.forEach { entryID, diskEntry ->
|
||||
val filename = Common.type254EntryIDtoType17Filename(entryID)
|
||||
if (diskEntry.contents !is EntryFile) throw IllegalStateException("Entry in the savegame is not a file (${diskEntry.contents.javaClass.simpleName})")
|
||||
|
||||
val entry = diskEntry.contents as EntryFile
|
||||
|
||||
val oldBytes = entry.bytes.toByteArray()
|
||||
|
||||
Clustfile(DOM, root, filename).let { file ->
|
||||
// write bytes
|
||||
file.createNewFile()
|
||||
file.writeBytes(oldBytes)
|
||||
// modify attributes
|
||||
val FAT = file.extortField<ClusteredFormatDOM.FATEntry>("FAT")!!
|
||||
FAT.creationDate = diskEntry.creationDate
|
||||
FAT.modificationDate = diskEntry.modificationDate
|
||||
}
|
||||
}
|
||||
|
||||
DOM.dispose()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package net.torvald.terrarum.modulebasegame.serialise
|
||||
|
||||
import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.console.Echo
|
||||
import net.torvald.terrarum.gameworld.BlockLayerI16
|
||||
import net.torvald.terrarum.gameworld.BlockLayerI16F16
|
||||
import net.torvald.terrarum.gameworld.BlockLayerOresI16I8
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.langpack.Lang
|
||||
import net.torvald.terrarum.modulebasegame.FancyWorldReadLoadScreen
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumIngame
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.savegame.*
|
||||
import net.torvald.terrarum.serialise.Common
|
||||
import java.io.Reader
|
||||
import java.util.logging.Level
|
||||
import kotlin.experimental.or
|
||||
|
||||
/**
|
||||
* Load and setup the game for the first load.
|
||||
*
|
||||
* To load additional actors/worlds, use ReadActor/ReadWorld.
|
||||
*
|
||||
* Created by minjaesong on 2021-09-03.
|
||||
*/
|
||||
object LoadSavegame {
|
||||
|
||||
fun getWorldName(worldDisk: SimpleFileSystem) = worldDisk.getDiskName(Common.CHARSET)
|
||||
fun getWorldSavefileName(world: GameWorld) = "${world.worldIndex}"
|
||||
fun getPlayerSavefileName(player: IngamePlayer) = "${player.uuid}"
|
||||
|
||||
fun getFileBytes(disk: SimpleFileSystem, id: Long): ByteArray64 = disk.getFile(id)!!.bytes
|
||||
fun getFileReader(disk: SimpleFileSystem, id: Long): Reader =
|
||||
ByteArray64Reader(getFileBytes(disk, id), Common.CHARSET)
|
||||
|
||||
operator fun invoke(diskPair: DiskPair) = invoke(diskPair.player, diskPair.world)
|
||||
|
||||
private val getGenver = Regex("""(?<="genver" ?: ?)[0-9]+""")
|
||||
|
||||
/**
|
||||
* @param playerDisk DiskSkimmer representing the Player.
|
||||
* @param worldDisk0 DiskSkimmer representing the World to be loaded.
|
||||
* If unset, last played world for the Player will be loaded.
|
||||
*/
|
||||
operator fun invoke(playerDisk: DiskSkimmer, worldDisk0: DiskSkimmer? = null) {
|
||||
val newIngame = TerrarumIngame(App.batch)
|
||||
playerDisk.rebuild()
|
||||
|
||||
val playerDiskSavegameInfo =
|
||||
ByteArray64Reader(playerDisk.getFile(VDFileID.SAVEGAMEINFO)!!.bytes, Common.CHARSET)
|
||||
|
||||
val player = ReadActor.invoke(playerDisk, playerDiskSavegameInfo) as IngamePlayer
|
||||
|
||||
App.printdbg(this, "Player localhash: ${player.localHashStr}, hasSprite: ${player.sprite != null}")
|
||||
|
||||
val currentWorldId = player.worldCurrentlyPlaying
|
||||
val worldDisk = worldDisk0 ?: App.savegameWorlds[currentWorldId]!!.loadable()
|
||||
worldDisk.rebuild()
|
||||
val worldDiskSavegameInfo = ByteArray64Reader(worldDisk.getFile(VDFileID.SAVEGAMEINFO)!!.bytes, Common.CHARSET)
|
||||
val world = ReadWorld(worldDiskSavegameInfo, worldDisk.diskFile)
|
||||
|
||||
|
||||
world.layerTerrain = BlockLayerI16(world.width, world.height)
|
||||
world.layerWall = BlockLayerI16(world.width, world.height)
|
||||
world.layerOres = BlockLayerOresI16I8(world.width, world.height)
|
||||
world.layerFluids = BlockLayerI16F16(world.width, world.height)
|
||||
world.chunkFlags = Array(world.height / LandUtil.CHUNK_H) { ByteArray(world.width / LandUtil.CHUNK_W) }
|
||||
|
||||
newIngame.world = world // must be set before the loadscreen, otherwise the loadscreen will try to read from the NullWorld which is already destroyed
|
||||
newIngame.worldDisk = VDUtil.readDiskArchive(worldDisk.diskFile, Level.INFO)
|
||||
newIngame.playerDisk = VDUtil.readDiskArchive(playerDisk.diskFile, Level.INFO)
|
||||
newIngame.worldName = getWorldName(worldDisk)
|
||||
newIngame.worldSavefileName = getWorldSavefileName(world)
|
||||
newIngame.playerSavefileName = getPlayerSavefileName(player)
|
||||
|
||||
// worldDisk.dispose()
|
||||
// playerDisk.dispose()
|
||||
|
||||
|
||||
val loadJob = { it: LoadScreenBase ->
|
||||
val loadscreen = it as FancyWorldReadLoadScreen
|
||||
loadscreen.addMessage(Lang["MENU_IO_LOADING"])
|
||||
|
||||
|
||||
val worldGenver = CharArray(128).let {
|
||||
worldDiskSavegameInfo.read(it, 0, 128)
|
||||
getGenver.find(String(it))?.value?.toLong()!!
|
||||
}
|
||||
val playerGenver = CharArray(128).let {
|
||||
playerDiskSavegameInfo.read(it, 0, 128)
|
||||
getGenver.find(String(it))?.value?.toLong()!!
|
||||
}
|
||||
|
||||
|
||||
val actors = world.actors.distinct()
|
||||
val worldParam =
|
||||
TerrarumIngame.Codices(newIngame.worldDisk, world, actors, player, worldGenver, playerGenver) {}
|
||||
|
||||
|
||||
newIngame.gameLoadInfoPayload = worldParam
|
||||
newIngame.gameLoadMode = TerrarumIngame.GameLoadMode.LOAD_FROM
|
||||
|
||||
App.printdbg(
|
||||
this,
|
||||
"World dim: ${world.width}x${world.height}, ${world.width / LandUtil.CHUNK_W}x${world.height / LandUtil.CHUNK_H}"
|
||||
)
|
||||
|
||||
// load all the world blocklayer chunks
|
||||
val cw = LandUtil.CHUNK_W
|
||||
val ch = LandUtil.CHUNK_H
|
||||
val chunkCount = world.width.toLong() * world.height / (cw * ch)
|
||||
val worldLayer = intArrayOf(GameWorld.TERRAIN, GameWorld.WALL, GameWorld.ORES, GameWorld.FLUID).map { world.getLayer(it) }
|
||||
for (chunk in 0L until chunkCount) {
|
||||
for (layer in worldLayer.indices) {
|
||||
loadscreen.addMessage(Lang["MENU_IO_LOADING"])
|
||||
|
||||
newIngame.worldDisk.getFile(Common.layerAndChunkNumToEntryID(layer, chunk))?.let { chunkFile ->
|
||||
val (cx, cy) = LandUtil.chunkNumToChunkXY(world, chunk)
|
||||
|
||||
ReadWorld.decodeChunkToLayer(chunkFile.getContent(), worldLayer[layer]!!, cx, cy)
|
||||
world.chunkFlags[cy][cx] = world.chunkFlags[cy][cx] or GameWorld.CHUNK_LOADED
|
||||
}
|
||||
}
|
||||
loadscreen.progress.getAndAdd(1)
|
||||
}
|
||||
|
||||
loadscreen.addMessage(Lang["MENU_IO_LOAD_UPDATING_BLOCK_MAPPINGS"])
|
||||
world.renumberTilesAfterLoad()
|
||||
|
||||
|
||||
Echo("${ccW}World loaded: $ccY${newIngame.worldDisk.getDiskName(Common.CHARSET)}")
|
||||
App.printdbg(this, "World loaded: ${newIngame.worldDisk.getDiskName(Common.CHARSET)}")
|
||||
}
|
||||
|
||||
val loadScreen = FancyWorldReadLoadScreen(newIngame, world.width, world.height, loadJob)
|
||||
Terrarum.setCurrentIngameInstance(newIngame)
|
||||
App.setLoadScreen(loadScreen)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -136,13 +136,13 @@ class WorldSavingThread(
|
||||
for (cx in 0 until cw) {
|
||||
for (cy in 0 until ch) {
|
||||
val chunkFlag = ingame.world.chunkFlags[cy][cx]
|
||||
val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy).toLong()
|
||||
val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy)
|
||||
|
||||
if (chunkFlag and 0x7F == CHUNK_LOADED) {
|
||||
// Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}")
|
||||
|
||||
val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy)
|
||||
val entryID = 0x1_0000_0000L or layer.toLong().shl(24) or chunkNumber
|
||||
val entryID = Common.layerAndChunkNumToEntryID(layer, chunkNumber)
|
||||
|
||||
val entryContent = EntryFile(chunkBytes)
|
||||
val entry = DiskEntry(entryID, VDFileID.ROOT, creation_t, time_t, entryContent)
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
package net.torvald.terrarum.modulebasegame.serialise
|
||||
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import net.torvald.terrarum.*
|
||||
import net.torvald.terrarum.App.printdbg
|
||||
import net.torvald.terrarum.console.Echo
|
||||
import net.torvald.terrarum.gameworld.BlockLayerI16
|
||||
import net.torvald.terrarum.gameworld.BlockLayerI16F16
|
||||
import net.torvald.terrarum.gameworld.BlockLayerOresI16I8
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.gameworld.GameWorld.Companion.CHUNK_LOADED
|
||||
import net.torvald.terrarum.gameworld.GameWorld.Companion.FLUID
|
||||
import net.torvald.terrarum.gameworld.GameWorld.Companion.ORES
|
||||
import net.torvald.terrarum.gameworld.GameWorld.Companion.TERRAIN
|
||||
import net.torvald.terrarum.gameworld.GameWorld.Companion.WALL
|
||||
import net.torvald.terrarum.langpack.Lang
|
||||
import net.torvald.terrarum.modulebasegame.FancyWorldReadLoadScreen
|
||||
import net.torvald.terrarum.modulebasegame.IngameRenderer
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumIngame
|
||||
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.savegame.*
|
||||
import net.torvald.terrarum.savegame.VDFileID.SAVEGAMEINFO
|
||||
import net.torvald.terrarum.serialise.Common
|
||||
import net.torvald.terrarum.worlddrawer.WorldCamera
|
||||
import java.io.File
|
||||
import java.io.Reader
|
||||
import java.util.logging.Level
|
||||
import kotlin.experimental.or
|
||||
|
||||
/**
|
||||
* It's your responsibility to create a new VirtualDisk if your save is new, and create a backup for modifying existing save.
|
||||
@@ -106,123 +86,3 @@ object WriteSavegame {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Load and setup the game for the first load.
|
||||
*
|
||||
* To load additional actors/worlds, use ReadActor/ReadWorld.
|
||||
*
|
||||
* Created by minjaesong on 2021-09-03.
|
||||
*/
|
||||
object LoadSavegame {
|
||||
|
||||
fun getWorldName(worldDisk: SimpleFileSystem) = worldDisk.getDiskName(Common.CHARSET)
|
||||
fun getWorldSavefileName(world: GameWorld) = "${world.worldIndex}"
|
||||
fun getPlayerSavefileName(player: IngamePlayer) = "${player.uuid}"
|
||||
|
||||
fun getFileBytes(disk: SimpleFileSystem, id: Long): ByteArray64 = disk.getFile(id)!!.bytes
|
||||
fun getFileReader(disk: SimpleFileSystem, id: Long): Reader = ByteArray64Reader(getFileBytes(disk, id), Common.CHARSET)
|
||||
|
||||
operator fun invoke(diskPair: DiskPair) = invoke(diskPair.player, diskPair.world)
|
||||
|
||||
private val getGenver = Regex("""(?<="genver" ?: ?)[0-9]+""")
|
||||
|
||||
/**
|
||||
* @param playerDisk DiskSkimmer representing the Player.
|
||||
* @param worldDisk0 DiskSkimmer representing the World to be loaded.
|
||||
* If unset, last played world for the Player will be loaded.
|
||||
*/
|
||||
operator fun invoke(playerDisk: DiskSkimmer, worldDisk0: DiskSkimmer? = null) {
|
||||
val newIngame = TerrarumIngame(App.batch)
|
||||
playerDisk.rebuild()
|
||||
|
||||
val playerDiskSavegameInfo = ByteArray64Reader(playerDisk.getFile(SAVEGAMEINFO)!!.bytes, Common.CHARSET)
|
||||
|
||||
val player = ReadActor.invoke(playerDisk, playerDiskSavegameInfo) as IngamePlayer
|
||||
|
||||
printdbg(this, "Player localhash: ${player.localHashStr}, hasSprite: ${player.sprite != null}")
|
||||
|
||||
val currentWorldId = player.worldCurrentlyPlaying
|
||||
val worldDisk = worldDisk0 ?: App.savegameWorlds[currentWorldId]!!.loadable()
|
||||
worldDisk.rebuild()
|
||||
val worldDiskSavegameInfo = ByteArray64Reader(worldDisk.getFile(SAVEGAMEINFO)!!.bytes, Common.CHARSET)
|
||||
val world = ReadWorld(worldDiskSavegameInfo, worldDisk.diskFile)
|
||||
|
||||
|
||||
world.layerTerrain = BlockLayerI16(world.width, world.height)
|
||||
world.layerWall = BlockLayerI16(world.width, world.height)
|
||||
world.layerOres = BlockLayerOresI16I8(world.width, world.height)
|
||||
world.layerFluids = BlockLayerI16F16(world.width, world.height)
|
||||
world.chunkFlags = Array(world.height / LandUtil.CHUNK_H) { ByteArray(world.width / LandUtil.CHUNK_W) }
|
||||
|
||||
newIngame.world = world // must be set before the loadscreen, otherwise the loadscreen will try to read from the NullWorld which is already destroyed
|
||||
newIngame.worldDisk = VDUtil.readDiskArchive(worldDisk.diskFile, Level.INFO)
|
||||
newIngame.playerDisk = VDUtil.readDiskArchive(playerDisk.diskFile, Level.INFO)
|
||||
newIngame.worldName = getWorldName(worldDisk)
|
||||
newIngame.worldSavefileName = getWorldSavefileName(world)
|
||||
newIngame.playerSavefileName = getPlayerSavefileName(player)
|
||||
|
||||
// worldDisk.dispose()
|
||||
// playerDisk.dispose()
|
||||
|
||||
|
||||
val loadJob = { it: LoadScreenBase ->
|
||||
val loadscreen = it as FancyWorldReadLoadScreen
|
||||
loadscreen.addMessage(Lang["MENU_IO_LOADING"])
|
||||
|
||||
|
||||
val worldGenver = CharArray(128).let {
|
||||
worldDiskSavegameInfo.read(it, 0, 128)
|
||||
getGenver.find(String(it))?.value?.toLong()!!
|
||||
}
|
||||
val playerGenver = CharArray(128).let {
|
||||
playerDiskSavegameInfo.read(it, 0, 128)
|
||||
getGenver.find(String(it))?.value?.toLong()!!
|
||||
}
|
||||
|
||||
|
||||
val actors = world.actors.distinct()
|
||||
val worldParam = TerrarumIngame.Codices(newIngame.worldDisk, world, actors, player, worldGenver, playerGenver) {}
|
||||
|
||||
|
||||
newIngame.gameLoadInfoPayload = worldParam
|
||||
newIngame.gameLoadMode = TerrarumIngame.GameLoadMode.LOAD_FROM
|
||||
|
||||
printdbg(this, "World dim: ${world.width}x${world.height}, ${world.width / LandUtil.CHUNK_W}x${world.height / LandUtil.CHUNK_H}")
|
||||
|
||||
// load all the world blocklayer chunks
|
||||
val cw = LandUtil.CHUNK_W
|
||||
val ch = LandUtil.CHUNK_H
|
||||
val chunkCount = world.width * world.height / (cw * ch)
|
||||
val worldLayer = intArrayOf(TERRAIN, WALL, ORES, FLUID).map { world.getLayer(it) }
|
||||
for (chunk in 0L until chunkCount) {
|
||||
for (layer in worldLayer.indices) {
|
||||
loadscreen.addMessage(Lang["MENU_IO_LOADING"])
|
||||
|
||||
newIngame.worldDisk.getFile(0x1_0000_0000L or layer.toLong().shl(24) or chunk)?.let { chunkFile ->
|
||||
val (cx, cy) = LandUtil.chunkNumToChunkXY(world, chunk.toInt())
|
||||
|
||||
ReadWorld.decodeChunkToLayer(chunkFile.getContent(), worldLayer[layer]!!, cx, cy)
|
||||
world.chunkFlags[cy][cx] = world.chunkFlags[cy][cx] or CHUNK_LOADED
|
||||
}
|
||||
}
|
||||
loadscreen.progress.getAndAdd(1)
|
||||
}
|
||||
|
||||
loadscreen.addMessage(Lang["MENU_IO_LOAD_UPDATING_BLOCK_MAPPINGS"])
|
||||
world.renumberTilesAfterLoad()
|
||||
|
||||
|
||||
Echo("${ccW}World loaded: $ccY${newIngame.worldDisk.getDiskName(Common.CHARSET)}")
|
||||
printdbg(this, "World loaded: ${newIngame.worldDisk.getDiskName(Common.CHARSET)}")
|
||||
}
|
||||
|
||||
val loadScreen = FancyWorldReadLoadScreen(newIngame, world.width, world.height, loadJob)
|
||||
Terrarum.setCurrentIngameInstance(newIngame)
|
||||
App.setLoadScreen(loadScreen)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user