more chunk pool and savegame migration codes

This commit is contained in:
minjaesong
2024-10-08 12:55:30 +09:00
parent b4523b8492
commit e0fcd00d82
10 changed files with 261 additions and 172 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -22,7 +22,7 @@ import java.util.TreeMap
* Created by minjaesong on 2024-10-07. * Created by minjaesong on 2024-10-07.
*/ */
data class ChunkAllocation( data class ChunkAllocation(
val chunkNumber: Int, val chunkNumber: Long,
var classifier: ChunkAllocClass, var classifier: ChunkAllocClass,
var lastAccessTime: Long = System.nanoTime() var lastAccessTime: Long = System.nanoTime()
) )
@@ -42,12 +42,12 @@ enum class ChunkAllocClass {
open class ChunkPool( open class ChunkPool(
// `DiskSkimmer` or `ClusteredFormatDOM` // `DiskSkimmer` or `ClusteredFormatDOM`
val disk: Any, val disk: Any,
val layerIndex: Int,
val wordSizeInBytes: Long, val wordSizeInBytes: Long,
val world: GameWorld, val world: GameWorld,
val chunkNumToFileNum: (Int) -> String,
val renumberFun: (Int) -> Int, val renumberFun: (Int) -> Int,
) { ) {
private val pointers = TreeMap<Int, Long>() private val pointers = TreeMap<Long, Long>()
private var allocCap = 32 private var allocCap = 32
private var allocMap = Array<ChunkAllocation?>(allocCap) { null } private var allocMap = Array<ChunkAllocation?>(allocCap) { null }
private var allocCounter = 0 private var allocCounter = 0
@@ -59,12 +59,12 @@ open class ChunkPool(
allocMap.fill(null) allocMap.fill(null)
} }
private fun createPointerViewOfChunk(chunkNumber: Int): UnsafePtr { private fun createPointerViewOfChunk(chunkNumber: Long): UnsafePtr {
val baseAddr = pointers[chunkNumber]!! val baseAddr = pointers[chunkNumber]!!
return UnsafePtr(baseAddr, chunkSize) 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]!! val baseAddr = pointers[chunkNumber]!!
return UnsafePtr(baseAddr, chunkSize) to wordSizeInBytes * (offsetY * CHUNK_W + offsetX) 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` * 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 if (this == playerChunkNum) return false
val pxy = LandUtil.chunkNumToChunkXY(world, playerChunkNum) 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() updateAllocMapUsingIngamePlayer()
// find the empty spot within the pool // 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(allocation: ChunkAllocation) = deallocate(allocation.chunkNumber)
private fun deallocate(chunkNumber: Int): Boolean { private fun deallocate(chunkNumber: Long): Boolean {
val ptr = pointers[chunkNumber] ?: return false val ptr = pointers[chunkNumber] ?: return false
storeToDisk(chunkNumber) storeToDisk(chunkNumber)
@@ -168,8 +168,8 @@ open class ChunkPool(
} }
} }
private fun fetchFromDisk(chunkNumber: Int) { private fun fetchFromDisk(chunkNumber: Long) {
val fileName = chunkNumToFileNum(chunkNumber) val fileName = chunkNumToFileNum(layerIndex, chunkNumber)
// read data from the disk // read data from the disk
if (disk is ClusteredFormatDOM) { if (disk is ClusteredFormatDOM) {
@@ -191,8 +191,8 @@ open class ChunkPool(
} }
} }
private fun storeToDisk(chunkNumber: Int) { private fun storeToDisk(chunkNumber: Long) {
val fileName = chunkNumToFileNum(chunkNumber) val fileName = chunkNumToFileNum(layerIndex, chunkNumber)
// write to the disk (the disk must be an autosaving copy of the original) // write to the disk (the disk must be an autosaving copy of the original)
if (disk is ClusteredFormatDOM) { 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)) { if (!pointers.containsKey(chunkNumber)) {
fetchFromDisk(chunkNumber) fetchFromDisk(chunkNumber)
} }
} }
private fun serialise(chunkNumber: Int): ByteArray { private fun serialise(chunkNumber: Long): ByteArray {
val ptr = pointers[chunkNumber]!! val ptr = pointers[chunkNumber]!!
val out = ByteArray(chunkSize.toInt()) val out = ByteArray(chunkSize.toInt())
UnsafeHelper.memcpyFromPtrToArr(ptr, out, 0, chunkSize) 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 3: Int `00_B2_B1_B0`
* - word size is 2: Int `00_00_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) checkForChunk(chunkNumber)
allocMap.find { it?.chunkNumber == chunkNumber }!!.let { it.lastAccessTime = System.nanoTime() } allocMap.find { it?.chunkNumber == chunkNumber }!!.let { it.lastAccessTime = System.nanoTime() }
val (ptr, ptrOff) = createPointerViewOfChunk(chunkNumber, offX, offY) val (ptr, ptrOff) = createPointerViewOfChunk(chunkNumber, offX, offY)
@@ -249,7 +249,7 @@ open class ChunkPool(
* - First element: Int `00_00_B1_B0` * - First element: Int `00_00_B1_B0`
* - Second element: Float16(`B3_B2`).toFloat32 * - 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 raw = getTileRaw(chunkNumber, offX, offY)
val ibits = raw.get1SS() val ibits = raw.get1SS()
val fbits = raw.get2SS().toShort() val fbits = raw.get2SS().toShort()
@@ -261,7 +261,7 @@ open class ChunkPool(
* Return format: * Return format:
* - Int `00_00_B1_B0` * - 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 raw = getTileRaw(chunkNumber, offX, offY)
val ibits = raw.get1SS() val ibits = raw.get1SS()
return ibits return ibits
@@ -273,7 +273,7 @@ open class ChunkPool(
* - First element: Int `00_00_B1_B0` * - First element: Int `00_00_B1_B0`
* - Second element: Int `00_00_00_B2` * - 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 raw = getTileRaw(chunkNumber, offX, offY)
val ibits = raw.get1SS() val ibits = raw.get1SS()
val jbits = raw.get2SS() and 255 val jbits = raw.get2SS() and 255
@@ -281,6 +281,11 @@ open class ChunkPool(
} }
companion object { 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.get1SS() = this and 65535
private fun Int.get2SS() = (this ushr 16) and 65535 private fun Int.get2SS() = (this ushr 16) and 65535

View 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()
}
}

View File

@@ -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)
}
}

