mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
more chunk pool and savegame migration codes
This commit is contained in:
BIN
lib/TerranVirtualDisk-src.jar
LFS
BIN
lib/TerranVirtualDisk-src.jar
LFS
Binary file not shown.
BIN
lib/TerranVirtualDisk.jar
LFS
BIN
lib/TerranVirtualDisk.jar
LFS
Binary file not shown.
@@ -22,7 +22,7 @@ import java.util.TreeMap
|
||||
* Created by minjaesong on 2024-10-07.
|
||||
*/
|
||||
data class ChunkAllocation(
|
||||
val chunkNumber: Int,
|
||||
val chunkNumber: Long,
|
||||
var classifier: ChunkAllocClass,
|
||||
var lastAccessTime: Long = System.nanoTime()
|
||||
)
|
||||
@@ -42,12 +42,12 @@ enum class ChunkAllocClass {
|
||||
open class ChunkPool(
|
||||
// `DiskSkimmer` or `ClusteredFormatDOM`
|
||||
val disk: Any,
|
||||
val layerIndex: Int,
|
||||
val wordSizeInBytes: Long,
|
||||
val world: GameWorld,
|
||||
val chunkNumToFileNum: (Int) -> String,
|
||||
val renumberFun: (Int) -> Int,
|
||||
) {
|
||||
private val pointers = TreeMap<Int, Long>()
|
||||
private val pointers = TreeMap<Long, Long>()
|
||||
private var allocCap = 32
|
||||
private var allocMap = Array<ChunkAllocation?>(allocCap) { null }
|
||||
private var allocCounter = 0
|
||||
@@ -59,12 +59,12 @@ open class ChunkPool(
|
||||
allocMap.fill(null)
|
||||
}
|
||||
|
||||
private fun createPointerViewOfChunk(chunkNumber: Int): UnsafePtr {
|
||||
private fun createPointerViewOfChunk(chunkNumber: Long): UnsafePtr {
|
||||
val baseAddr = pointers[chunkNumber]!!
|
||||
return UnsafePtr(baseAddr, chunkSize)
|
||||
}
|
||||
|
||||
private fun createPointerViewOfChunk(chunkNumber: Int, offsetX: Int, offsetY: Int): Pair<UnsafePtr, Long> {
|
||||
private fun createPointerViewOfChunk(chunkNumber: Long, offsetX: Int, offsetY: Int): Pair<UnsafePtr, Long> {
|
||||
val baseAddr = pointers[chunkNumber]!!
|
||||
return UnsafePtr(baseAddr, chunkSize) to wordSizeInBytes * (offsetY * CHUNK_W + offsetX)
|
||||
}
|
||||
@@ -72,7 +72,7 @@ open class ChunkPool(
|
||||
/**
|
||||
* If the chunk number == playerChunkNum, the result will be `false`
|
||||
*/
|
||||
private fun Int.isChunkNearPlayer(playerChunkNum: Int): Boolean {
|
||||
private fun Long.isChunkNearPlayer(playerChunkNum: Long): Boolean {
|
||||
if (this == playerChunkNum) return false
|
||||
|
||||
val pxy = LandUtil.chunkNumToChunkXY(world, playerChunkNum)
|
||||
@@ -124,7 +124,7 @@ open class ChunkPool(
|
||||
}
|
||||
}
|
||||
|
||||
private fun allocate(chunkNumber: Int, allocClass: ChunkAllocClass = ChunkAllocClass.TEMPORARY): UnsafePtr {
|
||||
private fun allocate(chunkNumber: Long, allocClass: ChunkAllocClass = ChunkAllocClass.TEMPORARY): UnsafePtr {
|
||||
updateAllocMapUsingIngamePlayer()
|
||||
|
||||
// find the empty spot within the pool
|
||||
@@ -141,7 +141,7 @@ open class ChunkPool(
|
||||
|
||||
private fun deallocate(allocation: ChunkAllocation) = deallocate(allocation.chunkNumber)
|
||||
|
||||
private fun deallocate(chunkNumber: Int): Boolean {
|
||||
private fun deallocate(chunkNumber: Long): Boolean {
|
||||
val ptr = pointers[chunkNumber] ?: return false
|
||||
|
||||
storeToDisk(chunkNumber)
|
||||
@@ -168,8 +168,8 @@ open class ChunkPool(
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchFromDisk(chunkNumber: Int) {
|
||||
val fileName = chunkNumToFileNum(chunkNumber)
|
||||
private fun fetchFromDisk(chunkNumber: Long) {
|
||||
val fileName = chunkNumToFileNum(layerIndex, chunkNumber)
|
||||
|
||||
// read data from the disk
|
||||
if (disk is ClusteredFormatDOM) {
|
||||
@@ -191,8 +191,8 @@ open class ChunkPool(
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeToDisk(chunkNumber: Int) {
|
||||
val fileName = chunkNumToFileNum(chunkNumber)
|
||||
private fun storeToDisk(chunkNumber: Long) {
|
||||
val fileName = chunkNumToFileNum(layerIndex, chunkNumber)
|
||||
|
||||
// write to the disk (the disk must be an autosaving copy of the original)
|
||||
if (disk is ClusteredFormatDOM) {
|
||||
@@ -212,13 +212,13 @@ open class ChunkPool(
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkForChunk(chunkNumber: Int) {
|
||||
private fun checkForChunk(chunkNumber: Long) {
|
||||
if (!pointers.containsKey(chunkNumber)) {
|
||||
fetchFromDisk(chunkNumber)
|
||||
}
|
||||
}
|
||||
|
||||
private fun serialise(chunkNumber: Int): ByteArray {
|
||||
private fun serialise(chunkNumber: Long): ByteArray {
|
||||
val ptr = pointers[chunkNumber]!!
|
||||
val out = ByteArray(chunkSize.toInt())
|
||||
UnsafeHelper.memcpyFromPtrToArr(ptr, out, 0, chunkSize)
|
||||
@@ -233,7 +233,7 @@ open class ChunkPool(
|
||||
* - word size is 3: Int `00_B2_B1_B0`
|
||||
* - word size is 2: Int `00_00_B1_B0`
|
||||
*/
|
||||
fun getTileRaw(chunkNumber: Int, offX: Int, offY: Int): Int {
|
||||
fun getTileRaw(chunkNumber: Long, offX: Int, offY: Int): Int {
|
||||
checkForChunk(chunkNumber)
|
||||
allocMap.find { it?.chunkNumber == chunkNumber }!!.let { it.lastAccessTime = System.nanoTime() }
|
||||
val (ptr, ptrOff) = createPointerViewOfChunk(chunkNumber, offX, offY)
|
||||
@@ -249,7 +249,7 @@ open class ChunkPool(
|
||||
* - First element: Int `00_00_B1_B0`
|
||||
* - Second element: Float16(`B3_B2`).toFloat32
|
||||
*/
|
||||
fun getTileI16F16(chunkNumber: Int, offX: Int, offY: Int): Pair<Int, Float> {
|
||||
fun getTileI16F16(chunkNumber: Long, offX: Int, offY: Int): Pair<Int, Float> {
|
||||
val raw = getTileRaw(chunkNumber, offX, offY)
|
||||
val ibits = raw.get1SS()
|
||||
val fbits = raw.get2SS().toShort()
|
||||
@@ -261,7 +261,7 @@ open class ChunkPool(
|
||||
* Return format:
|
||||
* - Int `00_00_B1_B0`
|
||||
*/
|
||||
fun getTileI16(chunkNumber: Int, offX: Int, offY: Int): Int {
|
||||
fun getTileI16(chunkNumber: Long, offX: Int, offY: Int): Int {
|
||||
val raw = getTileRaw(chunkNumber, offX, offY)
|
||||
val ibits = raw.get1SS()
|
||||
return ibits
|
||||
@@ -273,7 +273,7 @@ open class ChunkPool(
|
||||
* - First element: Int `00_00_B1_B0`
|
||||
* - Second element: Int `00_00_00_B2`
|
||||
*/
|
||||
fun getTileI16I8(chunkNumber: Int, offX: Int, offY: Int): Pair<Int, Int> {
|
||||
fun getTileI16I8(chunkNumber: Long, offX: Int, offY: Int): Pair<Int, Int> {
|
||||
val raw = getTileRaw(chunkNumber, offX, offY)
|
||||
val ibits = raw.get1SS()
|
||||
val jbits = raw.get2SS() and 255
|
||||
@@ -281,6 +281,11 @@ open class ChunkPool(
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun chunkNumToFileNum(layerNum: Int, chunkNum: Long): String {
|
||||
val entryID = Common.layerAndChunkNumToEntryID(layerNum, chunkNum)
|
||||
return Common.type254EntryIDtoType17Filename(entryID)
|
||||
}
|
||||
|
||||
private fun Int.get1SS() = this and 65535
|
||||
private fun Int.get2SS() = (this ushr 16) and 65535
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ object LandUtil {
|
||||
const val LAYER_WIRE = 20
|
||||
const val LAYER_FLUID = 3
|
||||
|
||||
fun toChunkNum(world: GameWorld, x: Int, y: Int): Int {
|
||||
fun toChunkNum(world: GameWorld, x: Int, y: Int): Long {
|
||||
// coercing and fmod-ing follows ROUNDWORLD rule. See: GameWorld.coerceXY()
|
||||
val (x, y) = world.coerceXY(x, y)
|
||||
return chunkXYtoChunkNum(world, x / CHUNK_W, y / CHUNK_H)
|
||||
@@ -32,13 +32,13 @@ object LandUtil {
|
||||
return Point2i(x / CHUNK_W, y / CHUNK_H)
|
||||
}
|
||||
|
||||
fun chunkXYtoChunkNum(world: GameWorld, cx: Int, cy: Int): Int {
|
||||
val ch = world.height / CHUNK_H
|
||||
fun chunkXYtoChunkNum(world: GameWorld, cx: Int, cy: Int): Long {
|
||||
val ch = (world.height / CHUNK_H).toLong()
|
||||
return cx * ch + cy
|
||||
}
|
||||
fun chunkNumToChunkXY(world: GameWorld, chunkNum: Int): Point2i {
|
||||
fun chunkNumToChunkXY(world: GameWorld, chunkNum: Long): Point2i {
|
||||
val ch = world.height / CHUNK_H
|
||||
return Point2i(chunkNum / ch, chunkNum % ch)
|
||||
return Point2i((chunkNum / ch).toInt(), (chunkNum % ch).toInt())
|
||||
}
|
||||
|
||||
fun getBlockAddr(world: GameWorld, x: Int, y: Int): BlockAddress {
|
||||
|
||||
@@ -161,13 +161,13 @@ class VirtualDisk(
|
||||
var extraInfoBytes = ByteArray(16)
|
||||
val entries = HashMap<EntryID, DiskEntry>()
|
||||
val isReadOnly = false
|
||||
var saveMode: Int
|
||||
var saveMode: Int // auto? quick?
|
||||
set(value) { extraInfoBytes[1] = value.toByte() }
|
||||
get() = extraInfoBytes[1].toUint()
|
||||
var saveKind: Int
|
||||
var saveKind: Int // player? world?
|
||||
set(value) { extraInfoBytes[2] = value.toByte() }
|
||||
get() = extraInfoBytes[2].toUint()
|
||||
var saveOrigin: Int
|
||||
var saveOrigin: Int // imported? native?
|
||||
set(value) { extraInfoBytes[3] = value.toByte() }
|
||||
get() = extraInfoBytes[3].toUint()
|
||||
var snapshot: Snapshot?
|
||||
|
||||
@@ -755,6 +755,14 @@ object Common {
|
||||
|
||||
return UUID(bytes.toBigInt64(0), bytes.toBigInt64(8))
|
||||
}
|
||||
|
||||
fun layerAndChunkNumToEntryID(layerNum: Int, chunkNum: Long) =
|
||||
0x1_0000_0000L or layerNum.toLong().shl(24) or chunkNum
|
||||
|
||||
|
||||
fun type254EntryIDtoType17Filename(entryID: Long): String {
|
||||
return entryID.toString(16).uppercase().padStart(16,'0')
|
||||
}
|
||||
}
|
||||
|
||||
class SaveLoadError(file: File?, cause: Throwable) : RuntimeException("An error occured while loading save file '${file?.absolutePath}'", cause)
|
||||
Reference in New Issue
Block a user