diff --git a/src/net/torvald/terrarum/IngameInstance.kt b/src/net/torvald/terrarum/IngameInstance.kt index 4efa905fa..5b87bf2c0 100644 --- a/src/net/torvald/terrarum/IngameInstance.kt +++ b/src/net/torvald/terrarum/IngameInstance.kt @@ -10,6 +10,7 @@ import net.torvald.terrarum.gameitem.ItemID import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk import net.torvald.terrarum.ui.ConsoleWindow import net.torvald.util.SortedArrayList import java.util.concurrent.locks.Lock @@ -20,6 +21,9 @@ import java.util.concurrent.locks.Lock */ open class IngameInstance(val batch: SpriteBatch) : Screen { + lateinit var savegameArchive: VirtualDisk + internal set + var screenZoom = 1.0f val ZOOM_MAXIMUM = 4.0f val ZOOM_MINIMUM = 1.0f @@ -81,6 +85,9 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { val wallChangeQueue = ArrayList() val wireChangeQueue = ArrayList() // if 'old' is set and 'new' is blank, it's a wire cutter + var loadedTime_t = AppLoader.getTIME_T() + protected set + override fun hide() { } @@ -119,6 +126,8 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { printdbg(this, "dispose called by") printStackTrace(this) + actorContainerActive.forEach { it.dispose() } + actorContainerInactive.forEach { it.dispose() } world.dispose() } @@ -275,6 +284,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { + data class BlockChangeQueueItem(val old: ItemID, val new: ItemID, val posX: Int, val posY: Int) open fun sendNotification(messages: Array) {} diff --git a/src/net/torvald/terrarum/ModMgr.kt b/src/net/torvald/terrarum/ModMgr.kt index f2025e25e..151027ce6 100644 --- a/src/net/torvald/terrarum/ModMgr.kt +++ b/src/net/torvald/terrarum/ModMgr.kt @@ -14,6 +14,7 @@ import net.torvald.terrarum.utils.CSVFetcher import net.torvald.terrarum.utils.JsonFetcher import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVParser +import org.apache.commons.csv.CSVRecord import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -250,7 +251,20 @@ object ModMgr { val itemPath = "items/" @JvmStatic operator fun invoke(module: String) { - val csv = CSVFetcher.readFromModule(module, itemPath + "itemid.csv") + register(module, CSVFetcher.readFromModule(module, itemPath + "itemid.csv")) + } + + fun fromCSV(module: String, csvString: String) { + val csvParser = org.apache.commons.csv.CSVParser.parse( + csvString, + CSVFetcher.terrarumCSVFormat + ) + val csvRecordList = csvParser.records + csvParser.close() + register(module, csvRecordList) + } + + private fun register(module: String, csv: List) { csv.forEach { val className: String = it["classname"].toString() val internalID: Int = it["id"].toInt() diff --git a/src/net/torvald/terrarum/blockproperties/BlockCodex.kt b/src/net/torvald/terrarum/blockproperties/BlockCodex.kt index 3a340e69a..f62106244 100644 --- a/src/net/torvald/terrarum/blockproperties/BlockCodex.kt +++ b/src/net/torvald/terrarum/blockproperties/BlockCodex.kt @@ -46,49 +46,70 @@ object BlockCodex { */ val virtualToTile = HashMap() + fun clear() { + blockProps.clear() + dynamicLights.clear() + highestNumber = -1 + virtualTileCursor = 1 + tileToVirtual.clear() + virtualToTile.clear() + } + /** * Later entry (possible from other modules) will replace older ones */ operator fun invoke(module: String, path: String) { + AppLoader.printmsg(this, "Building block properties table") try { - val records = CSVFetcher.readFromModule(module, path) - - AppLoader.printmsg(this, "Building block properties table") - - records.forEach { - /*if (it.intVal("id") == -1) { - setProp(nullProp, it) - } - else { - setProp(blockProps[it.intVal("id")], it) - }*/ - - setProp(module, it.intVal("id"), it) - val tileId = "$module:${it.intVal("id")}" - - // register tiles with dynamic light - if ((blockProps[tileId]?.dynamicLuminosityFunction ?: 0) != 0) { - dynamicLights.add(tileId) - - // add virtual props for dynamic lights - val virtualChunk = ArrayList() - repeat(DYNAMIC_RANDOM_CASES) { _ -> - val virtualID = "$PREFIX_VIRTUALTILE:$virtualTileCursor" - - virtualToTile[virtualID] = tileId - virtualChunk.add(virtualID) - - setProp(PREFIX_VIRTUALTILE, virtualTileCursor, it) - - printdbg(this, "Block ID $tileId -> Virtual ID $virtualID, baseLum: ${blockProps[virtualID]?.baseLumCol}") - virtualTileCursor += 1 - } - tileToVirtual[tileId] = virtualChunk.sorted().toList() - } - } + register(module, CSVFetcher.readFromModule(module, path)) } - catch (e: IOException) { - e.printStackTrace() + catch (e: IOException) { e.printStackTrace() } + } + + fun fromCSV(module: String, csvString: String) { + AppLoader.printmsg(this, "Building wire properties table for module $module") + + val csvParser = org.apache.commons.csv.CSVParser.parse( + csvString, + CSVFetcher.terrarumCSVFormat + ) + val csvRecordList = csvParser.records + csvParser.close() + + register(module, csvRecordList) + } + + private fun register(module: String, records: List) { + records.forEach { + /*if (it.intVal("id") == -1) { + setProp(nullProp, it) + } + else { + setProp(blockProps[it.intVal("id")], it) + }*/ + + setProp(module, it.intVal("id"), it) + val tileId = "$module:${it.intVal("id")}" + + // register tiles with dynamic light + if ((blockProps[tileId]?.dynamicLuminosityFunction ?: 0) != 0) { + dynamicLights.add(tileId) + + // add virtual props for dynamic lights + val virtualChunk = ArrayList() + repeat(DYNAMIC_RANDOM_CASES) { _ -> + val virtualID = "$PREFIX_VIRTUALTILE:$virtualTileCursor" + + virtualToTile[virtualID] = tileId + virtualChunk.add(virtualID) + + setProp(PREFIX_VIRTUALTILE, virtualTileCursor, it) + + printdbg(this, "Block ID $tileId -> Virtual ID $virtualID, baseLum: ${blockProps[virtualID]?.baseLumCol}") + virtualTileCursor += 1 + } + tileToVirtual[tileId] = virtualChunk.sorted().toList() + } } } diff --git a/src/net/torvald/terrarum/blockproperties/WireCodex.kt b/src/net/torvald/terrarum/blockproperties/WireCodex.kt index 2a74a8f9b..6e9c10413 100644 --- a/src/net/torvald/terrarum/blockproperties/WireCodex.kt +++ b/src/net/torvald/terrarum/blockproperties/WireCodex.kt @@ -21,6 +21,10 @@ object WireCodex { private val nullProp = WireProp() + fun clear() { + wireProps.clear() + } + /** * `wire.csv` and texture for all wires are expected to be found in the given path. * @@ -29,30 +33,41 @@ object WireCodex { */ operator fun invoke(module: String, path: String) { AppLoader.printmsg(this, "Building wire properties table for module $module") - try { - val records = CSVFetcher.readFromModule(module, path + "wires.csv") - - - records.forEach { - WireCodex.setProp(module, it.intVal("id"), it) - } - - AppLoader.printmsg(this, "Registering wire textures into the resource pool") - wireProps.keys.forEach { id -> - val wireid = id.split(':').last().toInt() - - CommonResourcePool.addToLoadingList(id) { - val t = TextureRegionPack(ModMgr.getPath(module, "$path$wireid.tga"), TILE_SIZE, TILE_SIZE) - /*return*/t - } - } - - CommonResourcePool.loadAll() + register(module, path, CSVFetcher.readFromModule(module, path + "wires.csv")) } - catch (e: IOException) { - e.printStackTrace() + catch (e: IOException) { e.printStackTrace() } + } + + fun fromCSV(module: String, path: String, csvString: String) { + AppLoader.printmsg(this, "Building wire properties table for module $module") + + val csvParser = org.apache.commons.csv.CSVParser.parse( + csvString, + CSVFetcher.terrarumCSVFormat + ) + val csvRecordList = csvParser.records + csvParser.close() + + register(module, path, csvRecordList) + } + + private fun register(module: String, path: String, records: List) { + records.forEach { + WireCodex.setProp(module, it.intVal("id"), it) } + + AppLoader.printmsg(this, "Registering wire textures into the resource pool") + wireProps.keys.forEach { id -> + val wireid = id.split(':').last().toInt() + + CommonResourcePool.addToLoadingList(id) { + val t = TextureRegionPack(ModMgr.getPath(module, "$path$wireid.tga"), TILE_SIZE, TILE_SIZE) + /*return*/t + } + } + + CommonResourcePool.loadAll() } fun getAll() = WireCodex.wireProps.values diff --git a/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt b/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt index 512582327..e379fda26 100644 --- a/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt +++ b/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt @@ -110,8 +110,10 @@ class SavegameCracker( it.call(this, args) } catch (e: Throwable) { - printerrln("An error occured:") - e.printStackTrace(stderr) + val error = e.cause ?: e + printerrln("Error -- ${error}") + error.printStackTrace(stderr) + printerrln("Error -- ${error}") } } } @@ -128,6 +130,13 @@ class SavegameCracker( return null } + private fun String.padEnd(len: Int, padfun: (Int) -> Char): String { + val sb = StringBuilder() + for (i in 0 until len - this.length) + sb.append(padfun(i)) + return this + sb.toString() + } + @Command("Loads a disk archive", "path-to-file") fun load(args: List) { file = File(args[1]) @@ -142,12 +151,12 @@ class SavegameCracker( if (i != 0) println( ccNoun + i.toString(10).padStart(11, ' ') + " " + - ccNoun2 + (entry.filename.toCanonicalString(charset) + " " + cc0).padEnd(18, '.') + " " + - ccConst + entry.contents.getSizePure() + " bytes" + ccNoun2 + (entry.filename.toCanonicalString(charset) + cc0).padEnd(18) { if (it == 0) ' ' else '.' } + + ccConst + " " + entry.contents.getSizePure() + " bytes" ) } val entryCount = it.entries.size - 1 - println("${cc0}$entryCount ${if (entryCount != 1) "Entries" else "Entry"}, total ${it.usedBytes}/${it.capacity} bytes") + println("${cc0}$entryCount entries, total ${it.usedBytes} bytes") } } diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index af448a231..8ea143e69 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -8,6 +8,7 @@ import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.BlockCodex import net.torvald.terrarum.blockproperties.Fluid +import net.torvald.terrarum.gameactors.ActorID import net.torvald.terrarum.gameactors.WireActor import net.torvald.terrarum.gameitem.ItemID import net.torvald.terrarum.realestate.LandUtil @@ -42,9 +43,6 @@ class GameWorld() : Disposable { var totalPlayTime: Long = 0 internal set - /** Used to calculate play time */ - @Transient open val loadTime: Long = System.currentTimeMillis() / 1000L - //layers lateinit var layerWall: BlockLayer lateinit var layerTerrain: BlockLayer @@ -106,6 +104,8 @@ class GameWorld() : Disposable { internal var genver = -1 internal var comp = -1 + internal val actors = ArrayList() // only filled up on save and load; DO NOT USE THIS + /** * Create new world */ diff --git a/src/net/torvald/terrarum/itemproperties/ItemCodex.kt b/src/net/torvald/terrarum/itemproperties/ItemCodex.kt index df4147a7b..6bbd6ad2a 100644 --- a/src/net/torvald/terrarum/itemproperties/ItemCodex.kt +++ b/src/net/torvald/terrarum/itemproperties/ItemCodex.kt @@ -32,6 +32,12 @@ object ItemCodex { val ACTORID_MIN = ReferencingRanges.ACTORS.first + fun clear() { + itemCodex.clear() + dynamicItemDescription.clear() + dynamicToStaticTable.clear() + } + private val itemImagePlaceholder: TextureRegion get() = CommonResourcePool.getAsTextureRegion("itemplaceholder_24") // copper pickaxe diff --git a/src/net/torvald/terrarum/itemproperties/Material.kt b/src/net/torvald/terrarum/itemproperties/Material.kt index 47b15d8d1..16673f2ff 100644 --- a/src/net/torvald/terrarum/itemproperties/Material.kt +++ b/src/net/torvald/terrarum/itemproperties/Material.kt @@ -1,9 +1,11 @@ package net.torvald.terrarum.itemproperties import net.torvald.terrarum.AppLoader.printmsg +import net.torvald.terrarum.ModMgr import net.torvald.terrarum.blockproperties.floatVal import net.torvald.terrarum.blockproperties.intVal import net.torvald.terrarum.utils.CSVFetcher +import org.apache.commons.csv.CSVRecord import java.io.IOException /** @@ -32,30 +34,37 @@ object MaterialCodex { private val nullMaterial = Material() operator fun invoke(module: String, path: String) { - try { - val records = CSVFetcher.readFromModule(module, path) + register(CSVFetcher.readFromModule(module, path)) + } - printmsg(this, "Building materials table") + fun fromCSV(module: String, csvString: String) { + val csvParser = org.apache.commons.csv.CSVParser.parse( + csvString, + CSVFetcher.terrarumCSVFormat + ) + val csvRecordList = csvParser.records + csvParser.close() + register(csvRecordList) + } - records.forEach { - val prop = Material() - prop.strength = it.intVal("tens") - prop.density = it.intVal("dsty") - prop.forceMod = it.intVal("fmod") - prop.enduranceMod = it.floatVal("endurance") - prop.thermalConductivity = it.floatVal("tcond") - prop.identifier = it.get("idst").toUpperCase() + private fun register(records: List) { + records.forEach { + val prop = Material() + prop.strength = it.intVal("tens") + prop.density = it.intVal("dsty") + prop.forceMod = it.intVal("fmod") + prop.enduranceMod = it.floatVal("endurance") + prop.thermalConductivity = it.floatVal("tcond") + prop.identifier = it.get("idst").toUpperCase() - materialProps[prop.identifier] = prop + materialProps[prop.identifier] = prop - printmsg(this, "${prop.identifier}\t${prop.strength}\t${prop.density}\t${prop.forceMod}\t${prop.enduranceMod}") - } - } - catch (e: IOException) { - e.printStackTrace() + printmsg(this, "${prop.identifier}\t${prop.strength}\t${prop.density}\t${prop.forceMod}\t${prop.enduranceMod}") } } + fun clear() = materialProps.clear() + operator fun get(identifier: String) = try { materialProps[identifier.toUpperCase()]!! } diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt index caa08e9aa..f204ef168 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt @@ -9,6 +9,7 @@ import net.torvald.terrarum.* import net.torvald.terrarum.AppLoader.* import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED +import net.torvald.terrarum.blockproperties.BlockCodex import net.torvald.terrarum.blockproperties.BlockPropUtil import net.torvald.terrarum.blockproperties.WireCodex import net.torvald.terrarum.blockstats.BlockStats @@ -23,17 +24,24 @@ import net.torvald.terrarum.gameitem.GameItem import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.console.AVTracker import net.torvald.terrarum.console.ActorsList +import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.gameparticles.ParticleBase import net.torvald.terrarum.gameactors.WireActor import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.modulebasegame.gameactors.* import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver import net.torvald.terrarum.gameworld.WorldSimulator +import net.torvald.terrarum.itemproperties.MaterialCodex import net.torvald.terrarum.modulebasegame.gameworld.GameEconomy import net.torvald.terrarum.modulebasegame.ui.* +import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser import net.torvald.terrarum.weather.WeatherMixer import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen import net.torvald.terrarum.modulebasegame.worldgenerator.WorldgenParams +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk +import net.torvald.terrarum.serialise.Common +import net.torvald.terrarum.serialise.WriteMeta import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.worlddrawer.BlocksDrawer import net.torvald.terrarum.worlddrawer.FeaturesDrawer @@ -221,7 +229,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { when (gameLoadMode) { GameLoadMode.CREATE_NEW -> enterCreateNewWorld(gameLoadInfoPayload as NewWorldParameters) - GameLoadMode.LOAD_FROM -> enterLoadFromSave(gameLoadInfoPayload as GameSaveData) + GameLoadMode.LOAD_FROM -> enterLoadFromSave(gameLoadInfoPayload as WriteMeta.WorldMeta) } IngameRenderer.setRenderedWorld(world) @@ -230,16 +238,6 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { super.show() // gameInitialised = true } - data class GameSaveData( - val world: GameWorld, - val historicalFigureIDBucket: ArrayList, - val realGamePlayer: IngamePlayer, - val rogueS0: Long, - val rogueS1: Long, - val weatherS0: Long, - val weatherS1: Long - ) - data class NewWorldParameters( val width: Int, val height: Int, @@ -259,12 +257,30 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { /** * Init instance by loading saved world */ - private fun enterLoadFromSave(gameSaveData: GameSaveData) { + private fun enterLoadFromSave(meta: WriteMeta.WorldMeta) { if (gameInitialised) { printdbg(this, "loaded successfully.") } else { - TODO() + RoguelikeRandomiser.loadFromSave(meta.randseed0, meta.randseed1) + WeatherMixer.loadFromSave(meta.weatseed0, meta.weatseed1) + + // Load BlockCodex // + BlockCodex.clear() + meta.blocks.forEach { module, csv -> BlockCodex.fromCSV(module, csv.doc) } + + // Load WireCodex // + WireCodex.clear() + meta.wires.forEach { module, csv -> WireCodex.fromCSV(module, ModMgr.getPath(module, "wires/"), csv.doc) } + + // Load ItemCodex // + ItemCodex.clear() + meta.items.forEach { module, csv -> ModMgr.GameItemLoader.fromCSV(module, csv.doc) } + // TODO registerNewDynamicItem + + // Load MaterialCodex // + MaterialCodex.clear() + meta.materials.forEach { module, csv -> MaterialCodex.fromCSV(module, csv.doc) } } } @@ -284,7 +300,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { // init map as chosen size - val timeNow = System.currentTimeMillis() / 1000 + val timeNow = AppLoader.getTIME_T() world = GameWorld(1, worldParams.width, worldParams.height, timeNow, timeNow, 0) // new game, so the creation time is right now gameworldIndices.add(world.worldIndex) world.extraFields["basegame.economy"] = GameEconomy() @@ -307,10 +323,17 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { /** Load rest of the game with GL context */ fun postInit() { - //setTheRealGamerFirstTime(PlayerBuilderSigrid()) - setTheRealGamerFirstTime(PlayerBuilderTestSubject1()) + if (actorNowPlaying == null) { + //setTheRealGamerFirstTime(PlayerBuilderSigrid()) + setTheRealGamerFirstTime(PlayerBuilderTestSubject1()) // setTheRealGamerFirstTime(PlayerBuilderWerebeastTest()) + savegameArchive = VDUtil.createNewDisk( + 1L shl 60, + actorNowPlaying!!.actorValue.getAsString(AVKey.NAME) ?: "Player ${AppLoader.getTIME_T()}", + Common.CHARSET + ) + } MegaRainGovernor // invoke MegaRain Governor diff --git a/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt b/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt index 31d0364a2..2a81a4f46 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt @@ -3,6 +3,7 @@ package net.torvald.terrarum.modulebasegame.console import com.badlogic.gdx.utils.Json import net.torvald.terrarum.AppLoader import net.torvald.terrarum.Terrarum +import net.torvald.terrarum.Terrarum.ingame import net.torvald.terrarum.console.ConsoleCommand import net.torvald.terrarum.console.Echo import net.torvald.terrarum.modulebasegame.TerrarumIngame @@ -19,7 +20,8 @@ import java.io.IOException object ExportMeta : ConsoleCommand { override fun execute(args: Array) { try { - val str = WriteMeta(Terrarum.ingame!! as TerrarumIngame).invoke() + val currentPlayTime_t = AppLoader.getTIME_T() - ingame!!.loadedTime_t + val str = WriteMeta(ingame!! as TerrarumIngame, currentPlayTime_t) val writer = java.io.FileWriter(AppLoader.defaultDir + "/Exports/savegame.json", false) writer.write(str) writer.close() @@ -40,7 +42,7 @@ object ExportWorld : ConsoleCommand { override fun execute(args: Array) { if (args.size == 2) { try { - val str = WriteWorld(Terrarum.ingame!! as TerrarumIngame).invoke() + val str = WriteWorld(ingame!! as TerrarumIngame) val writer = java.io.FileWriter(AppLoader.defaultDir + "/Exports/${args[1]}.json", false) writer.write(str) writer.close() diff --git a/src/net/torvald/terrarum/modulebasegame/console/ImportWorld.kt b/src/net/torvald/terrarum/modulebasegame/console/ImportWorld.kt index 2fc6232d8..ee6343d09 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/ImportWorld.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/ImportWorld.kt @@ -18,7 +18,7 @@ object ImportWorld : ConsoleCommand { if (args.size == 2) { try { val reader = java.io.FileReader(AppLoader.defaultDir + "/Exports/${args[1]}.json") - ReadWorld(Terrarum.ingame!! as TerrarumIngame).invoke(reader) + ReadWorld(Terrarum.ingame!! as TerrarumIngame, reader) Echo("Importworld: imported a world from ${args[1]}.json") } catch (e: IOException) { @@ -41,7 +41,7 @@ object ImportActor : ConsoleCommand { if (args.size == 2) { try { val reader = java.io.FileReader(AppLoader.defaultDir + "/Exports/${args[1]}.json") - ReadActor(Terrarum.ingame!! as TerrarumIngame).invoke(reader) + ReadActor(Terrarum.ingame!! as TerrarumIngame, reader) Echo("Importactor: imported an actor from ${args[1]}.json") } catch (e: IOException) { diff --git a/src/net/torvald/terrarum/modulebasegame/console/Load.kt b/src/net/torvald/terrarum/modulebasegame/console/Load.kt index 6786a669d..b8b27c3f9 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/Load.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/Load.kt @@ -23,17 +23,9 @@ object Load : ConsoleCommand { val charset = Common.CHARSET val file = File(AppLoader.defaultDir + "/Exports/${args[1]}") val disk = VDUtil.readDiskArchive(file, charset = charset) + val meta = ReadMeta(disk) - val metaFile = disk.entries[-1]!! - - val metaReader = ByteArray64Reader((metaFile.contents as EntryFile).getContent(), Common.CHARSET) - val meta = Common.jsoner.fromJson(WriteMeta.WorldMeta::class.java, metaReader) - - WriteMeta.WorldMeta::class.declaredMemberProperties.forEach { - println("${it.name} = ${it.get(meta)}") - } - - println(WriteMeta.unasciiAndUnzipStr(meta.blocks)) + meta.blocks.forEach { s, str -> println("Module $s\n"); println(str.doc) } println(meta.loadorder.joinToString()) } catch (e: IOException) { diff --git a/src/net/torvald/terrarum/modulebasegame/console/Save.kt b/src/net/torvald/terrarum/modulebasegame/console/Save.kt index bc211557d..5d387eae4 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/Save.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/Save.kt @@ -11,10 +11,7 @@ import net.torvald.terrarum.gameactors.Actor import net.torvald.terrarum.gameactors.BlockMarkerActor import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.* -import net.torvald.terrarum.serialise.Common -import net.torvald.terrarum.serialise.WriteActor -import net.torvald.terrarum.serialise.WriteMeta -import net.torvald.terrarum.serialise.WriteWorld +import net.torvald.terrarum.serialise.* import java.io.File import java.io.IOException @@ -40,33 +37,11 @@ object Save : ConsoleCommand { try { val ingame = Terrarum.ingame!! as TerrarumIngame val savename = args[1].trim() - val creation_t = VDUtil.currentUnixtime - val time_t = VDUtil.currentUnixtime - val disk = VDUtil.createNewDisk(1L shl 60, savename, Common.CHARSET) + val file = File(AppLoader.defaultDir + "/Exports/${args[1]}") - // NOTE: don't bother with the entryID of DiskEntries; it will be overwritten anyway - val metaContent = EntryFile(WriteMeta(ingame).encodeToByteArray64()) - val meta = DiskEntry(-1, 0, "savegame".toByteArray(), creation_t, time_t, metaContent) - addFile(disk, meta) - - val worldContent = EntryFile(WriteWorld(ingame).encodeToByteArray64()) - val world = DiskEntry(ingame.world.worldIndex, 0, "world${ingame.world.worldIndex}".toByteArray(), creation_t, time_t, worldContent) - addFile(disk, world) - - listOf(ingame.actorContainerActive, ingame.actorContainerInactive).forEach { actors -> - actors.forEach { - if (acceptable(it)) { - val actorContent = EntryFile(WriteActor.encodeToByteArray64(it)) - val actor = DiskEntry(it.referenceID, 0, "actor${it.referenceID}".toByteArray(), creation_t, time_t, actorContent) - addFile(disk, actor) - } - } - } - - disk.capacity = 0 - VDUtil.dumpToRealMachine(disk, File(AppLoader.defaultDir + "/Exports/${args[1]}")) + WriteSavegame(disk, file, ingame) } catch (e: IOException) { Echo("Save: IOException raised.") diff --git a/src/net/torvald/terrarum/modulebasegame/console/SavegameWriterTest.kt b/src/net/torvald/terrarum/modulebasegame/console/SavegameWriterTest.kt deleted file mode 100644 index 77530057f..000000000 --- a/src/net/torvald/terrarum/modulebasegame/console/SavegameWriterTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.torvald.terrarum.modulebasegame.console - -import net.torvald.terrarum.console.ConsoleCommand -import net.torvald.terrarum.console.Echo -import net.torvald.terrarum.console.EchoError -import net.torvald.terrarum.serialise.SavegameWriter - -/** - * Created by minjaesong on 2019-02-22. - */ -internal object SavegameWriterTest: ConsoleCommand { - - override fun execute(args: Array) { - val r = SavegameWriter.invoke(args.getOrNull(1)) - if (!r) { - EchoError("Saving failed") - } - } - - override fun printUsage() { - Echo("savetest [optional out name}") - } -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt index 730e069df..cdf9d5e4c 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt @@ -15,6 +15,7 @@ class IngamePlayer : ActorHumanoid, HasAssembledSprite { override var animDescPath = "invalid" override var animDescPathGlow: String? = null + internal var worldCurrentlyPlaying = 0 // only filled up on save and load; DO NOT USE THIS private constructor() @@ -33,6 +34,7 @@ class IngamePlayer : ActorHumanoid, HasAssembledSprite { referenceID = Terrarum.PLAYER_REF_ID // forcibly set ID density = BASE_DENSITY collisionType = COLLISION_KINEMATIC + worldCurrentlyPlaying = Terrarum.ingame?.world?.worldIndex ?: 0 } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/SavegameWriter.kt b/src/net/torvald/terrarum/serialise/ByteUtils.kt similarity index 52% rename from src/net/torvald/terrarum/serialise/SavegameWriter.kt rename to src/net/torvald/terrarum/serialise/ByteUtils.kt index 122a06533..e1c0c27a7 100644 --- a/src/net/torvald/terrarum/serialise/SavegameWriter.kt +++ b/src/net/torvald/terrarum/serialise/ByteUtils.kt @@ -1,103 +1,5 @@ package net.torvald.terrarum.serialise -import com.badlogic.gdx.Gdx -import net.torvald.random.HQRNG -import net.torvald.terrarum.AppLoader -import net.torvald.terrarum.Terrarum -import net.torvald.terrarum.gameactors.AVKey -import net.torvald.terrarum.gameactors.Actor -import net.torvald.terrarum.gameitem.GameItem -import net.torvald.terrarum.itemproperties.ItemCodex -import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.* -import net.torvald.util.SortedArrayList -import java.io.File -import java.nio.charset.Charset -import kotlin.math.roundToInt - -internal class RNGPool() { - private val RNG = HQRNG() - private val used = SortedArrayList() - - init { - for (i in 0 until 32767) { - used.add(i) - } - } - - fun next(): Int { - var n = RNG.nextLong().ushr(32).toInt() - while (used.contains(n)) { - n = RNG.nextLong().ushr(32).toInt() - } - used.add(n) - return n - } -} - -/** - * Created by minjaesong on 2018-10-03. - */ -object SavegameWriter { - - // TODO create temporary files (worldinfo), create JSON files on RAM, pack those into TEVd as per Savegame container.txt - - private val rngPool = RNGPool() - - private val charset = Charset.forName("UTF-8") - - private lateinit var playerName: String - - operator fun invoke(pnameOverride: String? = null): Boolean { - playerName = pnameOverride ?: "${Terrarum.ingame!!.actorGamer.actorValue[AVKey.NAME]}" - if (playerName.isEmpty()) playerName = "Test subject ${Math.random().times(0x7FFFFFFF).roundToInt()}" - - try { - val diskImage = generateNewDiskImage() - val outFile = File("${AppLoader.defaultSaveDir}/$playerName") - VDUtil.dumpToRealMachine(diskImage, outFile) - - return true - } - catch (e: Throwable) { - e.printStackTrace() - } - - return false - } - - - fun generateNewDiskImage(): VirtualDisk { - val creationDate = System.currentTimeMillis() / 1000L - val ingame = Terrarum.ingame!! - val gameworld = ingame.world - val player = ingame.actorGamer - val disk = VDUtil.createNewDisk(0x7FFFFFFFFFFFFFFFL, "Tesv-$playerName", charset) - val ROOT = disk.root.entryID - - // serialise current world (stage) - - - - // items - /*ItemCodex.dynamicItemDescription.forEach { dynamicID, item -> - VDUtil.registerFile(disk, DiskEntry( - rngPool.next(), ROOT, - dynamicID.toByteArray(charset), - creationDate, creationDate, - EntryFile(serialiseItem(item)) - )) - }*/ - - System.gc() - - return disk - } - - fun modifyExistingSave(savefile: File): VirtualDisk { - TODO() - } -} - fun Int.toLittle() = byteArrayOf( this.and(0xFF).toByte(), this.ushr(8).and(0xFF).toByte(), @@ -171,4 +73,4 @@ fun ByteArray.toLittleInt48() = fun ByteArray.toLittleFloat() = java.lang.Float.intBitsToFloat(this.toLittleInt()) fun Byte.toUlong() = java.lang.Byte.toUnsignedLong(this) -fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this) \ No newline at end of file +fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this) diff --git a/src/net/torvald/terrarum/serialise/Common.kt b/src/net/torvald/terrarum/serialise/Common.kt index 0a6b249e9..4f6f810b4 100644 --- a/src/net/torvald/terrarum/serialise/Common.kt +++ b/src/net/torvald/terrarum/serialise/Common.kt @@ -10,6 +10,7 @@ import net.torvald.terrarum.gameworld.WorldTime import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64Reader import net.torvald.terrarum.tail import net.torvald.terrarum.utils.* import org.apache.commons.codec.digest.DigestUtils @@ -47,7 +48,6 @@ object Common { init { // BigInteger jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer { - override fun write(json: Json, obj: BigInteger?, knownType: Class<*>?) { json.writeValue(obj?.toString()) } @@ -56,9 +56,18 @@ object Common { return BigInteger(jsonData.asString()) } }) + // ZipCodedStr + jsoner.setSerializer(ZipCodedStr::class.java, object : Json.Serializer { + override fun write(json: Json, obj: ZipCodedStr, knownType: Class<*>?) { + json.writeValue(zipStrAndEnascii(obj.doc)) + } + + override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): ZipCodedStr { + return ZipCodedStr(unasciiAndUnzipStr(jsonData.asString())) + } + }) // BlockLayer jsoner.setSerializer(BlockLayer::class.java, object : Json.Serializer { - override fun write(json: Json, obj: BlockLayer, knownType: Class<*>?) { digester.reset() obj.bytesIterator().forEachRemaining { digester.update(it) } @@ -66,9 +75,6 @@ object Common { val layer = LayerInfo(hash, blockLayerToStr(obj), obj.width, obj.height) -// printdbg(this, "pre: ${(0L..1023L).map { obj.ptr[it].tostr() }.joinToString(" ")}") - - json.writeValue(layer) } @@ -270,4 +276,16 @@ object Common { return unzipdBytes } + + /** + * @param [s] a String + * @return UTF-8 encoded [s] which are GZip'd then Ascii85-encoded + */ + fun zipStrAndEnascii(s: String): String { + return Common.bytesToZipdStr(s.toByteArray(Common.CHARSET).iterator()) + } + + fun unasciiAndUnzipStr(s: String): String { + return ByteArray64Reader(strToBytes(StringReader(s)), CHARSET).readText() + } } diff --git a/src/net/torvald/terrarum/serialise/ReadActor.kt b/src/net/torvald/terrarum/serialise/ReadActor.kt deleted file mode 100644 index 8ed49a0b7..000000000 --- a/src/net/torvald/terrarum/serialise/ReadActor.kt +++ /dev/null @@ -1,60 +0,0 @@ -package net.torvald.terrarum.serialise - -import net.torvald.spriteanimation.HasAssembledSprite -import net.torvald.spriteanimation.SpriteAnimation -import net.torvald.terrarum.NoSuchActorWithIDException -import net.torvald.terrarum.gameactors.Actor -import net.torvald.terrarum.gameactors.ActorWithBody -import net.torvald.terrarum.modulebasegame.TerrarumIngame -import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid -import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer -import net.torvald.terrarum.modulebasegame.gameactors.Pocketed -import java.io.InputStream -import java.io.Reader - -/** - * Actor's JSON representation is expected to have "class" property on the root object, such as: - * ``` - * "class":"net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer" - * ``` - * - * Created by minjaesong on 2021-08-27. - */ -class ReadActor(val ingame: TerrarumIngame) { - - open fun invoke(worldDataStream: InputStream) { - postRead(Common.jsoner.fromJson(null, worldDataStream)) - } - - open fun invoke(worldDataStream: Reader) { - postRead(Common.jsoner.fromJson(null, worldDataStream)) - } - - private fun postRead(actor: Actor) { - // filling in Transients - actor.actorValue.actor = actor - - if (actor is Pocketed) - actor.inventory.actor = actor - - if (actor is ActorWithBody) { - actor.sprite = SpriteAnimation(actor) - - if (actor is HasAssembledSprite) { - if (actor.animDescPathGlow != null) actor.spriteGlow = SpriteAnimation(actor) - actor.reassembleSprite(actor.sprite!!, actor.spriteGlow) - } - } - // replace existing player - val oldPlayerID = ingame.actorNowPlaying?.referenceID - try { - ingame.forceRemoveActor(ingame.getActorByID(actor.referenceID)) - } - catch (e: NoSuchActorWithIDException) { /* no actor to delete, you may proceed */ } - ingame.addNewActor(actor) - - if (actor.referenceID == oldPlayerID) - ingame.actorNowPlaying = actor as ActorHumanoid - } - -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/ReadWorld.kt b/src/net/torvald/terrarum/serialise/ReadWorld.kt deleted file mode 100644 index 70b4b9789..000000000 --- a/src/net/torvald/terrarum/serialise/ReadWorld.kt +++ /dev/null @@ -1,29 +0,0 @@ -package net.torvald.terrarum.serialise - -import net.torvald.terrarum.console.Echo -import net.torvald.terrarum.gameworld.GameWorld -import net.torvald.terrarum.modulebasegame.IngameRenderer -import net.torvald.terrarum.modulebasegame.TerrarumIngame -import java.io.InputStream -import java.io.Reader - -/** - * Created by minjaesong on 2021-08-25. - */ -open class ReadWorld(val ingame: TerrarumIngame) { - - open fun invoke(worldDataStream: InputStream) { - postRead(Common.jsoner.fromJson(GameWorld::class.java, worldDataStream)) - } - - open fun invoke(worldDataStream: Reader) { - postRead(Common.jsoner.fromJson(GameWorld::class.java, worldDataStream)) - } - - private fun postRead(world: GameWorld) { - world.postLoad() - - ingame.world = world - } - -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/SavegameLedger.kt b/src/net/torvald/terrarum/serialise/SavegameLedger.kt deleted file mode 100644 index 738f9533d..000000000 --- a/src/net/torvald/terrarum/serialise/SavegameLedger.kt +++ /dev/null @@ -1,33 +0,0 @@ -package net.torvald.terrarum.serialise - -import net.torvald.terrarum.AppLoader -import java.io.File -import java.io.FileInputStream - - - -object SavegameLedger { - - private val SAVE_DIRECTORY = File(AppLoader.defaultSaveDir) - - fun hasSavegameDirectory() = SAVE_DIRECTORY.exists() && SAVE_DIRECTORY.isDirectory - - private fun peekFewBytes(file: File, length: Int): ByteArray { - val buffer = ByteArray(length) - val `is` = FileInputStream(file) - if (`is`.read(buffer) != buffer.size) { - throw InternalError() - } - `is`.close() - return buffer - } - private val MAGIC_TEVD = "TEVd".toByteArray() - - fun getSavefileList(): List? { - return if (!hasSavegameDirectory()) null - else SAVE_DIRECTORY.listFiles().filter { it.isFile && peekFewBytes(it, 4) contentEquals MAGIC_TEVD } - } - - fun getSavefileCount() = getSavefileList()?.count() ?: 0 - -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/SavegameReader.kt b/src/net/torvald/terrarum/serialise/SavegameReader.kt deleted file mode 100644 index 39753f169..000000000 --- a/src/net/torvald/terrarum/serialise/SavegameReader.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.torvald.terrarum.serialise - -/** - * Created by minjaesong on 2018-10-03. - */ -object SavegameReader { - - // TODO read TEVd, load necessary shits - -} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteActor.kt b/src/net/torvald/terrarum/serialise/WriteActor.kt index e9d774220..ff7b25c80 100644 --- a/src/net/torvald/terrarum/serialise/WriteActor.kt +++ b/src/net/torvald/terrarum/serialise/WriteActor.kt @@ -1,8 +1,16 @@ package net.torvald.terrarum.serialise +import net.torvald.spriteanimation.HasAssembledSprite +import net.torvald.spriteanimation.SpriteAnimation +import net.torvald.terrarum.NoSuchActorWithIDException import net.torvald.terrarum.gameactors.Actor +import net.torvald.terrarum.gameactors.ActorWithBody +import net.torvald.terrarum.modulebasegame.TerrarumIngame +import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid +import net.torvald.terrarum.modulebasegame.gameactors.Pocketed import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64Writer +import java.io.Reader /** * Created by minjaesong on 2021-08-24. @@ -23,4 +31,54 @@ object WriteActor { return baw.toByteArray64() } +} + + + +/** + * Actor's JSON representation is expected to have "class" property on the root object, such as: + * ``` + * "class":"net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer" + * ``` + * + * Created by minjaesong on 2021-08-27. + */ +object ReadActor { + + fun readActorOnly(worldDataStream: Reader): Actor = + Common.jsoner.fromJson(null, worldDataStream) + + operator fun invoke(ingame: TerrarumIngame, worldDataStream: Reader): Actor = + postRead(ingame, readActorOnly(worldDataStream)) + + private fun postRead(ingame: TerrarumIngame, actor: Actor): Actor { + // filling in Transients + actor.actorValue.actor = actor + + if (actor is Pocketed) + actor.inventory.actor = actor + + if (actor is ActorWithBody) { + actor.sprite = SpriteAnimation(actor) + + if (actor is HasAssembledSprite) { + if (actor.animDescPathGlow != null) actor.spriteGlow = SpriteAnimation(actor) + actor.reassembleSprite(actor.sprite!!, actor.spriteGlow) + } + } + // replace existing player + val oldPlayerID = ingame.actorNowPlaying?.referenceID + try { + ingame.forceRemoveActor(ingame.getActorByID(actor.referenceID)) + } + catch (e: NoSuchActorWithIDException) { /* no actor to delete, you may proceed */ } + ingame.addNewActor(actor) + + if (actor.referenceID == oldPlayerID) + ingame.actorNowPlaying = actor as ActorHumanoid + + + return actor + } + } \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteMeta.kt b/src/net/torvald/terrarum/serialise/WriteMeta.kt index c2d363093..146c6cdb8 100644 --- a/src/net/torvald/terrarum/serialise/WriteMeta.kt +++ b/src/net/torvald/terrarum/serialise/WriteMeta.kt @@ -6,17 +6,21 @@ import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64Reader +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.EntryFile +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk +import net.torvald.terrarum.utils.MetaModuleCSVPair +import net.torvald.terrarum.utils.ZipCodedStr import net.torvald.terrarum.weather.WeatherMixer import java.io.StringReader /** * Created by minjaesong on 2021-08-23. */ -open class WriteMeta(val ingame: TerrarumIngame) { +object WriteMeta { - open fun invoke(): String { + operator fun invoke(ingame: TerrarumIngame, currentPlayTime_t: Long): String { val world = ingame.world - + val meta = WorldMeta( genver = Common.GENVER, savename = world.worldName, @@ -28,34 +32,26 @@ open class WriteMeta(val ingame: TerrarumIngame) { playerid = ingame.actorGamer.referenceID, creation_t = world.creationTime, lastplay_t = world.lastPlayTime, - playtime_t = world.totalPlayTime, - blocks = StringBuilder().let { - ModMgr.getFilesFromEveryMod("blocks/blocks.csv").forEach { (modname, file) -> - it.append(modnameToOrnamentalHeader(modname)) - it.append(file.readText()) - } - zipStrAndEnascii(it.toString()) + playtime_t = world.totalPlayTime + currentPlayTime_t, + blocks = ModMgr.getFilesFromEveryMod("blocks/blocks.csv").fold(MetaModuleCSVPair()) { + map, (modname, file) -> + map[modname] = ZipCodedStr(file.readText ()) + /*return*/map }, - items = StringBuilder().let { - ModMgr.getFilesFromEveryMod("items/itemid.csv").forEach { (modname, file) -> - it.append(modnameToOrnamentalHeader(modname)) - it.append(file.readText()) - } - zipStrAndEnascii(it.toString()) + items = ModMgr.getFilesFromEveryMod("items/itemid.csv").fold(MetaModuleCSVPair()) { + map, (modname, file) -> + map[modname] = ZipCodedStr(file.readText ()) + /*return*/map }, - wires = StringBuilder().let { - ModMgr.getFilesFromEveryMod("wires/wires.csv").forEach { (modname, file) -> - it.append(modnameToOrnamentalHeader(modname)) - it.append(file.readText()) - } - zipStrAndEnascii(it.toString()) + wires = ModMgr.getFilesFromEveryMod("wires/wires.csv").fold(MetaModuleCSVPair()) { + map, (modname, file) -> + map[modname] = ZipCodedStr(file.readText ()) + /*return*/map }, - materials = StringBuilder().let { - ModMgr.getFilesFromEveryMod("materials/materials.csv").forEach { (modname, file) -> - it.append(modnameToOrnamentalHeader(modname)) - it.append(file.readText()) - } - zipStrAndEnascii(it.toString()) + materials = ModMgr.getFilesFromEveryMod("materials/materials.csv").fold(MetaModuleCSVPair()) { + map, (modname, file) -> + map[modname] = ZipCodedStr(file.readText ()) + /*return*/map }, loadorder = ModMgr.loadOrder.toTypedArray(), worlds = ingame.gameworldIndices.toTypedArray() @@ -64,9 +60,9 @@ open class WriteMeta(val ingame: TerrarumIngame) { return Common.jsoner.toJson(meta) } - fun encodeToByteArray64(): ByteArray64 { + fun encodeToByteArray64(ingame: TerrarumIngame, currentPlayTime_t: Long): ByteArray64 { val ba = ByteArray64() - this.invoke().toByteArray(Common.CHARSET).forEach { ba.add(it) } + this.invoke(ingame, currentPlayTime_t).toByteArray(Common.CHARSET).forEach { ba.add(it) } return ba } @@ -82,10 +78,10 @@ open class WriteMeta(val ingame: TerrarumIngame) { val creation_t: Long = 0, val lastplay_t: Long = 0, val playtime_t: Long = 0, - val blocks: String = "", - val items: String = "", - val wires: String = "", - val materials: String = "", + val blocks: MetaModuleCSVPair = MetaModuleCSVPair(), + val items: MetaModuleCSVPair = MetaModuleCSVPair(), + val wires: MetaModuleCSVPair = MetaModuleCSVPair(), + val materials: MetaModuleCSVPair = MetaModuleCSVPair(), val loadorder: Array = arrayOf(), // do not use list; Could not instantiate instance of class: java.util.Collections$SingletonList val worlds: Array = arrayOf() // do not use list; Could not instantiate instance of class: java.util.Collections$SingletonList ) { @@ -94,23 +90,22 @@ open class WriteMeta(val ingame: TerrarumIngame) { } } - companion object { - private fun modnameToOrnamentalHeader(s: String) = - "\n\n${"#".repeat(16 + s.length)}\n" + - "## module: $s ##\n" + - "${"#".repeat(16 + s.length)}\n\n" - - /** - * @param [s] a String - * @return UTF-8 encoded [s] which are GZip'd then Ascii85-encoded - */ - fun zipStrAndEnascii(s: String): String { - return Common.bytesToZipdStr(s.toByteArray(Common.CHARSET).iterator()) - } - - fun unasciiAndUnzipStr(s: String): String { - return ByteArray64Reader(Common.strToBytes(StringReader(s)), Common.CHARSET).readText() - } - } + private fun modnameToOrnamentalHeader(s: String) = + "\n\n${"#".repeat(16 + s.length)}\n" + + "## module: $s ##\n" + + "${"#".repeat(16 + s.length)}\n\n" } + +/** + * Created by minjaesong on 2021-09-03. + */ +object ReadMeta { + + operator fun invoke(savefile: VirtualDisk): WriteMeta.WorldMeta { + val metaFile = savefile.entries[-1]!! + val metaReader = ByteArray64Reader((metaFile.contents as EntryFile).getContent(), Common.CHARSET) + return Common.jsoner.fromJson(WriteMeta.WorldMeta::class.java, metaReader) + } + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteSavegame.kt b/src/net/torvald/terrarum/serialise/WriteSavegame.kt new file mode 100644 index 000000000..5b209e22d --- /dev/null +++ b/src/net/torvald/terrarum/serialise/WriteSavegame.kt @@ -0,0 +1,98 @@ +package net.torvald.terrarum.serialise + +import net.torvald.terrarum.* +import net.torvald.terrarum.gameactors.Actor +import net.torvald.terrarum.gameactors.BlockMarkerActor +import net.torvald.terrarum.modulebasegame.TerrarumIngame +import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.* +import net.torvald.terrarum.serialise.WriteWorld.actorAcceptable +import java.io.File + +/** + * It's your responsibility to create a new VirtualDisk if your save is new, and create a backup for modifying existing save. + * + * Created by minjaesong on 2021-09-03. + */ +object WriteSavegame { + + /** + * Will happily overwrite existing entry + */ + private fun addFile(disk: VirtualDisk, file: DiskEntry) { + disk.entries[file.entryID] = file + file.parentEntryID = 0 + val dir = VDUtil.getAsDirectory(disk, 0) + if (!dir.contains(file.entryID)) dir.add(file.entryID) + } + + operator fun invoke(disk: VirtualDisk, outFile: File, ingame: TerrarumIngame) { + val creation_t = ingame.world.creationTime + val time_t = AppLoader.getTIME_T() + val currentPlayTime_t = time_t - ingame.loadedTime_t + + + // Write Meta // + val metaContent = EntryFile(WriteMeta.encodeToByteArray64(ingame, currentPlayTime_t)) + val meta = DiskEntry(-1, 0, "savegame".toByteArray(), creation_t, time_t, metaContent) + addFile(disk, meta) + + // Write World // + val worldNum = ingame.world.worldIndex + val worldContent = EntryFile(WriteWorld.encodeToByteArray64(ingame)) + val world = DiskEntry(worldNum, 0, "world${worldNum}".toByteArray(), creation_t, time_t, worldContent) + addFile(disk, world) + + // Write Actors // + listOf(ingame.actorContainerActive, ingame.actorContainerInactive).forEach { actors -> + actors.forEach { + if (actorAcceptable(it)) { + val actorContent = EntryFile(WriteActor.encodeToByteArray64(it)) + val actor = DiskEntry(it.referenceID, 0, "actor${it.referenceID}".toByteArray(), creation_t, time_t, actorContent) + addFile(disk, actor) + } + } + } + + disk.capacity = 0 + VDUtil.dumpToRealMachine(disk, outFile) + } +} + + + +/** + * 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 { + + operator fun invoke(disk: VirtualDisk) { + val meta = ReadMeta(disk) + val player = ReadActor.readActorOnly( + ByteArray64Reader(VDUtil.getAsNormalFile(disk, 9545698).getContent(), Common.CHARSET) + ) as IngamePlayer + val world = ReadWorld.readWorldOnly( + ByteArray64Reader(VDUtil.getAsNormalFile(disk, player.worldCurrentlyPlaying).getContent(), Common.CHARSET) + ) + val actors = world.actors.map { + ReadActor.readActorOnly(ByteArray64Reader(VDUtil.getAsNormalFile(disk, it).getContent(), Common.CHARSET)) + } + + val ingame = TerrarumIngame(AppLoader.batch) + val worldParam = meta + ingame.world = world + ingame.gameLoadInfoPayload = worldParam + ingame.gameLoadMode = TerrarumIngame.GameLoadMode.LOAD_FROM + ingame.savegameArchive = disk + actors.forEach { ingame.addNewActor(it) } + ingame.actorNowPlaying = player + + Terrarum.setCurrentIngameInstance(ingame) + val loadScreen = SanicLoadScreen + AppLoader.setLoadScreen(loadScreen) + } +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/serialise/WriteWorld.kt b/src/net/torvald/terrarum/serialise/WriteWorld.kt index d73a72377..23cd3b3d5 100644 --- a/src/net/torvald/terrarum/serialise/WriteWorld.kt +++ b/src/net/torvald/terrarum/serialise/WriteWorld.kt @@ -1,32 +1,69 @@ package net.torvald.terrarum.serialise +import net.torvald.terrarum.CommonResourcePool +import net.torvald.terrarum.ReferencingRanges +import net.torvald.terrarum.gameactors.Actor +import net.torvald.terrarum.gameactors.BlockMarkerActor +import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64 import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64Writer +import java.io.Reader /** * Created by minjaesong on 2021-08-23. */ -open class WriteWorld(val ingame: TerrarumIngame) { +object WriteWorld { - open fun invoke(): String { - val world = ingame.world - world.genver = Common.GENVER - world.comp = Common.COMP_GZIP - return Common.jsoner.toJson(world) + fun actorAcceptable(actor: Actor): Boolean { + return actor.referenceID !in ReferencingRanges.ACTORS_WIRES && + actor.referenceID !in ReferencingRanges.ACTORS_WIRES_HELPER && + actor != (CommonResourcePool.get("blockmarking_actor") as BlockMarkerActor) } - fun encodeToByteArray64(): ByteArray64 { + private fun preWrite(ingame: TerrarumIngame): GameWorld { val world = ingame.world world.genver = Common.GENVER world.comp = Common.COMP_GZIP + ingame.actorContainerActive.filter { actorAcceptable(it) }.forEach { world.actors.add(it.referenceID) } + ingame.actorContainerInactive.filter { actorAcceptable(it) }.forEach { world.actors.add(it.referenceID) } + return world + } + + operator fun invoke(ingame: TerrarumIngame): String { + return Common.jsoner.toJson(preWrite(ingame)) + } + + fun encodeToByteArray64(ingame: TerrarumIngame): ByteArray64 { val baw = ByteArray64Writer(Common.CHARSET) - Common.jsoner.toJson(world, baw) + Common.jsoner.toJson(preWrite(ingame), baw) baw.flush(); baw.close() return baw.toByteArray64() } } + + + +/** + * Created by minjaesong on 2021-08-25. + */ +object ReadWorld { + + fun readWorldOnly(worldDataStream: Reader): GameWorld = + Common.jsoner.fromJson(GameWorld::class.java, worldDataStream) + + operator fun invoke(ingame: TerrarumIngame, worldDataStream: Reader): GameWorld = + postRead(ingame, readWorldOnly(worldDataStream)) + + private fun postRead(ingame: TerrarumIngame, world: GameWorld): GameWorld { + world.postLoad() + ingame.world = world + + return world + } + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/utils/HashArray.kt b/src/net/torvald/terrarum/utils/HashArray.kt index a6e0c37db..1e14019d1 100644 --- a/src/net/torvald/terrarum/utils/HashArray.kt +++ b/src/net/torvald/terrarum/utils/HashArray.kt @@ -1,10 +1,15 @@ package net.torvald.terrarum.utils +import com.badlogic.gdx.utils.Json +import com.badlogic.gdx.utils.JsonValue import net.torvald.terrarum.gameitem.ItemID import net.torvald.terrarum.gameworld.BlockAddress import net.torvald.terrarum.gameworld.FluidType import net.torvald.terrarum.gameworld.GameWorld +import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64Reader +import net.torvald.terrarum.serialise.Common +import java.io.StringReader /** * Created by minjaesong on 2021-08-26. @@ -16,3 +21,14 @@ class WiringGraphMap: HashMap() class HashedFluidType: HashMap() class HashedWirings: HashMap() class HashedWiringGraph: HashMap() +class MetaModuleCSVPair: HashMap() + +/** + * @param doc plaintext + * + * Note: the content of the class is only encoded on serialisation; when the class is deserialised, this + * class always holds plaintext. + */ +@JvmInline value class ZipCodedStr(val doc: String = "") { + override fun toString() = doc +} diff --git a/work_files/DataFormats/just-json-it-saveformat.md b/work_files/DataFormats/just-json-it-saveformat.md index 14953dcf5..67faea77e 100644 --- a/work_files/DataFormats/just-json-it-saveformat.md +++ b/work_files/DataFormats/just-json-it-saveformat.md @@ -1,4 +1,37 @@ -Following code is an example savegame JSON files. +## Savegame Structure + +- The Savegame is a TerranVirtualDisk archive that stores multiple files in the disk's root directory +- Savegame stores metadata, Worlds and Actors in the game +- A player gets one unique Savegame +- A player can have Multiple worlds + - Worlds are identified using integer ranged 1 through 32767 (inclusive) +- Actor ID is unique within the scope of the Savegame + - A World stores list of Actor IDs that resides in the world + + +### File Structure + +Each file on the Savegame has following convention: + +|Type|Filename|ID| +|--|--|--| +|Metadata|savegame|-1| +|Worlds|world$n ($n is a world index)|$n| +|Actors|actor$n ($n is an Actor ID)|$n| + + +### Solving Problems + +#### How do I determine which world to read in? + +Load the player (always has the entry ID of 9545698) and the property "worldCurrentlyPlaying" should +contain an integer that is a world index. Only the actors that are instance of IngamePlayer will have +the property. + + +### Save File Examples + +Following code is an example Savegame JSON files. #### savegame.json ```