View File

@@ -136,13 +136,13 @@ class WorldSavingThread(
for (cx in 0 until cw) { for (cx in 0 until cw) {
for (cy in 0 until ch) { for (cy in 0 until ch) {
val chunkFlag = ingame.world.chunkFlags[cy][cx] 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) { if (chunkFlag and 0x7F == CHUNK_LOADED) {
// Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}") // Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}")
val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy) 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 entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, VDFileID.ROOT, creation_t, time_t, entryContent) val entry = DiskEntry(entryID, VDFileID.ROOT, creation_t, time_t, entryContent)

View File

@@ -1,32 +1,12 @@
package net.torvald.terrarum.modulebasegame.serialise package net.torvald.terrarum.modulebasegame.serialise
import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Pixmap
import net.torvald.terrarum.*
import net.torvald.terrarum.App.printdbg 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.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame 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.*
import net.torvald.terrarum.savegame.VDFileID.SAVEGAMEINFO
import net.torvald.terrarum.serialise.Common
import net.torvald.terrarum.worlddrawer.WorldCamera import net.torvald.terrarum.worlddrawer.WorldCamera
import java.io.File 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. * 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)
}
}

View File

@@ -20,7 +20,7 @@ object LandUtil {
const val LAYER_WIRE = 20 const val LAYER_WIRE = 20
const val LAYER_FLUID = 3 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() // coercing and fmod-ing follows ROUNDWORLD rule. See: GameWorld.coerceXY()
val (x, y) = world.coerceXY(x, y) val (x, y) = world.coerceXY(x, y)
return chunkXYtoChunkNum(world, x / CHUNK_W, y / CHUNK_H) return chunkXYtoChunkNum(world, x / CHUNK_W, y / CHUNK_H)
@@ -32,13 +32,13 @@ object LandUtil {
return Point2i(x / CHUNK_W, y / CHUNK_H) return Point2i(x / CHUNK_W, y / CHUNK_H)
} }
fun chunkXYtoChunkNum(world: GameWorld, cx: Int, cy: Int): Int { fun chunkXYtoChunkNum(world: GameWorld, cx: Int, cy: Int): Long {
val ch = world.height / CHUNK_H val ch = (world.height / CHUNK_H).toLong()
return cx * ch + cy return cx * ch + cy
} }
fun chunkNumToChunkXY(world: GameWorld, chunkNum: Int): Point2i { fun chunkNumToChunkXY(world: GameWorld, chunkNum: Long): Point2i {
val ch = world.height / CHUNK_H 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 { fun getBlockAddr(world: GameWorld, x: Int, y: Int): BlockAddress {

View File

@@ -161,13 +161,13 @@ class VirtualDisk(
var extraInfoBytes = ByteArray(16) var extraInfoBytes = ByteArray(16)
val entries = HashMap<EntryID, DiskEntry>() val entries = HashMap<EntryID, DiskEntry>()
val isReadOnly = false val isReadOnly = false
var saveMode: Int var saveMode: Int // auto? quick?
set(value) { extraInfoBytes[1] = value.toByte() } set(value) { extraInfoBytes[1] = value.toByte() }
get() = extraInfoBytes[1].toUint() get() = extraInfoBytes[1].toUint()
var saveKind: Int var saveKind: Int // player? world?
set(value) { extraInfoBytes[2] = value.toByte() } set(value) { extraInfoBytes[2] = value.toByte() }
get() = extraInfoBytes[2].toUint() get() = extraInfoBytes[2].toUint()
var saveOrigin: Int var saveOrigin: Int // imported? native?
set(value) { extraInfoBytes[3] = value.toByte() } set(value) { extraInfoBytes[3] = value.toByte() }
get() = extraInfoBytes[3].toUint() get() = extraInfoBytes[3].toUint()
var snapshot: Snapshot? var snapshot: Snapshot?

View File

@@ -755,6 +755,14 @@ object Common {
return UUID(bytes.toBigInt64(0), bytes.toBigInt64(8)) 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) class SaveLoadError(file: File?, cause: Throwable) : RuntimeException("An error occured while loading save file '${file?.absolutePath}'", cause)