diff --git a/SAVE_FORMAT.md b/SAVE_FORMAT.md index e5ae9314b..81fc0565b 100644 --- a/SAVE_FORMAT.md +++ b/SAVE_FORMAT.md @@ -7,7 +7,7 @@ The main game directory is composed of following directories: ``` .Terrarum + Players - - "${PlayerName}-${UUID}", TVDA { + - "${PlayerName}-${UUID}", TVDA { [-1] player JSON, [-2] spritedef, [-3] optional spritedef-glow, @@ -30,6 +30,8 @@ The main game directory is composed of following directories: (TEVD stands for Terrarum Virtual Disk spec version 3, TVDA stands for spec version 254; both have MAGIC header of `TEVd`) +Do not rely on filename to look for a world; players can change the filename + ## Handling The Player Data Some of the "player assets" are stored to the world, such assets include: @@ -48,6 +50,8 @@ If the World has the Actorvalue, World's value will be used; otherwise use incom Multiplayer world will initialise Actorvalue pool using incoming value -- or they may choose to use their own Actorvalue called "gamerules" to either implement their own "gamemode" or prevent cheating) +For Singleplayer, only the xy-position is saved to the world for later load. + Worlds must overwrite new Actor's position to make them spawn in right place. ### Remarks diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index 33cb96dc6..867b98a22 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -55,13 +55,6 @@ public class App implements ApplicationListener { public static final String GAME_NAME = TerrarumAppConfiguration.GAME_NAME; - // is this jvm good? - static { - if (System.getProperty("sun.arch.data.model") == null || System.getProperty("sun.arch.data.model").equals("unknown")) { - System.err.println("Error: Your JVM is not supported by the application.\nPlease install the desired version."); - System.exit(1); - } - } public static final int VERSION_RAW = TerrarumAppConfiguration.VERSION_RAW; @@ -195,7 +188,8 @@ public class App implements ApplicationListener { /** * Sorted by the lastplaytime, in reverse order (index 0 is the most recent game played) */ - public static ArrayList savegames = new ArrayList<>(); + public static TreeMap savegameWorlds = new TreeMap<>(); + public static TreeMap savegamePlayers = new TreeMap<>(); public static void updateListOfSavegames() { AppUpdateListOfSavegames(); @@ -711,6 +705,7 @@ public class App implements ApplicationListener { //MinimapComposer.INSTANCE.dispose(); //FloatDrawer.INSTANCE.dispose(); + ThreadExecutor.INSTANCE.killAll(); ditherPattern.dispose(); shaderBayerSkyboxFill.dispose(); diff --git a/src/net/torvald/terrarum/IngameInstance.kt b/src/net/torvald/terrarum/IngameInstance.kt index bf5b528e1..11fbe3562 100644 --- a/src/net/torvald/terrarum/IngameInstance.kt +++ b/src/net/torvald/terrarum/IngameInstance.kt @@ -30,7 +30,7 @@ import java.util.concurrent.locks.Lock * Although the game (as product) can have infinitely many stages/planets/etc., those stages must be manually managed by YOU; * this instance only stores the stage that is currently being used. */ -open class IngameInstance(val batch: SpriteBatch) : Screen { +open class IngameInstance(val batch: SpriteBatch, val isMultiplayer: Boolean = false) : Screen { open protected val actorMBRConverter = object : MBRConverter { override fun getDimensions(): Int = 2 @@ -127,10 +127,6 @@ open class IngameInstance(val batch: SpriteBatch) : Screen { val modifiedChunks = Array(16) { TreeSet() } - internal var creationTime = App.getTIME_T() // cumulative value for the savegame - internal var lastPlayTime = App.getTIME_T() // cumulative value for the savegame - internal var totalPlayTime = 0L // cumulative value for the savegame - var loadedTime_t = App.getTIME_T() protected set diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index 1689f7eca..4e11dab3d 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -10,25 +10,24 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.graphics.glutils.ShapeRenderer import com.badlogic.gdx.utils.Disposable +import com.badlogic.gdx.utils.JsonReader import com.jme3.math.FastMath import net.torvald.UnsafeHelper import net.torvald.gdx.graphics.Cvec import net.torvald.random.HQRNG import net.torvald.terrarum.App.* -import net.torvald.terrarum.Terrarum.PLAYER_REF_ID import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.blockproperties.BlockCodex import net.torvald.terrarum.blockproperties.WireCodex import net.torvald.terrarum.gameactors.Actor import net.torvald.terrarum.gameactors.ActorID import net.torvald.terrarum.gameactors.faction.FactionCodex -import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.fmod import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.itemproperties.MaterialCodex -import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer -import net.torvald.terrarum.serialise.* -import net.torvald.terrarum.tvda.* +import net.torvald.terrarum.serialise.Common +import net.torvald.terrarum.tvda.ByteArray64Reader +import net.torvald.terrarum.tvda.DiskSkimmer import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.worlddrawer.WorldCamera import net.torvald.terrarumsansbitmap.gdx.GameFontBase @@ -36,6 +35,7 @@ import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.util.CircularArray import java.io.File import java.io.PrintStream +import java.util.* import kotlin.math.absoluteValue import kotlin.math.round @@ -681,8 +681,11 @@ class Codex : KVHashMap() { } fun AppUpdateListOfSavegames() { - App.savegames.clear() - File(App.saveDir).listFiles().filter { !it.isDirectory && !it.name.contains('.') }.map { file -> + App.savegamePlayers.clear() + App.savegameWorlds.clear() + + // create list of worlds + (File(App.worldsDir).listFiles().filter { !it.isDirectory && !it.name.contains('.') }.map { file -> try { DiskSkimmer(file, Common.CHARSET, true) } @@ -691,9 +694,14 @@ fun AppUpdateListOfSavegames() { e.printStackTrace() null } - }.filter { it != null }.sortedByDescending { it!!.diskFile.lastModified() }.forEach { - App.savegames.add(it!!) + }.filter { it != null }.sortedByDescending { it!!.diskFile.lastModified() } as List).forEach { + // TODO write simple and dumb SAX parser for JSON + val json = JsonReader().parse(ByteArray64Reader(it.getFile(-1L)!!.bytes, Common.CHARSET).readText()) + val worldUUID = UUID.fromString(json.get("worldIndex")!!.asString()) + App.savegameWorlds[worldUUID] = it } + + // TODO: savegamePlayers } /** @@ -708,7 +716,7 @@ fun checkForSavegameDamage(skimmer: DiskSkimmer): Boolean { // # check if: // the world The Player is at actually exists // all the actors for the world actually exists - // TODO might want SAX parser for JSON + // TODO SAX parser for JSON -- use JsonReader().parse() for now... val currentWorld = (player as EntryFile).bytes.let { (ReadActor.readActorBare(ByteArray64Reader(it, Common.CHARSET)) as? IngamePlayer ?: return true).worldCurrentlyPlaying diff --git a/src/net/torvald/terrarum/TitleScreen.kt b/src/net/torvald/terrarum/TitleScreen.kt index f19c55b42..78f75e8b7 100644 --- a/src/net/torvald/terrarum/TitleScreen.kt +++ b/src/net/torvald/terrarum/TitleScreen.kt @@ -203,7 +203,7 @@ class TitleScreen(batch: SpriteBatch) : IngameInstance(batch) { uiContainer.add(uiFakeBlurOverlay) - uiRemoCon = UIRemoCon(this, UITitleRemoConYaml(App.savegames.isNotEmpty())) + uiRemoCon = UIRemoCon(this, UITitleRemoConYaml(App.savegamePlayers.isNotEmpty())) uiRemoCon.setPosition(0, 0) uiRemoCon.setAsOpen() diff --git a/src/net/torvald/terrarum/concurrent/ThreadExecutor.kt b/src/net/torvald/terrarum/concurrent/ThreadExecutor.kt index 3dfbb5fe5..524a17d04 100644 --- a/src/net/torvald/terrarum/concurrent/ThreadExecutor.kt +++ b/src/net/torvald/terrarum/concurrent/ThreadExecutor.kt @@ -71,6 +71,9 @@ object ThreadExecutor { allFinished = true } + fun killAll() { + executor.shutdownNow() + } } /** diff --git a/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt b/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt index 61ffc4503..b28524f79 100644 --- a/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt +++ b/src/net/torvald/terrarum/debuggerapp/SavegameCracker.kt @@ -59,7 +59,7 @@ class SavegameCracker( private val cc0 = colourCodes[0] private val prompt: String - get() = "$ccConst${disk?.getDiskNameString(charset) ?: ""}$cc0% " + get() = "$ccConst${disk?.getDiskName(charset) ?: ""}$cc0% " private val cmds: HashMap> = HashMap() init { diff --git a/src/net/torvald/terrarum/gameactors/Actor.kt b/src/net/torvald/terrarum/gameactors/Actor.kt index 4f18b0cd3..1bdff86ff 100644 --- a/src/net/torvald/terrarum/gameactors/Actor.kt +++ b/src/net/torvald/terrarum/gameactors/Actor.kt @@ -18,6 +18,12 @@ abstract class Actor : Comparable, Runnable { * @return Reference ID. (16777216-0x7FFF_FFFF) */ open var referenceID: ActorID = 0 // in old time this was nullable without initialiser. If you're going to revert to that, add the reason why this should be nullable. + + /** + * RenderOrder does not affect ReferenceID "too much" (ID generation will still depend on it, but it's just because of ye olde tradition by now) + * + * IngameRenderer will only look for RenderOrder and won't look for referenceID, so if you want to change the RenderOrder, just modify this field and not the referenceID. + */ var renderOrder = RenderOrder.MIDDLE protected constructor() diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index 40fef96d9..917ad8377 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -23,12 +23,10 @@ typealias BlockAddress = Long class PhysicalStatus() { // bottom-center point var position = Point2d() - // some actorvalues - var scale = 1.0 + // actorvalues are copied separately so don't worry about them here constructor(player: IngamePlayer) : this() { this.position = Point2d(player.hitbox.canonicalX, player.hitbox.canonicalY) - this.scale = player.avBaseScale } } @@ -50,6 +48,12 @@ open class GameWorld() : Disposable { var playersLastStatus = PlayersLastStatus() // only gets used when the game saves and loads + /** + * 0,1 - RoguelikeRandomiser + * 2,3 - WeatherMixer + */ + val randSeeds = LongArray(256) // stores 128 128-bit numbers + /** Creation time for this world, NOT the entire savegame */ internal var creationTime: Long = App.getTIME_T() internal set diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt index 611df1510..c42bc481b 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt @@ -7,7 +7,6 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch import net.torvald.EMDASH import net.torvald.terrarum.* import net.torvald.terrarum.App.* -import net.torvald.terrarum.Terrarum.PLAYER_REF_ID import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZED import net.torvald.terrarum.blockproperties.BlockPropUtil @@ -24,7 +23,6 @@ import net.torvald.terrarum.gameitem.inInteractableRange import net.torvald.terrarum.gameparticles.ParticleBase import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.WorldSimulator -import net.torvald.terrarum.itemproperties.ItemCodex import net.torvald.terrarum.modulebasegame.gameactors.* import net.torvald.terrarum.modulebasegame.gameactors.physicssolver.CollisionSolver import net.torvald.terrarum.modulebasegame.gameitems.PickaxeCore @@ -34,9 +32,12 @@ import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser import net.torvald.terrarum.modulebasegame.worldgenerator.Worldgen import net.torvald.terrarum.modulebasegame.worldgenerator.WorldgenParams import net.torvald.terrarum.realestate.LandUtil -import net.torvald.terrarum.serialise.* +import net.torvald.terrarum.serialise.Common +import net.torvald.terrarum.serialise.LoadSavegame +import net.torvald.terrarum.serialise.ReadActor +import net.torvald.terrarum.serialise.WriteSavegame +import net.torvald.terrarum.tvda.DiskSkimmer import net.torvald.terrarum.tvda.VDUtil -import net.torvald.terrarum.tvda.VirtualDisk import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.UIAutosaveNotifier import net.torvald.terrarum.ui.UICanvas @@ -249,15 +250,17 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { } data class Codices( - val disk: VirtualDisk, - val meta: WriteMeta.WorldMeta, + val disk: DiskSkimmer, // WORLD disk + val world: GameWorld, +// val meta: WriteMeta.WorldMeta, // val block: BlockCodex, - val item: ItemCodex, +// val item: ItemCodex, // val wire: WireCodex, // val material: MaterialCodex, // val faction: FactionCodex, - val apocryphas: Map, - val actors: List +// val apocryphas: Map, + val actors: List, + val player: IngamePlayer ) @@ -272,13 +275,13 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { else { printdbg(this, "Ingame setting things up from the savegame") - RoguelikeRandomiser.loadFromSave(codices.meta.randseed0, codices.meta.randseed1) - WeatherMixer.loadFromSave(codices.meta.weatseed0, codices.meta.weatseed1) + RoguelikeRandomiser.loadFromSave(codices.world.randSeeds[0], codices.world.randSeeds[1]) + WeatherMixer.loadFromSave(codices.world.randSeeds[2], codices.world.randSeeds[3]) - Terrarum.itemCodex.loadFromSave(codices.item) - Terrarum.apocryphas = HashMap(codices.apocryphas) +// Terrarum.itemCodex.loadFromSave(codices.item) +// Terrarum.apocryphas = HashMap(codices.apocryphas) - savegameNickname = codices.disk.getDiskNameString(Common.CHARSET) + savegameNickname = codices.disk.getDiskName(Common.CHARSET) } } @@ -287,18 +290,36 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { codices.actors.forEach { try { val actor = ReadActor(codices.disk, LoadSavegame.getFileReader(codices.disk, it.toLong())) - addNewActor(actor) + if (actor !is IngamePlayer) { // actor list should not contain IngamePlayers (see WriteWorld.preWrite) but just in case... + addNewActor(actor) + } } catch (e: NullPointerException) { System.err.println("Could not read the actor ${it} from the disk") e.printStackTrace() - if (it == PLAYER_REF_ID) throw e -// throw e // if not player, don't rethrow -- let players play the corrupted world if it loads, they'll be able to cope with their losses even though there will be buncha lone actorblocks lying around... } } + // assign new random referenceID for player + codices.player.referenceID = Terrarum.generateUniqueReferenceID(Actor.RenderOrder.MIDDLE) + addNewActor(codices.player) + + + // overwrite player's props with world's for multiplayer + // see comments on IngamePlayer.unauthorisedPlayerProps to know why this is necessary. + codices.player.backupPlayerProps(isMultiplayer) // backup first! + world.playersLastStatus[codices.player.uuid]?.let { // if nothing was saved, nothing would happen and we still keep the backup, which WriteActor looks for it + codices.player.setPosition(it.physics.position) + if (isMultiplayer) { + codices.player.actorValue = it.actorValue!! + codices.player.inventory = it.inventory!! + } + } + + // by doing this, whatever the "possession" the player had will be broken by the game load - actorNowPlaying = getActorByID(Terrarum.PLAYER_REF_ID) as IngamePlayer + actorNowPlaying = codices.player + actorGamer = codices.player makeSavegameBackupCopy() // don't put it on the postInit() or render(); postInitForNewGame calls this function on the savegamewriter's callback } @@ -332,10 +353,11 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) { // 1. lighten the IO burden // 2. cannot sync up the "counter" to determine whether both are finished uiAutosaveNotifier.setAsOpen() - WriteSavegame.immediate(WriteSavegame.SaveMode.PLAYER, playerDisk, getPlayerSaveFiledesc(playerSavefileName), this, false, true) { + val saveTime_t = App.getTIME_T() + WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.PLAYER, playerDisk, getPlayerSaveFiledesc(playerSavefileName), this, false, true) { makeSavegameBackupCopy(getPlayerSaveFiledesc(playerSavefileName)) - WriteSavegame.immediate(WriteSavegame.SaveMode.WORLD, worldDisk, getWorldSaveFiledesc(worldSavefileName), this, false, true) { + WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.WORLD, worldDisk, getWorldSaveFiledesc(worldSavefileName), this, false, true) { makeSavegameBackupCopy(getWorldSaveFiledesc(worldSavefileName)) // don't put it on the postInit() or render(); must be called using callback uiAutosaveNotifier.setAsClose() } diff --git a/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt b/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt index ddcc3453e..200ed17c1 100644 --- a/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt +++ b/src/net/torvald/terrarum/modulebasegame/console/ExportMeta.kt @@ -8,17 +8,16 @@ import net.torvald.terrarum.console.Echo import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer import net.torvald.terrarum.serialise.WriteActor -import net.torvald.terrarum.serialise.WriteMeta import net.torvald.terrarum.serialise.WriteWorld import java.io.IOException /** * Created by minjaesong on 2017-07-18. */ -object ExportMeta : ConsoleCommand { +/*object ExportMeta : ConsoleCommand { override fun execute(args: Array) { try { - val str = WriteMeta(ingame!! as TerrarumIngame, App.getTIME_T()) + val str = net.torvald.terrarum.serialise.WriteMeta(ingame!! as TerrarumIngame, App.getTIME_T()) val writer = java.io.FileWriter(App.defaultDir + "/Exports/savegame.json", false) writer.write(str) writer.close() @@ -33,7 +32,7 @@ object ExportMeta : ConsoleCommand { override fun printUsage() { Echo("Usage: Exportmeta") } -} +}*/ object ExportWorld : ConsoleCommand { override fun execute(args: Array) { diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt index a42fe1681..78919f956 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/IngamePlayer.kt @@ -2,13 +2,14 @@ package net.torvald.terrarum.modulebasegame.gameactors import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Texture -import net.torvald.spriteanimation.HasAssembledSprite import net.torvald.spriteanimation.SpriteAnimation import net.torvald.spriteassembler.ADProperties import net.torvald.spriteassembler.AssembleSheetPixmap +import net.torvald.terrarum.App import net.torvald.terrarum.Terrarum import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.tvda.SimpleFileSystem +import net.torvald.terrarum.utils.PlayerLastStatus import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import java.util.* @@ -21,7 +22,11 @@ import java.util.* class IngamePlayer : ActorHumanoid { - var uuid = UUID.randomUUID(); private set + val creationTime = App.getTIME_T() + var lastPlayTime = App.getTIME_T() // cumulative value for the savegame + var totalPlayTime = 0L // cumulative value for the savegame + + val uuid = UUID.randomUUID() var worldCurrentlyPlaying: UUID = UUID(0L,0L) // only filled up on save and load; DO NOT USE THIS /** ADL for main sprite. Necessary. */ @@ -50,6 +55,17 @@ class IngamePlayer : ActorHumanoid { } + /** Copy of some of the player's props before get overwritten by the props saved in the world. + * + * This field is only there for loading multiplayer map on singleplayer instances where the world loader would + * permanently changing player's props into multiplayer world's. + */ + @Transient internal lateinit var unauthorisedPlayerProps: PlayerLastStatus + + fun backupPlayerProps(isMultiplayer: Boolean) { + unauthorisedPlayerProps = PlayerLastStatus(this, isMultiplayer) + } + /** diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UILoadDemoSavefiles.kt b/src/net/torvald/terrarum/modulebasegame/ui/UILoadDemoSavefiles.kt index 396ef7d0f..6800bb65e 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UILoadDemoSavefiles.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UILoadDemoSavefiles.kt @@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.graphics.glutils.FrameBuffer import com.badlogic.gdx.graphics.glutils.ShapeRenderer +import com.badlogic.gdx.utils.JsonReader import net.torvald.getKeycapConsole import net.torvald.getKeycapPC import net.torvald.terrarum.* @@ -14,8 +15,6 @@ import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.LoadSavegame -import net.torvald.terrarum.serialise.ReadMeta -import net.torvald.terrarum.serialise.WriteMeta import net.torvald.terrarum.tvda.* import net.torvald.terrarum.ui.* import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack @@ -26,6 +25,9 @@ import java.util.logging.Level import java.util.zip.GZIPInputStream import kotlin.math.roundToInt +val SAVE_CELL_WIDTH = 480 +val SAVE_CELL_HEIGHT = 120 + /** * Only works if current screen set by the App is [TitleScreen] * @@ -55,13 +57,13 @@ class UILoadDemoSavefiles : UICanvas() { private val shapeRenderer = ShapeRenderer() - internal val uiWidth = UIItemDemoSaveCells.WIDTH // 480 + internal val uiWidth = SAVE_CELL_WIDTH internal val uiX = (width - uiWidth) / 2 internal val textH = App.fontGame.lineHeight.toInt() internal val cellGap = 20 - internal val cellInterval = cellGap + UIItemDemoSaveCells.HEIGHT + internal val cellInterval = cellGap + SAVE_CELL_HEIGHT internal val gradAreaHeight = 32 internal val titleTextPosY: Int = App.scr.tvSafeGraphicsHeight + 10 @@ -106,15 +108,15 @@ class UILoadDemoSavefiles : UICanvas() { Thread { // read savegames var savegamesCount = 0 - App.savegames.forEach { skimmer -> + App.savegameWorlds.forEach { (uuid, skimmer) -> val x = uiX + if (App.getConfigBoolean("fx_streamerslayout")) App.scr.chatWidth / 2 else 0 val y = titleTopGradEnd + cellInterval * savegamesCount try { - addUIitem(UIItemDemoSaveCells(this, x, y, skimmer)) + addUIitem(UIItemWorldCells(this, x, y, skimmer)) savegamesCount += 1 } catch (e: Throwable) { - System.err.println("[UILoadDemoSavefiles] Savefile '${skimmer.diskFile.absolutePath}' cannot be loaded") + System.err.println("[UILoadDemoSavefiles] Error while loading World '${skimmer.diskFile.absolutePath}'") e.printStackTrace() } @@ -290,7 +292,7 @@ class UILoadDemoSavefiles : UICanvas() { override fun resize(width: Int, height: Int) { super.resize(width, height) scrollAreaHeight = height - 2 * App.scr.tvSafeGraphicsHeight - 64 - savesVisible = (scrollAreaHeight + cellInterval) / (cellInterval + UIItemDemoSaveCells.HEIGHT) + savesVisible = (scrollAreaHeight + cellInterval) / (cellInterval + SAVE_CELL_HEIGHT) listScroll = 0 scrollTarget = 0 @@ -311,49 +313,66 @@ class UILoadDemoSavefiles : UICanvas() { - - - -class UIItemDemoSaveCells( +class UIItemPlayerCells( parent: UILoadDemoSavefiles, initialX: Int, initialY: Int, val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) { - companion object { - const val WIDTH = 480 - const val HEIGHT = 120 + override val width = SAVE_CELL_WIDTH + override val height = SAVE_CELL_HEIGHT + + + + override fun dispose() { } - private val metaFile: DiskEntry? +} + + +class UIItemWorldCells( + parent: UILoadDemoSavefiles, + initialX: Int, + initialY: Int, + val skimmer: DiskSkimmer) : UIItem(parent, initialX, initialY) { + + + private val metaFile: EntryFile? private val saveName: String private val saveMode: Int private val isQuick: Boolean private val isAuto: Boolean - private val meta: WriteMeta.WorldMeta? - private val saveDamaged: Boolean + private var saveDamaged: Boolean = false private val lastPlayedTimestamp: String init { printdbg(this, "Rebuilding skimmer for savefile ${skimmer.diskFile.absolutePath}") skimmer.rebuild() - metaFile = skimmer.requestFile(-1) + metaFile = skimmer.getFile(-1) + if (metaFile == null) saveDamaged = true + saveName = skimmer.getDiskName(Common.CHARSET) saveMode = skimmer.getSaveMode() isQuick = (saveMode % 2 == 1) isAuto = (saveMode.ushr(1) != 0) - meta = if (metaFile != null) ReadMeta.fromDiskEntry(metaFile) else null - saveDamaged = checkForSavegameDamage(skimmer) + saveDamaged = saveDamaged or checkForSavegameDamage(skimmer) - lastPlayedTimestamp = if (meta != null) - Instant.ofEpochSecond(meta.lastplay_t) - .atZone(TimeZone.getDefault().toZoneId()) - .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + - "/${parseDuration(meta.playtime_t)}" - else "--:--:--/--h--m--s" + if (metaFile != null) { + val worldJson = JsonReader().parse(ByteArray64Reader(metaFile.bytes, Common.CHARSET)) + val lastplay_t = worldJson["lastPlayTime"].asLong() + val playtime_t = worldJson["totalPlayTime"].asLong() + lastPlayedTimestamp = + Instant.ofEpochSecond(lastplay_t) + .atZone(TimeZone.getDefault().toZoneId()) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + + "/${parseDuration(playtime_t)}" + } + else { + lastPlayedTimestamp = "--:--:--/--h--m--s" + } } private fun parseDuration(seconds: Long): String { @@ -367,8 +386,8 @@ class UIItemDemoSaveCells( "${d}d${h.toString().padStart(2,'0')}h${m.toString().padStart(2,'0')}m${s.toString().padStart(2,'0')}s" } - override val width: Int = WIDTH - override val height: Int = HEIGHT + override val width: Int = SAVE_CELL_WIDTH + override val height: Int = SAVE_CELL_HEIGHT private var thumbPixmap: Pixmap? = null private var thumb: TextureRegion? = null diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIProxyLoadLatestSave.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIProxyLoadLatestSave.kt index dd03d9362..6eb45a0b5 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIProxyLoadLatestSave.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIProxyLoadLatestSave.kt @@ -2,12 +2,8 @@ package net.torvald.terrarum.modulebasegame.ui import com.badlogic.gdx.graphics.Camera import com.badlogic.gdx.graphics.g2d.SpriteBatch -import net.torvald.terrarum.App import net.torvald.terrarum.Second -import net.torvald.terrarum.serialise.LoadSavegame -import net.torvald.terrarum.tvda.VDUtil import net.torvald.terrarum.ui.UICanvas -import java.util.logging.Level /** * Created by minjaesong on 2021-09-13. @@ -31,11 +27,11 @@ class UIProxyLoadLatestSave : UICanvas() { } override fun endOpening(delta: Float) { - if (App.savegames.size > 0) { - LoadSavegame(VDUtil.readDiskArchive(App.savegames[0].diskFile, Level.INFO) { - System.err.println("Possibly damaged savefile ${App.savegames[0].diskFile.absolutePath}:\n$it") - }) - } + + + // do something! + + } override fun endClosing(delta: Float) { diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UITitleRemoConYaml.kt b/src/net/torvald/terrarum/modulebasegame/ui/UITitleRemoConYaml.kt index 349087dfd..9bf2831b7 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UITitleRemoConYaml.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UITitleRemoConYaml.kt @@ -12,6 +12,7 @@ object UITitleRemoConYaml { * The class must be the UICanvas */ private val menuBase = """ +- MENU_LABEL_NEW_GAME : net.torvald.terrarum.modulebasegame.ui.UIProxyNewRandomGame - MENU_OPTIONS - MENU_LABEL_GRAPHICS : net.torvald.terrarum.modulebasegame.ui.GraphicsControlPanel - MENU_OPTIONS_CONTROLS : net.torvald.terrarum.modulebasegame.ui.UIKeyboardControlPanel @@ -27,17 +28,15 @@ object UITitleRemoConYaml { private val menuWithSavefile = """ - MENU_LABEL_CONTINUE : net.torvald.terrarum.modulebasegame.ui.UIProxyLoadLatestSave -- MENU_LABEL_NEW_GAME : net.torvald.terrarum.modulebasegame.ui.UIProxyNewRandomGame - MENU_IO_LOAD : net.torvald.terrarum.modulebasegame.ui.UILoadDemoSavefiles - - MENU_LABEL_RETURN -""" + - MENU_LABEL_NEW_WORLD + - MENU_LABEL_RETURN""" private val menuNewGame = """ -- MENU_LABEL_NEW_GAME : net.torvald.terrarum.modulebasegame.ui.UIProxyNewRandomGame """ operator fun invoke(hasSave: Boolean) = - Yaml((if (hasSave) menuWithSavefile else menuNewGame) + menuBase).parse() + Yaml((if (!hasSave) menuWithSavefile else menuNewGame) + menuBase).parse() } diff --git a/src/net/torvald/terrarum/serialise/GameSavingThread.kt b/src/net/torvald/terrarum/serialise/GameSavingThread.kt index 4b9afec89..d529b41e5 100644 --- a/src/net/torvald/terrarum/serialise/GameSavingThread.kt +++ b/src/net/torvald/terrarum/serialise/GameSavingThread.kt @@ -1,7 +1,6 @@ package net.torvald.terrarum.serialise import net.torvald.gdx.graphics.PixmapIO2 -import net.torvald.terrarum.App import net.torvald.terrarum.ccG import net.torvald.terrarum.ccW import net.torvald.terrarum.console.Echo @@ -42,7 +41,7 @@ abstract class SavingThread(private val ingame: TerrarumIngame) : Runnable { /** * Created by minjaesong on 2021-09-14. */ -class WorldSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : SavingThread(ingame) { +class WorldSavingThread(val time_t: Long, val disk: VirtualDisk, val outFile: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : SavingThread(ingame) { override fun save() { @@ -71,8 +70,7 @@ class WorldSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Te Echo("Writing metadata...") - val creation_t = ingame.creationTime - val time_t = App.getTIME_T() + val creation_t = ingame.world.creationTime if (hasThumbnail) { @@ -86,45 +84,10 @@ class WorldSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Te WriteSavegame.saveProgress += 1f - - // Write BlockCodex// -// val blockCodexContent = EntryFile(zip(ByteArray64.fromByteArray(Common.jsoner.toJson(BlockCodex).toByteArray(Common.CHARSET)))) -// val blocks = DiskEntry(-16, 0, "blocks".toByteArray(Common.CHARSET), creation_t, time_t, blockCodexContent) -// addFile(disk, blocks) - // Commented out; nothing to write - - // Write ItemCodex// -// val itemCodexContent = EntryFile(Common.zip(ByteArray64.fromByteArray(Common.jsoner.toJson(ItemCodex).toByteArray(Common.CHARSET)))) -// val items = DiskEntry(-17, 0, creation_t, time_t, itemCodexContent) -// addFile(disk, items) - // Gotta save dynamicIDs - - // Write WireCodex// -// val wireCodexContent = EntryFile(zip(ByteArray64.fromByteArray(Common.jsoner.toJson(WireCodex).toByteArray(Common.CHARSET)))) -// val wires = DiskEntry(-18, 0, "wires".toByteArray(Common.CHARSET), creation_t, time_t, wireCodexContent) -// addFile(disk, wires) - // Commented out; nothing to write - - // Write MaterialCodex// -// val materialCodexContent = EntryFile(zip(ByteArray64.fromByteArray(Common.jsoner.toJson(MaterialCodex).toByteArray(Common.CHARSET)))) -// val materials = DiskEntry(-19, 0, "materials".toByteArray(Common.CHARSET), creation_t, time_t, materialCodexContent) -// addFile(disk, materials) - // Commented out; nothing to write - - // Write FactionCodex// -// val factionCodexContent = EntryFile(zip(ByteArray64.fromByteArray(Common.jsoner.toJson(FactionCodex).toByteArray(Common.CHARSET)))) -// val factions = DiskEntry(-20, 0, "factions".toByteArray(Common.CHARSET), creation_t, time_t, factionCodexContent) -// addFile(disk, factions) - - // Write Apocryphas// -// val apocryphasContent = EntryFile(Common.zip(ByteArray64.fromByteArray(Common.jsoner.toJson(Apocryphas).toByteArray(Common.CHARSET)))) -// val apocryphas = DiskEntry(-1024, 0, creation_t, time_t, apocryphasContent) -// addFile(disk, apocryphas) - // Write World // // record all player's last position playersList.forEach { - ingame.world.playersLastStatus[it.uuid] = PlayerLastStatus(it) + ingame.world.playersLastStatus[it.uuid] = PlayerLastStatus(it, ingame.isMultiplayer) } val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t)) val world = DiskEntry(-1L, 0, creation_t, time_t, worldMeta) @@ -192,14 +155,14 @@ class WorldSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Te * * Created by minjaesong on 2021-10-08 */ -class PlayerSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : SavingThread(ingame) { +class PlayerSavingThread(val time_t: Long, val disk: VirtualDisk, val outFile: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : SavingThread(ingame) { override fun save() { disk.saveMode = 2 * isAuto.toInt() // no quick disk.capacity = 0L Echo("Writing The Player...") - WritePlayer(ingame.actorGamer, disk) + WritePlayer(ingame.actorGamer, disk, ingame, time_t) VDUtil.dumpToRealMachine(disk, outFile) callback() diff --git a/src/net/torvald/terrarum/serialise/QuickSaveThread.kt b/src/net/torvald/terrarum/serialise/QuickSaveThread.kt index a9d75d36e..91341f0b7 100644 --- a/src/net/torvald/terrarum/serialise/QuickSaveThread.kt +++ b/src/net/torvald/terrarum/serialise/QuickSaveThread.kt @@ -1,22 +1,23 @@ package net.torvald.terrarum.serialise import net.torvald.gdx.graphics.PixmapIO2 -import net.torvald.terrarum.App import net.torvald.terrarum.ccG import net.torvald.terrarum.ccW import net.torvald.terrarum.console.Echo import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.TerrarumIngame +import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.toInt import net.torvald.terrarum.tvda.* +import net.torvald.terrarum.utils.PlayerLastStatus import java.io.File import java.util.zip.GZIPOutputStream /** * Created by minjaesong on 2021-09-29. */ -class QuickSaveThread(val disk: VirtualDisk, val file: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : Runnable { +class QuickSingleplayerWorldSavingThread(val time_t: Long, val disk: VirtualDisk, val file: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : Runnable { /** * Will happily overwrite existing entry @@ -34,11 +35,6 @@ class QuickSaveThread(val disk: VirtualDisk, val file: File, val ingame: Terraru override fun run() { - callback() - return - - // TODO // - val skimmer = DiskSkimmer(file, Common.CHARSET) if (hasThumbnail) { @@ -47,7 +43,10 @@ class QuickSaveThread(val disk: VirtualDisk, val file: File, val ingame: Terraru } } - val actorsList = listOf(ingame.actorContainerActive).flatMap { it.filter { WriteWorld.actorAcceptable(it) } } + val allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList() + + val playersList: List = allTheActors.filter{ it is IngamePlayer } as List + val actorsList = allTheActors.filter { WriteWorld.actorAcceptable(it) } val chunks = ingame.modifiedChunks val chunkCount = chunks.map { it.size }.sum() @@ -63,14 +62,8 @@ class QuickSaveThread(val disk: VirtualDisk, val file: File, val ingame: Terraru Echo("Writing metadata...") - val creation_t = ingame.creationTime - val time_t = App.getTIME_T() - + val creation_t = ingame.world.creationTime - // Write Meta // - val metaContent = EntryFile(WriteMeta.encodeToByteArray64(ingame, time_t)) - val meta = DiskEntry(-1, 0, creation_t, time_t, metaContent) - addFile(disk, meta); skimmer.appendEntryOnly(meta) if (hasThumbnail) { PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true) @@ -78,16 +71,18 @@ class QuickSaveThread(val disk: VirtualDisk, val file: File, val ingame: Terraru val thumbContent = EntryFile(tgaout.toByteArray64()) val thumb = DiskEntry(-2, 0, creation_t, time_t, thumbContent) - addFile(disk, thumb); skimmer.appendEntryOnly(thumb) + addFile(disk, thumb) } WriteSavegame.saveProgress += 1f - // Write World // - val worldNum = ingame.world.worldIndex + // record all player's last position + playersList.forEach { + ingame.world.playersLastStatus[it.uuid] = PlayerLastStatus(it, ingame.isMultiplayer) + } val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t)) - val world = DiskEntry(-1-1-1-1-1-1-1, 0, creation_t, time_t, worldMeta) + val world = DiskEntry(-1L, 0, creation_t, time_t, worldMeta) addFile(disk, world); skimmer.appendEntryOnly(world) WriteSavegame.saveProgress += 1f diff --git a/src/net/torvald/terrarum/serialise/WriteActor.kt b/src/net/torvald/terrarum/serialise/WriteActor.kt index 50baaa806..872e18e12 100644 --- a/src/net/torvald/terrarum/serialise/WriteActor.kt +++ b/src/net/torvald/terrarum/serialise/WriteActor.kt @@ -3,8 +3,6 @@ package net.torvald.terrarum.serialise import net.torvald.spriteanimation.HasAssembledSprite import net.torvald.spriteanimation.SpriteAnimation import net.torvald.spriteassembler.ADProperties -import net.torvald.terrarum.App -import net.torvald.terrarum.INGAME import net.torvald.terrarum.NoSuchActorWithIDException import net.torvald.terrarum.gameactors.Actor import net.torvald.terrarum.gameactors.ActorWithBody @@ -62,8 +60,21 @@ object WritePlayer { if (!dir.contains(file.entryID)) dir.add(file.entryID) } - operator fun invoke(player: IngamePlayer, playerDisk: VirtualDisk) { - val time_t = App.getTIME_T() + operator fun invoke(player: IngamePlayer, playerDisk: VirtualDisk, ingame: TerrarumIngame, time_t: Long) { + player.lastPlayTime = time_t + player.totalPlayTime += time_t - ingame.loadedTime_t + + + // restore player prop backup created on load-time + if (ingame.world.playersLastStatus[player.uuid] != null) { + player.setPosition(player.unauthorisedPlayerProps.physics.position) + if (ingame.isMultiplayer) { + player.actorValue = player.unauthorisedPlayerProps.actorValue!! + player.inventory = player.unauthorisedPlayerProps.inventory!! + } + } + + val actorJson = WriteActor.encodeToByteArray64(player) val adl = player.animDesc!!.getRawADL() val adlGlow = player.animDescGlow?.getRawADL() // NULLABLE! diff --git a/src/net/torvald/terrarum/serialise/WriteMeta.kt b/src/net/torvald/terrarum/serialise/WriteMeta.kt index ae7c8631c..073fef863 100644 --- a/src/net/torvald/terrarum/serialise/WriteMeta.kt +++ b/src/net/torvald/terrarum/serialise/WriteMeta.kt @@ -1,64 +1,10 @@ package net.torvald.terrarum.serialise -import net.torvald.terrarum.App -import net.torvald.terrarum.ModMgr -import net.torvald.terrarum.gameactors.ActorID -import net.torvald.terrarum.modulebasegame.TerrarumIngame -import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser -import net.torvald.terrarum.tvda.* -import net.torvald.terrarum.weather.WeatherMixer - /** * Created by minjaesong on 2021-08-23. */ object WriteMeta { - operator fun invoke(ingame: TerrarumIngame, time_t: Long): String { - val world = ingame.world - val currentPlayTime_t = time_t - ingame.loadedTime_t - - val meta = WorldMeta( - genver = Common.GENVER, - savename = world.worldName, - randseed0 = RoguelikeRandomiser.RNG.state0, - randseed1 = RoguelikeRandomiser.RNG.state1, - weatseed0 = WeatherMixer.RNG.state0, - weatseed1 = WeatherMixer.RNG.state1, - creation_t = ingame.creationTime, - lastplay_t = time_t, - playtime_t = ingame.totalPlayTime + currentPlayTime_t, - loadorder = ModMgr.loadOrder.toTypedArray(), - //worlds = ingame.gameworldIndices.toTypedArray() - ) - - return Common.jsoner.toJson(meta) - } - - fun encodeToByteArray64(ingame: TerrarumIngame, time_t: Long): ByteArray64 { - val ba = ByteArray64() - this.invoke(ingame, time_t).toByteArray(Common.CHARSET).forEach { ba.add(it) } - return ba - } - - data class WorldMeta( - val genver: Int = -1, - val savename: String = "", - val randseed0: Long = 0, - val randseed1: Long = 0, - val weatseed0: Long = 0, - val weatseed1: Long = 0, - val playerid: ActorID = 0, - val creation_t: Long = 0, - val lastplay_t: Long = 0, - val playtime_t: Long = 0, - 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 - ) { - - override fun equals(other: Any?): Boolean { - throw UnsupportedOperationException() - } - } private fun modnameToOrnamentalHeader(s: String) = "\n\n${"#".repeat(16 + s.length)}\n" + @@ -72,15 +18,5 @@ object WriteMeta { */ 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) - } - - fun fromDiskEntry(metaFile: DiskEntry): WriteMeta.WorldMeta { - 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 index 4a61bbdea..9ec9c5333 100644 --- a/src/net/torvald/terrarum/serialise/WriteSavegame.kt +++ b/src/net/torvald/terrarum/serialise/WriteSavegame.kt @@ -11,10 +11,9 @@ import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer import net.torvald.terrarum.realestate.LandUtil -import net.torvald.terrarum.serialise.Common.getUnzipInputStream import net.torvald.terrarum.tvda.ByteArray64 import net.torvald.terrarum.tvda.ByteArray64Reader -import net.torvald.terrarum.tvda.VDUtil +import net.torvald.terrarum.tvda.SimpleFileSystem import net.torvald.terrarum.tvda.VirtualDisk import java.io.File import java.io.Reader @@ -27,20 +26,21 @@ import java.io.Reader object WriteSavegame { enum class SaveMode { - META, PLAYER, WORLD, SHARED + META, PLAYER, WORLD, SHARED, QUICK_PLAYER, QUICK_WORLD } @Volatile var savingStatus = -1 // -1: not started, 0: saving in progress, 255: saving finished @Volatile var saveProgress = 0f @Volatile var saveProgressMax = 1f - private fun getSaveThread(mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, hasThumbnail: Boolean, isAuto: Boolean, callback: () -> Unit = {}) = when (mode) { - SaveMode.WORLD -> WorldSavingThread(disk, outFile, ingame, hasThumbnail, isAuto, callback) - SaveMode.PLAYER -> PlayerSavingThread(disk, outFile, ingame, hasThumbnail, isAuto, callback) + private fun getSaveThread(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, hasThumbnail: Boolean, isAuto: Boolean, callback: () -> Unit = {}) = when (mode) { + SaveMode.WORLD -> WorldSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback) + SaveMode.PLAYER -> PlayerSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback) + SaveMode.QUICK_PLAYER -> QuickSingleplayerWorldSavingThread(time_t, disk, outFile, ingame, hasThumbnail, isAuto, callback) else -> throw IllegalArgumentException("$mode") } - operator fun invoke(mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, callback: () -> Unit = {}) { + operator fun invoke(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, callback: () -> Unit = {}) { savingStatus = 0 Echo("Save queued") @@ -60,7 +60,7 @@ object WriteSavegame { } IngameRenderer.screencapRequested = true - val savingThread = Thread(getSaveThread(mode, disk, outFile, ingame, true, isAuto, callback), "TerrarumBasegameGameSaveThread") + val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, true, isAuto, callback), "TerrarumBasegameGameSaveThread") savingThread.start() // it is caller's job to keep the game paused or keep a "save in progress" ui up @@ -68,23 +68,23 @@ object WriteSavegame { } - fun immediate(mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, hasThumbnail: Boolean, isAuto: Boolean, callback: () -> Unit = {}) { + fun immediate(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, hasThumbnail: Boolean, isAuto: Boolean, callback: () -> Unit = {}) { savingStatus = 0 Echo("Immediate save fired") - val savingThread = Thread(getSaveThread(mode, disk, outFile, ingame, false, isAuto, callback), "TerrarumBasegameGameSaveThread") + val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, false, isAuto, callback), "TerrarumBasegameGameSaveThread") savingThread.start() // it is caller's job to keep the game paused or keep a "save in progress" ui up // use field 'savingStatus' to know when the saving is done } - fun quick(disk: VirtualDisk, file: File, ingame: TerrarumIngame, isAuto: Boolean, callback: () -> Unit = {}) { - return + fun quick(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, callback: () -> Unit = {}) { + if (ingame.isMultiplayer) TODO() - // TODO // + return // TODO // savingStatus = 0 @@ -105,7 +105,7 @@ object WriteSavegame { } IngameRenderer.screencapRequested = true - val savingThread = Thread(QuickSaveThread(disk, file, ingame, true, isAuto, callback), "TerrarumBasegameGameSaveThread") + val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, false, isAuto, callback), "TerrarumBasegameGameSaveThread") savingThread.start() // it is caller's job to keep the game paused or keep a "save in progress" ui up @@ -125,48 +125,33 @@ object WriteSavegame { */ object LoadSavegame { - fun getFileBytes(disk: VirtualDisk, id: Long): ByteArray64 = VDUtil.getAsNormalFile(disk, id).getContent() - fun getFileReader(disk: VirtualDisk, id: Long): Reader = ByteArray64Reader(getFileBytes(disk, id), Common.CHARSET) - - operator fun invoke(disk: VirtualDisk) { - TODO() + 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(playerDisk: SimpleFileSystem) { val newIngame = TerrarumIngame(App.batch) + val player = ReadActor.invoke(playerDisk, ByteArray64Reader(playerDisk.getFile(-1L)!!.bytes, Common.CHARSET)) as IngamePlayer - val meta = ReadMeta(disk) + val currentWorldId = player.worldCurrentlyPlaying + val worldDisk = App.savegameWorlds[currentWorldId]!! + val world = ReadWorld(ByteArray64Reader(worldDisk.getFile(-1L)!!.bytes, Common.CHARSET)) - // NOTE: do NOT set ingame.actorNowPlaying as one read directly from the disk; - // you'll inevitably read the player actor twice, and they're separate instances of the player! - val currentWorld = (ReadActor.readActorBare(getFileReader(disk, Terrarum.PLAYER_REF_ID.toLong())) as IngamePlayer).worldCurrentlyPlaying - val world = ReadWorld(getFileReader(disk, -1-1-1-1-1-1)) - - // set lateinit vars on the gameworld FIRST world.layerTerrain = BlockLayer(world.width, world.height) world.layerWall = BlockLayer(world.width, world.height) - newIngame.creationTime = meta.creation_t - newIngame.lastPlayTime = meta.lastplay_t - newIngame.totalPlayTime = meta.playtime_t newIngame.world = world // must be set before the loadscreen, otherwise the loadscreen will try to read from the NullWorld which is already destroyed val loadJob = { it: LoadScreenBase -> val loadscreen = it as ChunkLoadingLoadScreen - loadscreen.addMessage(Lang["MENU_IO_LOADING"]) - val actors = world.actors.distinct()//.map { ReadActor(getFileReader(disk, it.toLong())) } - // val block = Common.jsoner.fromJson(BlockCodex.javaClass, getUnzipInputStream(getFileBytes(disk, -16))) - val item = Common.jsoner.fromJson(ItemCodex.javaClass, getUnzipInputStream(getFileBytes(disk, -17))) - // val wire = Common.jsoner.fromJson(WireCodex.javaClass, getUnzipInputStream(getFileBytes(disk, -18))) - // val material = Common.jsoner.fromJson(MaterialCodex.javaClass, getUnzipInputStream(getFileBytes(disk, -19))) - // val faction = Common.jsoner.fromJson(FactionCodex.javaClass, getUnzipInputStream(getFileBytes(disk, -20))) - val apocryphas = Common.jsoner.fromJson(Apocryphas.javaClass, getUnzipInputStream(getFileBytes(disk, -1024))) + + val actors = world.actors.distinct() + val worldParam = TerrarumIngame.Codices(worldDisk, world, actors, player) - - val worldParam = TerrarumIngame.Codices(disk, meta, item, apocryphas, actors) newIngame.gameLoadInfoPayload = worldParam newIngame.gameLoadMode = TerrarumIngame.GameLoadMode.LOAD_FROM @@ -180,27 +165,25 @@ object LoadSavegame { for (layer in worldLayer.indices) { loadscreen.addMessage("${Lang["MENU_IO_LOADING"]} ${chunk*worldLayer.size+layer+1}/${chunkCount*2}") - val chunkFile = VDUtil.getAsNormalFile(disk, 0x1_0000_0000L or layer.toLong().shl(24) or chunk) + val chunkFile = worldDisk.getFile(0x1_0000_0000L or layer.toLong().shl(24) or chunk)!! val chunkXY = LandUtil.chunkNumToChunkXY(world, chunk.toInt()) ReadWorld.decodeChunkToLayer(chunkFile.getContent(), worldLayer[layer]!!, chunkXY.x, chunkXY.y) } } - loadscreen.addMessage("Updating Block Mappings...") world.renumberTilesAfterLoad() - Echo("${ccW}Savegame loaded from $ccY${disk.getDiskNameString(Common.CHARSET)}") - printdbg(this, "Savegame loaded from ${disk.getDiskNameString(Common.CHARSET)}") + Echo("${ccW}World loaded: $ccY${worldDisk.getDiskName(Common.CHARSET)}") + printdbg(this, "World loaded: ${worldDisk.getDiskName(Common.CHARSET)}") } + val loadScreen = ChunkLoadingLoadScreen(newIngame, world.width, world.height, loadJob) Terrarum.setCurrentIngameInstance(newIngame) App.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 61ff1b5bb..04013d214 100644 --- a/src/net/torvald/terrarum/serialise/WriteWorld.kt +++ b/src/net/torvald/terrarum/serialise/WriteWorld.kt @@ -10,9 +10,11 @@ import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorldTitleScreen import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer +import net.torvald.terrarum.modulebasegame.worldgenerator.RoguelikeRandomiser import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.tvda.ByteArray64 import net.torvald.terrarum.tvda.ByteArray64Writer +import net.torvald.terrarum.weather.WeatherMixer import java.io.Reader /** @@ -43,6 +45,11 @@ object WriteWorld { world.actors.clear() world.actors.addAll(actorIDbuf.sorted().distinct()) + world.randSeeds[0] = RoguelikeRandomiser.RNG.state0 + world.randSeeds[1] = RoguelikeRandomiser.RNG.state1 + world.randSeeds[2] = WeatherMixer.RNG.state0 + world.randSeeds[3] = WeatherMixer.RNG.state1 + return world } diff --git a/src/net/torvald/terrarum/tvda/DiskSkimmer.kt b/src/net/torvald/terrarum/tvda/DiskSkimmer.kt index 9d46b97af..acb209574 100644 --- a/src/net/torvald/terrarum/tvda/DiskSkimmer.kt +++ b/src/net/torvald/terrarum/tvda/DiskSkimmer.kt @@ -1,9 +1,7 @@ package net.torvald.terrarum.tvda -import net.torvald.terrarum.serialise.Common import java.io.* import java.nio.charset.Charset -import java.nio.file.NoSuchFileException import java.util.* import java.util.logging.Level import kotlin.experimental.and @@ -344,7 +342,7 @@ removefile: return fa.read() } - fun getDiskName(charset: Charset): String { + override fun getDiskName(charset: Charset): String { val bytes = ByteArray(268) fa.seek(10L) fa.read(bytes, 0, 32) diff --git a/src/net/torvald/terrarum/tvda/SimpleFileSystem.kt b/src/net/torvald/terrarum/tvda/SimpleFileSystem.kt index af29f6b09..0755a82c4 100644 --- a/src/net/torvald/terrarum/tvda/SimpleFileSystem.kt +++ b/src/net/torvald/terrarum/tvda/SimpleFileSystem.kt @@ -1,9 +1,12 @@ package net.torvald.terrarum.tvda +import java.nio.charset.Charset + /** * Created by minjaesong on 2021-10-07. */ interface SimpleFileSystem { fun getEntry(id: EntryID): DiskEntry? fun getFile(id: EntryID): EntryFile? + fun getDiskName(charset: Charset): String } \ No newline at end of file diff --git a/src/net/torvald/terrarum/tvda/VDUtil.kt b/src/net/torvald/terrarum/tvda/VDUtil.kt index 3d48f6132..75fe3bb14 100644 --- a/src/net/torvald/terrarum/tvda/VDUtil.kt +++ b/src/net/torvald/terrarum/tvda/VDUtil.kt @@ -56,7 +56,7 @@ object VDUtil { throw RuntimeException("Invalid Virtual Disk file!") val diskSize = inbytes.sliceArray64(4L..9L).toInt48Big() - val diskName = inbytes.sliceArray64(10L..10L + 31) + val diskName = inbytes.sliceArray(10..10 + 31) + inbytes.sliceArray(10+32+22..10+32+22+235) val diskCRC = inbytes.sliceArray64(10L + 32..10L + 32 + 3).toIntBig() // to check with completed vdisk val diskSpecVersion = inbytes[10L + 32 + 4] val footers = inbytes.sliceArray64(10L+32+6..10L+32+21) @@ -64,7 +64,7 @@ object VDUtil { if (diskSpecVersion != specversion) throw RuntimeException("Unsupported disk format version: current internal version is $specversion; the file's version is $diskSpecVersion") - val vdisk = VirtualDisk(diskSize, diskName.toByteArray()) + val vdisk = VirtualDisk(diskSize, diskName) vdisk.__internalSetFooter__(footers) diff --git a/src/net/torvald/terrarum/tvda/VirtualDisk.kt b/src/net/torvald/terrarum/tvda/VirtualDisk.kt index 28a3aebd1..15c0b0b79 100644 --- a/src/net/torvald/terrarum/tvda/VirtualDisk.kt +++ b/src/net/torvald/terrarum/tvda/VirtualDisk.kt @@ -133,7 +133,7 @@ class VirtualDisk( var saveMode: Int set(value) { extraInfoBytes[1] = value.toByte() } get() = extraInfoBytes[1].toUint() - fun getDiskNameString(charset: Charset) = diskName.toCanonicalString(charset) + override fun getDiskName(charset: Charset) = diskName.toCanonicalString(charset) val root: DiskEntry get() = entries[0]!! @@ -207,7 +207,7 @@ class VirtualDisk( } override fun equals(other: Any?) = if (other == null) false else this.hashCode() == other.hashCode() - override fun toString() = "VirtualDisk(name: ${getDiskNameString(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})" + override fun toString() = "VirtualDisk(name: ${getDiskName(Charsets.UTF_8)}, capacity: $capacity bytes, crc: ${hashCode().toHex()})" companion object { val HEADER_SIZE = 300L // according to the spec diff --git a/src/net/torvald/terrarum/utils/HashArray.kt b/src/net/torvald/terrarum/utils/HashArray.kt index fe312780b..dc6a56b59 100644 --- a/src/net/torvald/terrarum/utils/HashArray.kt +++ b/src/net/torvald/terrarum/utils/HashArray.kt @@ -24,14 +24,16 @@ class HashedWiringGraph: HashMap() class MetaModuleCSVPair: HashMap() class PlayersLastStatus: HashMap() class PlayerLastStatus() { - var physics = PhysicalStatus(); private set - var inventory = ActorInventory(); private set - var actorValue = ActorValue(); private set + var physics = PhysicalStatus(); private set // mandatory + var inventory: ActorInventory? = null; private set // optional (multiplayer only) + var actorValue: ActorValue? = null; private set // optional (multiplayer only) - constructor(player: IngamePlayer) : this() { + constructor(player: IngamePlayer, isMultiplayer: Boolean) : this() { physics = PhysicalStatus(player) - inventory = player.inventory - actorValue = player.actorValue + if (isMultiplayer) { + inventory = player.inventory + actorValue = player.actorValue + } } } /**