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.
|
* 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
|
||||||
|
|
||||||
|
|||||||
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 (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)
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user