mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
new load wip
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<DiskSkimmer> savegames = new ArrayList<>();
|
||||
public static TreeMap<UUID, DiskSkimmer> savegameWorlds = new TreeMap<>();
|
||||
public static TreeMap<UUID, DiskSkimmer> 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();
|
||||
|
||||
@@ -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<ActorWithBody> {
|
||||
override fun getDimensions(): Int = 2
|
||||
@@ -127,10 +127,6 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
|
||||
|
||||
val modifiedChunks = Array(16) { TreeSet<Int>() }
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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<DiskSkimmer>).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(<String>) for now...
|
||||
val currentWorld = (player as EntryFile).bytes.let {
|
||||
(ReadActor.readActorBare(ByteArray64Reader(it, Common.CHARSET)) as? IngamePlayer
|
||||
?: return true).worldCurrentlyPlaying
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@ object ThreadExecutor {
|
||||
allFinished = true
|
||||
}
|
||||
|
||||
fun killAll() {
|
||||
executor.shutdownNow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<String, KFunction<*>> = HashMap()
|
||||
init {
|
||||
|
||||
@@ -18,6 +18,12 @@ abstract class Actor : Comparable<Actor>, 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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, Any>,
|
||||
val actors: List<ActorID>
|
||||
// val apocryphas: Map<String, Any>,
|
||||
val actors: List<ActorID>,
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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<String>) {
|
||||
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<String>) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<IngamePlayer> = allTheActors.filter{ it is IngamePlayer } as List<IngamePlayer>
|
||||
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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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<String> = arrayOf() // do not use list; Could not instantiate instance of class: java.util.Collections$SingletonList
|
||||
//val worlds: Array<Int> = 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,14 +24,16 @@ class HashedWiringGraph: HashMap<BlockAddress, WiringGraphMap>()
|
||||
class MetaModuleCSVPair: HashMap<String, ZipCodedStr>()
|
||||
class PlayersLastStatus: HashMap<UUID, PlayerLastStatus>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user