finally working again: create new character

todo: make delete character work
This commit is contained in:
minjaesong
2023-06-27 21:13:51 +09:00
parent 057905c3b7
commit c5874a7f3d
16 changed files with 393 additions and 304 deletions

View File

@@ -10,7 +10,7 @@ import com.badlogic.gdx.Input
object DefaultConfig {
val hashMap = hashMapOf<String, Any>(
"jvm_xmx" to 8,
"jvm_xmx" to 4,
"jvm_extra_cmd" to "",
"displayfps" to 0, // 0: no limit, non-zero: limit
"displayfpsidle" to 0, // 0: no limit, non-zero: limit

View File

@@ -39,10 +39,10 @@ class SavegameCollection(files0: List<DiskSkimmer>) {
class SavegameCollectionPair(player: SavegameCollection?, world: SavegameCollection?) {
private lateinit var manualPlayer: DiskSkimmer
private lateinit var manualWorld: DiskSkimmer
private lateinit var autoPlayer: DiskSkimmer
private lateinit var autoWorld: DiskSkimmer
private var manualPlayer: DiskSkimmer? = null
private var manualWorld: DiskSkimmer? = null
private var autoPlayer: DiskSkimmer? = null
private var autoWorld: DiskSkimmer? = null
var status = 0 // 0: none available, 1: loadable manual save is newer than loadable auto; 2: loadable autosave is newer than loadable manual
private set
@@ -55,8 +55,8 @@ class SavegameCollectionPair(player: SavegameCollection?, world: SavegameCollect
if (player != null && world != null) {
printdbg(this, player.files.joinToString { it.diskFile.name })
printdbg(this, world.files.joinToString { it.diskFile.name })
printdbg(this, "player files: " + player.files.joinToString { it.diskFile.name })
printdbg(this, "world files:" + world.files.joinToString { it.diskFile.name })
// if a pair of files were saved successfully, they must have identical lastModifiedTime()
var pc = 0; val pt = player.files[0].getLastModifiedTime()
@@ -75,7 +75,7 @@ class SavegameCollectionPair(player: SavegameCollection?, world: SavegameCollect
when (pcf.isAutosaved().toInt(1) or wcf.isAutosaved().toInt()) {
3 -> {
if (!::autoPlayer.isInitialized && !::autoWorld.isInitialized) {
if (autoPlayer == null && autoWorld == null) {
autoPlayer = pcf
autoWorld = wcf
}
@@ -83,7 +83,7 @@ class SavegameCollectionPair(player: SavegameCollection?, world: SavegameCollect
wc += 1
}
0 -> {
if (!::manualPlayer.isInitialized && !::manualWorld.isInitialized) {
if (manualPlayer == null && manualWorld == null) {
manualPlayer = pcf
manualWorld = wcf
}
@@ -105,32 +105,25 @@ class SavegameCollectionPair(player: SavegameCollection?, world: SavegameCollect
if (::manualPlayer.isInitialized && ::manualWorld.isInitialized && ::autoPlayer.isInitialized && ::autoWorld.isInitialized)
if (manualPlayer != null && manualWorld != null && autoPlayer != null && autoWorld != null)
break
}
if (::manualPlayer.isInitialized && ::manualWorld.isInitialized && ::autoPlayer.isInitialized && ::autoWorld.isInitialized) {
status = if (manualPlayer.getLastModifiedTime() > autoPlayer.getLastModifiedTime()) 1 else 2
printdbg(this, "manualPlayer = ${manualPlayer.diskFile.path}")
printdbg(this, "manualWorld = ${manualWorld.diskFile.path}")
printdbg(this, "autoPlayer = ${autoPlayer.diskFile.path}")
printdbg(this, "autoWorld = ${autoWorld.diskFile.path}")
if (manualPlayer != null && manualWorld != null && autoPlayer != null && autoWorld != null) {
status = if (manualPlayer!!.getLastModifiedTime() > autoPlayer!!.getLastModifiedTime()) 1 else 2
}
else if (::manualPlayer.isInitialized && ::manualWorld.isInitialized || ::autoPlayer.isInitialized && ::autoWorld.isInitialized) {
else if (manualPlayer != null && manualWorld != null || autoPlayer != null && autoWorld != null) {
status = 1
if (::manualPlayer.isInitialized) {
printdbg(this, "manualPlayer = ${manualPlayer.diskFile.path}")
printdbg(this, "manualWorld = ${manualWorld.diskFile.path}")
}
else {
printdbg(this, "autoPlayer = ${autoPlayer.diskFile.path}")
printdbg(this, "autoWorld = ${autoWorld.diskFile.path}")
}
}
else {
status = 0
}
printdbg(this, "manualPlayer = ${manualPlayer?.diskFile?.path}")
printdbg(this, "manualWorld = ${manualWorld?.diskFile?.path}")
printdbg(this, "autoPlayer = ${autoPlayer?.diskFile?.path}")
printdbg(this, "autoWorld = ${autoWorld?.diskFile?.path}")
printdbg(this, "status = $status")
}
}
@@ -149,12 +142,20 @@ class SavegameCollectionPair(player: SavegameCollection?, world: SavegameCollect
fun getManualSave(): DiskPair? {
if (status == 0) return null
return DiskPair(manualPlayer, manualWorld)
return DiskPair(manualPlayer!!, manualWorld!!)
}
fun getAutoSave(): DiskPair? {
if (status != 2) return null
return DiskPair(autoPlayer, autoWorld)
return DiskPair(autoPlayer!!, autoWorld!!)
}
fun getLoadableSave(): DiskPair? {
if (status == 0) return null
return if (manualPlayer != null && manualWorld != null)
DiskPair(manualPlayer!!, manualWorld!!)
else
DiskPair(autoPlayer!!, autoWorld!!)
}
}

View File

@@ -16,7 +16,7 @@ object ScreencapNogui: ConsoleCommand {
PixmapIO2.writeTGA(Gdx.files.absolute(App.defaultDir + "/Exports/${args[1]}.tga"), p, true)
p.dispose()
}
IngameRenderer.screencapRequested = true
IngameRenderer.requestScreencap()
Echo("FBO exported to$ccG Exports/${args[1]}.tga")
}
else {

View File

@@ -11,7 +11,7 @@ import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.random.HQRNG
import net.torvald.terrarum.*
import net.torvald.terrarum.App.measureDebugTime
import net.torvald.terrarum.App.*
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZEF
import net.torvald.terrarum.gameactors.ActorWithBody
@@ -21,6 +21,7 @@ import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.fmod
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.weather.WeatherMixer
import net.torvald.terrarum.weather.WeatherMixer.render
import net.torvald.terrarum.worlddrawer.BlocksDrawer
import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.worlddrawer.LightmapRenderer
@@ -365,13 +366,19 @@ object IngameRenderer : Disposable {
///////////////////////////////////////////////////////////////////////
if (screencapRequested) {
screencapRequested = false
printdbg(this, "Screencap was requested, processing...")
var hasError = false
try {
screencapExportCallback(fboMixedOut)
}
catch (e: Throwable) {
printdbgerr(this, "An error occured while taking screencap:")
e.printStackTrace()
hasError = true
}
printdbg(this, "Screencap ${if (hasError) "failed" else "successful"}")
screencapBusy = false
screencapRequested = false
}
///////////////////////////////////////////////////////////////////////
@@ -416,11 +423,16 @@ object IngameRenderer : Disposable {
* This "screencap" will capture the game WITHOUT gui and postprocessors!
* To capture the entire game, use [App.requestScreenshot]
*/
@Volatile internal var screencapRequested = false
@Volatile internal var fboRGBexportedLatch = false
@Volatile private var screencapRequested = false
@Volatile internal var screencapBusy = false; private set
@Volatile internal var screencapExportCallback: (FrameBuffer) -> Unit = {}
@Volatile internal lateinit var fboRGBexport: Pixmap
fun requestScreencap() {
screencapRequested = true
screencapBusy = true
}
private fun drawToRGB(
actorsRenderBehind: List<ActorWithBody>?,
actorsRenderMiddle: List<ActorWithBody>?,

View File

@@ -420,10 +420,15 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
// 2. cannot sync up the "counter" to determine whether both are finished
uiAutosaveNotifier.setAsOpen()
val saveTime_t = App.getTIME_T()
printdbg(this, "Immediate Save")
WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.PLAYER, playerDisk, getPlayerSaveFiledesc(playerSavefileName), this, true, autosaveOnErrorAction) {
printdbg(this, "immediate save callback from PLAYER")
makeSavegameBackupCopy(getPlayerSaveFiledesc(playerSavefileName))
WriteSavegame.immediate(saveTime_t, WriteSavegame.SaveMode.WORLD, worldDisk, getWorldSaveFiledesc(worldSavefileName), this, true, autosaveOnErrorAction) {
printdbg(this, "immediate save callback from WORLD")
makeSavegameBackupCopy(getWorldSaveFiledesc(worldSavefileName)) // don't put it on the postInit() or render(); must be called using callback
uiAutosaveNotifier.setAsClose()
}
@@ -472,7 +477,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
world.worldCreator = UUID.fromString(player.uuid.toString())
printdbg(this, "new woridIndex: ${world.worldIndex}")
printdbg(this, "new worldIndex: ${world.worldIndex}")
printdbg(this, "worldCurrentlyPlaying: ${player.worldCurrentlyPlaying}")
actorNowPlaying = player

View File

@@ -1,36 +1,5 @@
package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.ItemCodex
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.ReferencingRanges.PREFIX_DYNAMICITEM
import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.modulebasegame.gameactors.Pocketed
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.toInt
import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.savegame.VDFileID.LOADORDER
import net.torvald.terrarum.savegame.VDFileID.PLAYER_SCREENSHOT
import net.torvald.terrarum.savegame.VDFileID.ROOT
import net.torvald.terrarum.savegame.VDFileID.SAVEGAMEINFO
import net.torvald.terrarum.savegame.VDFileID.THUMBNAIL
import net.torvald.terrarum.serialise.Common
import java.io.File
import java.util.zip.GZIPOutputStream
/**
* Will happily overwrite existing entry
*/
private fun addFile(disk: VirtualDisk, file: DiskEntry) {
disk.entries[file.entryID] = file
file.parentEntryID = 0
val dir = VDUtil.getAsDirectory(disk, 0)
if (!dir.contains(file.entryID)) dir.add(file.entryID)
}
abstract class SavingThread(private val errorHandler: (Throwable) -> Unit) : Runnable {
abstract fun save()
@@ -46,211 +15,4 @@ abstract class SavingThread(private val errorHandler: (Throwable) -> Unit) : Run
}
}
/**
* Created by minjaesong on 2021-09-14.
*/
class WorldSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
override fun save() {
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.WORLD_DATA
while (!IngameRenderer.fboRGBexportedLatch) {
Thread.sleep(1L)
}
val allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList()
val playersList: List<IngamePlayer> = allTheActors.filterIsInstance<IngamePlayer>()
val actorsList = allTheActors.filter { WriteWorld.actorAcceptable(it) }
val layers = intArrayOf(0,1).map { ingame.world.getLayer(it) }
val cw = ingame.world.width / LandUtil.CHUNK_W
val ch = ingame.world.height / LandUtil.CHUNK_H
WriteSavegame.saveProgress = 0f
WriteSavegame.saveProgressMax = 3f + (cw * ch * layers.size) + actorsList.size
val tgaout = ByteArray64GrowableOutputStream()
val gzout = GZIPOutputStream(tgaout)
printdbg(this, "Writing metadata...")
val creation_t = ingame.world.creationTime
// Write subset of Ingame.ItemCodex
// The existing ItemCodex must be rewritten to clear out obsolete records
// We're assuming the dynamic item generated by players does exist in the world, and it's recorded
// into the world's dynamicToStaticTable, therefore every item recorded into the world's dynamicToStaticTable
// can be found in this world without need to look up the players
ingame.world.dynamicToStaticTable.clear()
ingame.world.dynamicItemInventory.clear()
actorsList.filterIsInstance<Pocketed>().forEach { actor ->
actor.inventory.forEach { (itemid, _) ->
printdbg(this, "World side dynamicitem: $itemid contained in $actor")
if (itemid.startsWith("$PREFIX_DYNAMICITEM:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
actorsList.filterIsInstance<FixtureBase>().forEach { fixture ->
fixture.inventory?.forEach { (itemid, _) ->
printdbg(this, "World side dynamicitem: $itemid contained in $fixture")
if (itemid.startsWith("$PREFIX_DYNAMICITEM:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb = DiskEntry(THUMBNAIL, ROOT, creation_t, time_t, thumbContent)
addFile(disk, thumb)
WriteSavegame.saveProgress += 1f
// Write World //
val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t, actorsList, playersList))
val world = DiskEntry(SAVEGAMEINFO, ROOT, creation_t, time_t, worldMeta)
addFile(disk, world)
WriteSavegame.saveProgress += 1f
for (layer in layers.indices) {
for (cx in 0 until cw) {
for (cy in 0 until ch) {
val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy).toLong()
// Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}")
val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy)
val entryID = 0x1_0000_0000L or layer.toLong().shl(24) or chunkNumber
val entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, ROOT, creation_t, time_t, entryContent)
// "W1L0-92,15"
addFile(disk, entry)
WriteSavegame.saveProgress += 1
}
}
}
// Write Actors //
actorsList.forEachIndexed { count, it ->
// Echo("Writing actors... ${count+1}/${actorsList.size}")
val actorContent = EntryFile(WriteActor.encodeToByteArray64(it))
val actor = DiskEntry(it.referenceID.toLong(), ROOT, creation_t, time_t, actorContent)
addFile(disk, actor)
WriteSavegame.saveProgress += 1
}
// write loadorder //
val loadOrderBa64Writer = ByteArray64Writer(Common.CHARSET)
loadOrderBa64Writer.write(ModMgr.loadOrder.joinToString("\n"))
loadOrderBa64Writer.flush(); loadOrderBa64Writer.close()
val loadOrderText = loadOrderBa64Writer.toByteArray64()
val loadOrderContents = EntryFile(loadOrderText)
addFile(disk, DiskEntry(LOADORDER, ROOT, creation_t, time_t, loadOrderContents))
// Echo("Writing file to disk...")
disk.entries[0]!!.modificationDate = time_t
// entry zero MUST NOT be used to get lastPlayDate, but we'll update it anyway
// use entry -1 for that purpose!
disk.capacity = 0
VDUtil.dumpToRealMachine(disk, outFile)
printdbg(this, "Game saved with size of ${outFile.length()} bytes")
IngameRenderer.fboRGBexportedLatch = false
WriteSavegame.savingStatus = 255
callback()
}
}
/**
* This function called means the "Avatar" was not externally created and thus has no sprite-bodypart-name-to-entry-number-map
*
* Created by minjaesong on 2021-10-08
*/
class PlayerSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
override fun save() {
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.PLAYER_DATA
disk.capacity = 0L
WriteSavegame.saveProgress = 0f
// wait for screencap
while (!IngameRenderer.fboRGBexportedLatch) {
Thread.sleep(1L)
}
// write screencap
val tgaout = ByteArray64GrowableOutputStream()
val gzout = GZIPOutputStream(tgaout)
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb = DiskEntry(PLAYER_SCREENSHOT, ROOT, ingame.world.creationTime, time_t, thumbContent)
addFile(disk, thumb)
printdbg(this, "Writing The Player...")
WritePlayer(ingame.actorGamer, disk, ingame, time_t)
disk.entries[0]!!.modificationDate = time_t
VDUtil.dumpToRealMachine(disk, outFile)
IngameRenderer.fboRGBexportedLatch = false
callback()
}
}
const val SCREENCAP_WAIT_TRY_MAX = 256

View File

@@ -0,0 +1,77 @@
package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.App
import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.toInt
import java.io.File
import java.util.zip.GZIPOutputStream
/**
* This function called means the "Avatar" was not externally created and thus has no sprite-bodypart-name-to-entry-number-map
*
* Created by minjaesong on 2021-10-08
*/
class PlayerSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
/**
* Will happily overwrite existing entry
*/
private fun addFile(disk: VirtualDisk, file: DiskEntry) {
disk.entries[file.entryID] = file
file.parentEntryID = 0
val dir = VDUtil.getAsDirectory(disk, 0)
if (!dir.contains(file.entryID)) dir.add(file.entryID)
}
override fun save() {
App.printdbg(this, "outFile: ${outFile.path}")
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.PLAYER_DATA
disk.capacity = 0L
WriteSavegame.saveProgress = 0f
// wait for screencap
var emergencyStopCnt = 0
while (IngameRenderer.screencapBusy) {
// printdbg(this, "spinning for screencap to be taken")
Thread.sleep(4L)
emergencyStopCnt += 1
if (emergencyStopCnt >= SCREENCAP_WAIT_TRY_MAX) throw InterruptedException("Waiting screencap to be taken for too long")
}
// write screencap
val tgaout = ByteArray64GrowableOutputStream()
val gzout = GZIPOutputStream(tgaout)
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb =
DiskEntry(VDFileID.PLAYER_SCREENSHOT, VDFileID.ROOT, ingame.world.creationTime, time_t, thumbContent)
addFile(disk, thumb)
App.printdbg(this, "Writing The Player...")
WritePlayer(ingame.actorGamer, disk, ingame, time_t)
disk.entries[0]!!.modificationDate = time_t
VDUtil.dumpToRealMachine(disk, outFile)
// IngameRenderer.screencapBusy = false
callback()
}
}

View File

@@ -45,10 +45,17 @@ class QuickSingleplayerWorldSavingThread(
override fun save() {
printdbg(this, "outFile: ${outFile.path}")
val skimmer = DiskSkimmer(outFile)
while (!IngameRenderer.fboRGBexportedLatch) {
Thread.sleep(1L)
// wait for screencap
var emergencyStopCnt = 0
while (IngameRenderer.screencapBusy) {
// printdbg(this, "spinning for screencap to be taken")
Thread.sleep(4L)
emergencyStopCnt += 1
if (emergencyStopCnt >= SCREENCAP_WAIT_TRY_MAX) throw InterruptedException("Waiting screencap to be taken for too long")
}
val allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList()
@@ -147,7 +154,7 @@ class QuickSingleplayerWorldSavingThread(
printdbg(this, "Game saved with size of ${outFile.length()} bytes")
IngameRenderer.fboRGBexportedLatch = false
// IngameRenderer.screencapBusy = false
WriteSavegame.savingStatus = 255
ingame.clearModifiedChunks()

View File

@@ -0,0 +1,189 @@
package net.torvald.terrarum.modulebasegame.serialise
import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.*
import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.FixtureBase
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.modulebasegame.gameactors.Pocketed
import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.serialise.Common
import java.io.File
import java.util.zip.GZIPOutputStream
/**
* Created by minjaesong on 2021-09-14.
*/
class WorldSavingThread(
val time_t: Long,
val disk: VirtualDisk,
val outFile: File,
val ingame: TerrarumIngame,
val isAuto: Boolean,
val callback: () -> Unit,
val errorHandler: (Throwable) -> Unit
) : SavingThread(errorHandler) {
/**
* Will happily overwrite existing entry
*/
private fun addFile(disk: VirtualDisk, file: DiskEntry) {
disk.entries[file.entryID] = file
file.parentEntryID = 0
val dir = VDUtil.getAsDirectory(disk, 0)
if (!dir.contains(file.entryID)) dir.add(file.entryID)
}
override fun save() {
App.printdbg(this, "outFile: ${outFile.path}")
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.saveKind = VDSaveKind.WORLD_DATA
// wait for screencap
var emergencyStopCnt = 0
while (IngameRenderer.screencapBusy) {
// printdbg(this, "spinning for screencap to be taken")
Thread.sleep(4L)
emergencyStopCnt += 1
if (emergencyStopCnt >= SCREENCAP_WAIT_TRY_MAX) throw InterruptedException("Waiting screencap to be taken for too long")
}
val allTheActors = ingame.actorContainerActive.cloneToList() + ingame.actorContainerInactive.cloneToList()
val playersList: List<IngamePlayer> = allTheActors.filterIsInstance<IngamePlayer>()
val actorsList = allTheActors.filter { WriteWorld.actorAcceptable(it) }
val layers = intArrayOf(0,1).map { ingame.world.getLayer(it) }
val cw = ingame.world.width / LandUtil.CHUNK_W
val ch = ingame.world.height / LandUtil.CHUNK_H
WriteSavegame.saveProgress = 0f
WriteSavegame.saveProgressMax = 3f + (cw * ch * layers.size) + actorsList.size
val tgaout = ByteArray64GrowableOutputStream()
val gzout = GZIPOutputStream(tgaout)
App.printdbg(this, "Writing metadata...")
val creation_t = ingame.world.creationTime
// Write subset of Ingame.ItemCodex
// The existing ItemCodex must be rewritten to clear out obsolete records
// We're assuming the dynamic item generated by players does exist in the world, and it's recorded
// into the world's dynamicToStaticTable, therefore every item recorded into the world's dynamicToStaticTable
// can be found in this world without need to look up the players
ingame.world.dynamicToStaticTable.clear()
ingame.world.dynamicItemInventory.clear()
actorsList.filterIsInstance<Pocketed>().forEach { actor ->
actor.inventory.forEach { (itemid, _) ->
App.printdbg(this, "World side dynamicitem: $itemid contained in $actor")
if (itemid.startsWith("${ReferencingRanges.PREFIX_DYNAMICITEM}:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
actorsList.filterIsInstance<FixtureBase>().forEach { fixture ->
fixture.inventory?.forEach { (itemid, _) ->
App.printdbg(this, "World side dynamicitem: $itemid contained in $fixture")
if (itemid.startsWith("${ReferencingRanges.PREFIX_DYNAMICITEM}:")) {
ingame.world.dynamicToStaticTable[itemid] = ItemCodex.dynamicToStaticID(itemid)
ingame.world.dynamicItemInventory[itemid] = ItemCodex[itemid]!!
}
}
}
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose()
val thumbContent = EntryFile(tgaout.toByteArray64())
val thumb = DiskEntry(VDFileID.THUMBNAIL, VDFileID.ROOT, creation_t, time_t, thumbContent)
addFile(disk, thumb)
WriteSavegame.saveProgress += 1f
// Write World //
val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t, actorsList, playersList))
val world = DiskEntry(VDFileID.SAVEGAMEINFO, VDFileID.ROOT, creation_t, time_t, worldMeta)
addFile(disk, world)
WriteSavegame.saveProgress += 1f
for (layer in layers.indices) {
for (cx in 0 until cw) {
for (cy in 0 until ch) {
val chunkNumber = LandUtil.chunkXYtoChunkNum(ingame.world, cx, cy).toLong()
// Echo("Writing chunks... ${(cw*ch*layer) + chunkNumber + 1}/${cw*ch*layers.size}")
val chunkBytes = WriteWorld.encodeChunk(layers[layer]!!, cx, cy)
val entryID = 0x1_0000_0000L or layer.toLong().shl(24) or chunkNumber
val entryContent = EntryFile(chunkBytes)
val entry = DiskEntry(entryID, VDFileID.ROOT, creation_t, time_t, entryContent)
// "W1L0-92,15"
addFile(disk, entry)
WriteSavegame.saveProgress += 1
}
}
}
// Write Actors //
actorsList.forEachIndexed { count, it ->
// Echo("Writing actors... ${count+1}/${actorsList.size}")
val actorContent = EntryFile(WriteActor.encodeToByteArray64(it))
val actor = DiskEntry(it.referenceID.toLong(), VDFileID.ROOT, creation_t, time_t, actorContent)
addFile(disk, actor)
WriteSavegame.saveProgress += 1
}
// write loadorder //
val loadOrderBa64Writer = ByteArray64Writer(Common.CHARSET)
loadOrderBa64Writer.write(ModMgr.loadOrder.joinToString("\n"))
loadOrderBa64Writer.flush(); loadOrderBa64Writer.close()
val loadOrderText = loadOrderBa64Writer.toByteArray64()
val loadOrderContents = EntryFile(loadOrderText)
addFile(disk, DiskEntry(VDFileID.LOADORDER, VDFileID.ROOT, creation_t, time_t, loadOrderContents))
// Echo("Writing file to disk...")
disk.entries[0]!!.modificationDate = time_t
// entry zero MUST NOT be used to get lastPlayDate, but we'll update it anyway
// use entry -1 for that purpose!
disk.capacity = 0
VDUtil.dumpToRealMachine(disk, outFile)
App.printdbg(this, "Game saved with size of ${outFile.length()} bytes")
// IngameRenderer.screencapBusy = false
WriteSavegame.savingStatus = 255
callback()
}
}

View File

@@ -43,10 +43,7 @@ object WriteSavegame {
else -> throw IllegalArgumentException("$mode")
}
operator fun invoke(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) {
savingStatus = 0
printdbg(this, "Save queued")
private fun installScreencap() {
IngameRenderer.screencapExportCallback = { fb ->
printdbg(this, "Generating thumbnail...")
@@ -63,11 +60,19 @@ object WriteSavegame {
IngameRenderer.fboRGBexport = p
//PixmapIO2._writeTGA(gzout, p, true, true)
//p.dispose()
IngameRenderer.fboRGBexportedLatch = true
printdbg(this, "Done thumbnail generation")
}
IngameRenderer.screencapRequested = true
}
operator fun invoke(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) {
savingStatus = 0
printdbg(this, "Save queued")
installScreencap()
try { printdbg(this, "ScreencapExport installed: ${IngameRenderer.screencapExportCallback}") }
catch (e: UninitializedPropertyAccessException) { printdbg(this, "ScreencapExport installed: no") }
IngameRenderer.requestScreencap()
val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, isAuto, errorHandler, callback), "TerrarumBasegameGameSaveThread")
savingThread.start()
@@ -78,11 +83,14 @@ object WriteSavegame {
fun immediate(time_t: Long, mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, errorHandler: (Throwable) -> Unit, callback: () -> Unit) {
savingStatus = 0
printdbg(this, "Immediate save fired")
installScreencap()
try { printdbg(this, "ScreencapExport installed: ${IngameRenderer.screencapExportCallback}") }
catch (e: UninitializedPropertyAccessException) { printdbg(this, "ScreencapExport installed: no") }
IngameRenderer.requestScreencap()
val savingThread = Thread(getSaveThread(time_t, mode, disk, outFile, ingame, isAuto, errorHandler, callback), "TerrarumBasegameGameSaveThread")
savingThread.start()

View File

@@ -108,6 +108,7 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
private val MODE_SAVE_MULTIPLE_CHOICES = 2
private val MODE_LOAD_DA_SHIT_ALREADY = 255
private val MODE_SAVE_DAMAGED = 256
private val MODE_SAVE_DELETE = 512
private lateinit var loadables: SavegameCollectionPair
@@ -229,7 +230,7 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
MODE_SAVE_DAMAGED
}
else {
val (p, w) = loadables.getManualSave()!!
val (p, w) = loadables.getLoadableSave()!!
UILoadGovernor.playerDisk = p; UILoadGovernor.worldDisk = w
if (loadables.newerSaveIsDamaged) {
@@ -329,10 +330,25 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
private var oldMode = -1
private val mode1Node = Yaml(UITitleRemoConYaml.injectedMenuSingleCharSel).parse()
private val mode2Node = Yaml(UITitleRemoConYaml.injectedMenuSingleWorldSel).parse()
// private val mode2Node = Yaml(UITitleRemoConYaml.injectedMenuSingleWorldSel).parse()
private val menus = listOf(mode1Node, mode2Node)
private val titles = listOf("CONTEXT_CHARACTER", "MENU_LABEL_WORLD")
// private val menus = listOf(mode1Node, mode2Node)
private val deleteCharacterButton = UIItemTextButton(
this, "CONTEXT_CHARACTER_DELETE",
UIRemoCon.menubarOffX - UIRemoCon.UIRemoConElement.paddingLeft + 11,
UIRemoCon.menubarOffY - UIRemoCon.UIRemoConElement.lineHeight * 3 + 16,
remoCon.width + UIRemoCon.UIRemoConElement.paddingLeft,
true,
inactiveCol = Toolkit.Theme.COL_RED,
activeCol = Toolkit.Theme.COL_REDD,
hitboxSize = UIRemoCon.UIRemoConElement.lineHeight - 2
).also {
it.clickOnceListener = { _,_ ->
mode = MODE_SAVE_DELETE
it.highlighted = true
}
}
init {
// this UI will NOT persist; the parent of the mode1Node must be set using an absolute value (e.g. treeRoot, not remoCon.currentRemoConContents)
@@ -341,22 +357,25 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
//printStackTrace(this)
mode1Node.parent = remoCon.treeRoot
mode2Node.parent = mode1Node
// mode2Node.parent = mode1Node
mode1Node.data = "MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadSavegame"
mode2Node.data = "MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadSavegame"
// mode2Node.data = "MENU_MODE_SINGLEPLAYER : net.torvald.terrarum.modulebasegame.ui.UILoadSavegame"
// printdbg(this, "mode1Node parent: ${mode1Node.parent?.data}") // will be 'null' because the parent is the root node
// printdbg(this, "mode1Node data: ${mode1Node.data}")
// printdbg(this, "mode2Node data: ${mode2Node.data}")
}
private fun modeChangedHandler(mode: Int) {
remoCon.setNewRemoConContents(menus[mode])
printdbg(this, "Change mode: $oldMode -> $mode")
// remoCon.setNewRemoConContents(menus[mode])
remoCon.setNewRemoConContents(mode1Node)
}
override fun updateUI(delta: Float) {
if (mode == MODE_SELECT) {
if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
if (oldMode != mode) {
modeChangedHandler(mode)
@@ -413,7 +432,7 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
LoadSavegame(UILoadGovernor.playerDisk!!, UILoadGovernor.worldDisk)
}
}
else if (mode == MODE_SELECT) {
else if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
batch.end()
val cells = getCells()
@@ -474,11 +493,6 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
val saveTex = TextureRegion(Texture(savePixmap)); saveTex.flip(false, true)
batch.inUse {
batch.draw(saveTex, (width - uiWidth - 10) / 2f, 0f)
// draw texts
val loadGameTitleStr = Lang[titles[mode]]// + "$EMDASH$hash"
// "Game Load"
App.fontUITitle.draw(batch, loadGameTitleStr, (width - App.fontUITitle.getWidth(loadGameTitleStr)).div(2).toFloat(), titleTextPosY.toFloat())
// Control help
App.fontGame.draw(batch, controlHelp, uiX.toFloat(), controlHelperY.toFloat())
}
@@ -506,11 +520,16 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
loadAutoThumbButton.render(batch, camera)
loadManualThumbButton.render(batch, camera)
}
if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
deleteCharacterButton.render(batch, camera)
}
}
override fun keyDown(keycode: Int): Boolean {
if (this.isVisible) {
if (this.isVisible && (mode == MODE_SELECT || mode == MODE_SAVE_DELETE)) {
val cells = getCells()
if ((keycode == Input.Keys.UP || keycode == App.getConfigInt("control_key_up")) && scrollTarget > 0) {
@@ -531,6 +550,7 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
if (mode == MODE_SELECT) getCells().forEach { it.touchDown(screenX, screenY, pointer, button) }
if (::loadAutoThumbButton.isInitialized && mode == MODE_SAVE_MULTIPLE_CHOICES) { loadAutoThumbButton.touchDown(screenX, screenY, pointer, button) }
if (::loadManualThumbButton.isInitialized && mode == MODE_SAVE_MULTIPLE_CHOICES) { loadManualThumbButton.touchDown(screenX, screenY, pointer, button) }
if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) deleteCharacterButton.touchDown(screenX, screenY, pointer, button)
return true
}
@@ -538,11 +558,12 @@ class UILoadSavegame(val remoCon: UIRemoCon) : Advanceable() {
if (mode == MODE_SELECT) getCells().forEach { it.touchUp(screenX, screenY, pointer, button) }
if (::loadAutoThumbButton.isInitialized && mode == MODE_SAVE_MULTIPLE_CHOICES) { loadAutoThumbButton.touchUp(screenX, screenY, pointer, button) }
if (::loadManualThumbButton.isInitialized && mode == MODE_SAVE_MULTIPLE_CHOICES) { loadManualThumbButton.touchUp(screenX, screenY, pointer, button) }
if (mode == MODE_SELECT || mode == MODE_SAVE_DELETE) deleteCharacterButton.touchDown(screenX, screenY, pointer, button)
return true
}
override fun scrolled(amountX: Float, amountY: Float): Boolean {
if (this.isVisible && mode == MODE_SELECT) {
if (this.isVisible && mode == MODE_SELECT || mode == MODE_SAVE_DELETE) {
val cells = getCells()
if (amountY <= -1f && scrollTarget > 0) {

View File

@@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Camera
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.Second
import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameactors.AVKey
@@ -64,6 +65,7 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
val savingThread = Thread({
printdbg(this, "Player saving thread fired")
disk.saveMode = 2 // auto, no quick
disk.capacity = 0L
@@ -79,9 +81,11 @@ class UINewCharacter(val remoCon: UIRemoCon) : UICanvas() {
UILoadGovernor.playerDisk = DiskSkimmer(outFile)
// comment above if chargen must send gamers back to the charcters list
printdbg(this, "playerdisk: ${UILoadGovernor.playerDisk?.diskFile?.path}")
}, "TerrarumBasegameNewCharcterSaveThread")
// savingThread.start()
// savingThread.join()
remoCon.openUI(UINewWorld(remoCon, savingThread)) // let UINewWorld handle the character file generation

View File

@@ -95,10 +95,11 @@ class UINewWorld(val remoCon: UIRemoCon) : UICanvas() {
goButton.clickOnceListener = { _, _ ->
// after the save is complete, proceed to new world generation
newPlayerCreationThread.start()
newPlayerCreationThread.join()
// printdbg(this, "generate! Size=${sizeSelector.selection}, Name=${nameInput.getTextOrPlaceholder()}, Seed=${seedInput.getTextOrPlaceholder()}")
printdbg(this, "generate! Size=${sizeSelector.selection}, Name=${nameInput.getTextOrPlaceholder()}, Seed=${seedInput.getTextOrPlaceholder()}")
val ingame = TerrarumIngame(App.batch)
val player = ReadActor.invoke(UILoadGovernor.playerDisk!!, ByteArray64Reader(UILoadGovernor.playerDisk!!.getFile(SAVEGAMEINFO)!!.bytes, Common.CHARSET)) as IngamePlayer

View File

@@ -28,6 +28,8 @@ object Toolkit : Disposable {
val COL_SELECTED = Color(0x00f8ff_ff) // cyan, HIGHLY SATURATED
val COL_MOUSE_UP = Color(0xfff066_ff.toInt()) // yellow (all yellows are of low saturation according to the colour science)
val COL_DISABLED = Color(0xaaaaaaff.toInt())
val COL_RED = Color(0xff8888ff.toInt())
val COL_REDD = Color(0xff4448ff.toInt())
/*
Try this for alt colour set:

View File

@@ -32,7 +32,7 @@ class UIAutosaveNotifier : UICanvas() {
private var errored = false
private var normalCol = Color.WHITE
private var errorCol = Color(0xFF8888FF.toInt())
private var errorCol = Toolkit.Theme.COL_RED
override fun updateUI(delta: Float) {
spinnerTimer += delta

View File

@@ -80,7 +80,7 @@ class UIItemTextLineInput(
companion object {
val TEXTINPUT_COL_TEXT = Color.WHITE
val TEXTINPUT_COL_TEXT_NOMORE = Color(0xFF8888FF.toInt())
val TEXTINPUT_COL_TEXT_NOMORE = Toolkit.Theme.COL_RED
val TEXTINPUT_COL_TEXT_DISABLED = Toolkit.Theme.COL_DISABLED
val TEXTINPUT_COL_BACKGROUND = Toolkit.Theme.COL_CELL_FILL
val TEXTINPUT_COL_BACKGROUND2 = Toolkit.Theme.COL_CELL_FILL.cpy()
@@ -155,7 +155,7 @@ class UIItemTextLineInput(
private var textColours = arrayOf(
Color.WHITE,
Color(0xff8888ff.toInt()),
Toolkit.Theme.COL_RED,
Color(0x888888ff.toInt())
)