new player save format writing

This commit is contained in:
minjaesong
2021-10-08 14:25:59 +09:00
parent 3f9b41fd29
commit aec6fea49e
21 changed files with 280 additions and 116 deletions

View File

@@ -14,7 +14,7 @@ The main game directory is composed of following directories:
- <e.g. Disk GUID>, TEVD { * } - <e.g. Disk GUID>, TEVD { * }
- <this directory can have anything> - <this directory can have anything>
+ Worlds + Worlds
- <World Name Here>, TVDA { (-1) WriteWorld, (actorID) actors (mainly fixtures) JSON, (0x1_0000_0000 + (layerNumber << 24) + chunkNumber) chunk data, (-2) screenshot.tga.gz taken by the last player } - <World Name Here>, TVDA { (-1) WriteWorld, (actorID) actors (mainly fixtures) JSON, (0x1_0000_0000L or (layerNumber shl 24) or chunkNumber) chunk data, (-2) screenshot.tga.gz taken by the last player }
``` ```
(TEVD stands for Terrarum Virtual Disk spec version 3, TVDA stands for spec version 254; both have MAGIC header of `TEVd`) (TEVD stands for Terrarum Virtual Disk spec version 3, TVDA stands for spec version 254; both have MAGIC header of `TEVd`)

View File

@@ -8,7 +8,6 @@
"strengthmult": [90,95,98,100,102,105,110], "strengthmult": [90,95,98,100,102,105,110],
"accel": 0.67, "accel": 0.67,
"accelbuff": 1.0,
"speed": 3.0, "speed": 3.0,
"speedmult": [100,100,100,100,100,100,100], "speedmult": [100,100,100,100,100,100,100],
@@ -22,8 +21,8 @@
"scalemult": [100,100,100,100,100,100,100], "scalemult": [100,100,100,100,100,100,100],
"encumbrance": 1000, "encumbrance": 1000,
"basedefence": 100, "defence": 100,
"basereach": 84, "reach": 84,
"toolsize": 15, "toolsize": 15,

View File

@@ -8,7 +8,6 @@
"strengthmult": [90,95,98,100,102,105,110], "strengthmult": [90,95,98,100,102,105,110],
"accel": 0.67, "accel": 0.67,
"accelbuff": 1.0,
"speed": 6.0, "speed": 6.0,
"speedmult": [100,100,100,100,100,100,100], "speedmult": [100,100,100,100,100,100,100],
@@ -22,8 +21,8 @@
"scalemult": [100,100,100,100,100,100,100], "scalemult": [100,100,100,100,100,100,100],
"encumbrance": 10000, "encumbrance": 10000,
"basedefence": 100, "defence": 100,
"basereach": 180, "reach": 180,
"toolsize": 40, "toolsize": 40,

View File

@@ -13,9 +13,9 @@ import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
*/ */
interface HasAssembledSprite { interface HasAssembledSprite {
/** ADL path for main sprite. Necessary. */ /** ADL for main sprite. Necessary. */
var animDesc: ADProperties? var animDesc: ADProperties?
/** ADL path for glow sprite. Optional. */ /** ADL for glow sprite. Optional. */
var animDescGlow: ADProperties? var animDescGlow: ADProperties?
// FIXME sometimes the animmation is invisible (row and nFrames mismatch -- row is changed to 1 but it's drawing 3rd frame?) // FIXME sometimes the animmation is invisible (row and nFrames mismatch -- row is changed to 1 but it's drawing 3rd frame?)

View File

@@ -34,7 +34,7 @@ internal data class Transform(val joint: Joint, val translate: ADPropertyObject.
class ADProperties { class ADProperties {
private var fileFrom = "" private var fileFrom = ""
private var adlString = "" @Transient private var adlString = ""
private val javaProp = Properties() private val javaProp = Properties()
/** Every key is CAPITALISED */ /** Every key is CAPITALISED */
@@ -52,8 +52,8 @@ class ADProperties {
/** an "animation frame" property (ANIM_RUN_1, ANIM_RUN_2) */ /** an "animation frame" property (ANIM_RUN_1, ANIM_RUN_2) */
internal lateinit var transforms: HashMap<String, List<Transform>>; private set internal lateinit var transforms: HashMap<String, List<Transform>>; private set
private val reservedProps = listOf("SPRITESHEET", "EXTENSION", "CONFIG", "BODYPARTS") @Transient private val reservedProps = listOf("SPRITESHEET", "EXTENSION", "CONFIG", "BODYPARTS")
private val animMustContain = listOf("DELAY", "ROW", "SKELETON") @Transient private val animMustContain = listOf("DELAY", "ROW", "SKELETON")
lateinit var baseFilename: String; private set lateinit var baseFilename: String; private set
lateinit var extension: String; private set lateinit var extension: String; private set
@@ -63,9 +63,9 @@ class ADProperties {
internal val origin: ADPropertyObject.Vector2i internal val origin: ADPropertyObject.Vector2i
get() = ADPropertyObject.Vector2i(originX, 0) get() = ADPropertyObject.Vector2i(originX, 0)
private val animFrameSuffixRegex = Regex("""_[0-9]+""") @Transient private val animFrameSuffixRegex = Regex("""_[0-9]+""")
private val ALL_JOINT = Joint(ALL_JOINT_SELECT_KEY, ADPropertyObject.Vector2i(0, 0)) @Transient private val ALL_JOINT = Joint(ALL_JOINT_SELECT_KEY, ADPropertyObject.Vector2i(0, 0))
var rows = -1; private set var rows = -1; private set
var cols = -1; private set var cols = -1; private set

View File

@@ -979,7 +979,7 @@ public class App implements ApplicationListener {
} }
private static void createDirs() { private static void createDirs() {
File[] dirs = {new File(saveDir)}; File[] dirs = {new File(saveDir), new File(saveSharedDir), new File(playersDir), new File(worldsDir)};
for (File it : dirs) { for (File it : dirs) {
if (!it.exists()) if (!it.exists())

View File

@@ -12,6 +12,7 @@ import net.torvald.terrarum.gameitem.ItemID
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid import net.torvald.terrarum.modulebasegame.gameactors.ActorHumanoid
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.modulebasegame.ui.Notification import net.torvald.terrarum.modulebasegame.ui.Notification
import net.torvald.terrarum.modulebasegame.ui.UITooltip import net.torvald.terrarum.modulebasegame.ui.UITooltip
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
@@ -48,8 +49,8 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
} }
} }
lateinit var savegameArchive: VirtualDisk lateinit var worldDisk: VirtualDisk; internal set
internal set lateinit var playerDisk: VirtualDisk; internal set
var savegameNickname: String = "SplinesReticulated" var savegameNickname: String = "SplinesReticulated"
internal set internal set
@@ -106,8 +107,7 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
/** /**
* The actual gamer * The actual gamer
*/ */
val actorGamer: ActorHumanoid open lateinit var actorGamer: IngamePlayer
get() = getActorByID(Terrarum.PLAYER_REF_ID) as ActorHumanoid
open var gameInitialised = false open var gameInitialised = false
internal set internal set
@@ -362,29 +362,34 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
} }
} }
fun makeSavegameBackupCopy() {
System.err.println("This function is deleteh")
printStackTrace(this)
}
/** /**
* Copies most recent `save` to `save.1`, leaving `save` for overwriting, previous `save.1` will be copied to `save.2` * Copies most recent `save` to `save.1`, leaving `save` for overwriting, previous `save.1` will be copied to `save.2`
*/ */
fun makeSavegameBackupCopy() { fun makeSavegameBackupCopy(file: File) {
try { try {
// do not overwrite clean .2 with dirty .1 // do not overwrite clean .2 with dirty .1
val file2 = File(App.saveDir, INGAME.savegameNickname + ".3") val file2 = File("${file.absolutePath}.3")
val file1 = File(App.saveDir, INGAME.savegameNickname + ".2") val file1 = File("${file.absolutePath}.2")
val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r } val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r }
val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r } val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r }
if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) file1.copyTo(file2, true) if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) file1.copyTo(file2, true)
} catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {} } catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try { try {
// do not overwrite clean .2 with dirty .1 // do not overwrite clean .2 with dirty .1
val file2 = File(App.saveDir, INGAME.savegameNickname + ".2") val file2 = File("${file.absolutePath}.2")
val file1 = File(App.saveDir, INGAME.savegameNickname + ".1") val file1 = File("${file.absolutePath}.1")
val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r } val flags2 = FileInputStream(file2).let { it.skip(49L); val r = it.read(); it.close(); r }
val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r } val flags1 = FileInputStream(file1).let { it.skip(49L); val r = it.read(); it.close(); r }
if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) file1.copyTo(file2, true) if (!(flags2 == 0 && flags1 != 0) || !file2.exists()) file1.copyTo(file2, true)
} catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {} } catch (e: NoSuchFileException) {} catch (e: FileNotFoundException) {}
try { try {
File(App.saveDir, INGAME.savegameNickname).copyTo( file.copyTo(
File(App.saveDir, INGAME.savegameNickname + ".1"), // don't use .bak as it's used by the savecracker File("${file.absolutePath}.1"), // don't use .bak as it's used by the savecracker
true true
) )
} catch (e: NoSuchFileException) {} } catch (e: NoSuchFileException) {}
@@ -392,8 +397,10 @@ open class IngameInstance(val batch: SpriteBatch) : Screen {
fun getSaveFileMain() = File(App.saveDir, savegameNickname) // fun getSaveFileMain() = File(App.saveDir, savegameNickname)
fun getWorldSaveFiledesc(filename: String) = File(App.worldsDir, filename)
fun getPlayerSaveFiledesc(filename: String) = File(App.playersDir, filename)
fun getSharedSaveFiledesc(filename: String) = File(App.saveSharedDir, filename)
// simple euclidean norm, squared // simple euclidean norm, squared

View File

@@ -88,12 +88,14 @@ object AVKey {
/** (unit TBA) /** (unit TBA)
* base defence point of the species * base defence point of the species
*/ */
const val BASEDEFENCE = "basedefence" const val DEFENCE = "defence"
const val DEFENCEBUFF = "$DEFENCE$BUFF"
/** Pixels /** Pixels
* base hand reach of the species (only affects the gameplay as the player) * base hand reach of the species (only affects the gameplay as the player)
*/ */
const val BASEREACH = "basereach" const val REACH = "reach"
const val REACHBUFF = "$REACH$BUFF"
/** (unit TBA) /** (unit TBA)
* current defence point of worn armour(s) * current defence point of worn armour(s)

View File

@@ -329,7 +329,7 @@ fun inInteractableRange(actor: ActorWithBody, action: () -> Boolean): Boolean {
val mousePos3 = Vector2(Terrarum.mouseX - INGAME.world.width * TILE_SIZED, Terrarum.mouseY) val mousePos3 = Vector2(Terrarum.mouseX - INGAME.world.width * TILE_SIZED, Terrarum.mouseY)
val actorPos = actor.centrePosVector val actorPos = actor.centrePosVector
val dist = minOf(actorPos.distanceSquared(mousePos1), actorPos.distanceSquared(mousePos2), actorPos.distanceSquared(mousePos3)) val dist = minOf(actorPos.distanceSquared(mousePos1), actorPos.distanceSquared(mousePos2), actorPos.distanceSquared(mousePos3))
val distMax = actor.actorValue.getAsInt(AVKey.BASEREACH)!! * actor.scale // perform some error checking here val distMax = actor.actorValue.getAsDouble(AVKey.REACH)!! * (actor.actorValue.getAsDouble(AVKey.REACHBUFF) ?: 1.0) * actor.scale // perform some error checking here
if (dist <= distMax.sqr()) return action() else return false if (dist <= distMax.sqr()) return action() else return false
} }
fun IntRange.pickRandom() = HQRNG().nextInt(this.endInclusive - this.start + 1) + this.start // count() on 200 million entries? Se on vitun hyvää idea fun IntRange.pickRandom() = HQRNG().nextInt(this.endInclusive - this.start + 1) + this.start // count() on 200 million entries? Se on vitun hyvää idea

View File

@@ -10,6 +10,7 @@ import net.torvald.terrarum.blockproperties.Fluid
import net.torvald.terrarum.gameactors.ActorID import net.torvald.terrarum.gameactors.ActorID
import net.torvald.terrarum.gameactors.WireActor import net.torvald.terrarum.gameactors.WireActor
import net.torvald.terrarum.gameitem.ItemID import net.torvald.terrarum.gameitem.ItemID
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.utils.* import net.torvald.terrarum.utils.*
import net.torvald.util.SortedArrayList import net.torvald.util.SortedArrayList
@@ -22,6 +23,18 @@ import kotlin.math.absoluteValue
typealias BlockAddress = Long typealias BlockAddress = Long
class PhysicalStatus() {
// bottom-center point
var position = Point2d()
// some actorvalues
var scale = 1.0
constructor(player: IngamePlayer) : this() {
this.position = Point2d(player.hitbox.canonicalX, player.hitbox.canonicalY)
this.scale = player.avBaseScale
}
}
/** /**
* Special version of GameWorld where layer data are not transient * Special version of GameWorld where layer data are not transient
*/ */
@@ -38,6 +51,8 @@ open class GameWorld() : Disposable {
var width: Int = 999; private set var width: Int = 999; private set
var height: Int = 999; private set var height: Int = 999; private set
var playersLastStatus = PlayersLastPhysics() // only gets used when the game saves and loads
/** Creation time for this world, NOT the entire savegame */ /** Creation time for this world, NOT the entire savegame */
internal var creationTime: Long = App.getTIME_T() internal var creationTime: Long = App.getTIME_T()
internal set internal set
@@ -106,8 +121,8 @@ open class GameWorld() : Disposable {
val extraFields = HashMap<String, Any?>() val extraFields = HashMap<String, Any?>()
internal var genver = -1 internal var genver = -1 // only gets used when the game saves and loads
internal var comp = -1 internal var comp = -1 // only gets used when the game saves and loads
@Deprecated("This value is only used for savegames; DO NOT USE THIS", ReplaceWith("INGAME.actorContainerActive", "net.torvald.terrarum.INGAME")) @Deprecated("This value is only used for savegames; DO NOT USE THIS", ReplaceWith("INGAME.actorContainerActive", "net.torvald.terrarum.INGAME"))
internal val actors = ArrayList<ActorID>() // only filled up on save and load; DO NOT USE THIS internal val actors = ArrayList<ActorID>() // only filled up on save and load; DO NOT USE THIS

View File

@@ -46,7 +46,11 @@ import net.torvald.terrarum.worlddrawer.FeaturesDrawer
import net.torvald.terrarum.worlddrawer.WorldCamera import net.torvald.terrarum.worlddrawer.WorldCamera
import net.torvald.util.CircularArray import net.torvald.util.CircularArray
import org.khelekore.prtree.PRTree import org.khelekore.prtree.PRTree
import java.util.*
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.HashSet
/** /**
@@ -218,7 +222,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
// gameLoadMode and gameLoadInfoPayload must be set beforehand!! // gameLoadMode and gameLoadInfoPayload must be set beforehand!!
when (gameLoadMode) { when (gameLoadMode) {
GameLoadMode.CREATE_NEW -> enterCreateNewWorld(gameLoadInfoPayload as NewWorldParameters) GameLoadMode.CREATE_NEW -> enterCreateNewWorld(gameLoadInfoPayload as NewGameParams)
GameLoadMode.LOAD_FROM -> enterLoadFromSave(gameLoadInfoPayload as Codices) GameLoadMode.LOAD_FROM -> enterLoadFromSave(gameLoadInfoPayload as Codices)
} }
@@ -228,6 +232,11 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
super.show() // this function sets gameInitialised = true super.show() // this function sets gameInitialised = true
} }
data class NewGameParams(
val player: IngamePlayer,
val newWorldParams: NewWorldParameters
)
data class NewWorldParameters( data class NewWorldParameters(
val width: Int, val width: Int,
val height: Int, val height: Int,
@@ -254,14 +263,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
val actors: List<ActorID> val actors: List<ActorID>
) )
private fun setTheRealGamerFirstTime(actor: IngamePlayer) {
if (actor.referenceID != Terrarum.PLAYER_REF_ID) {
throw Error()
}
actorNowPlaying = actor
addNewActor(actorNowPlaying)
}
/** /**
* Init instance by loading saved world * Init instance by loading saved world
@@ -308,33 +310,49 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
// go to spawn position // go to spawn position
printdbg(this, "World Spawn position: (${world.spawnX}, ${world.spawnY})") printdbg(this, "World Spawn position: (${world.spawnX}, ${world.spawnY})")
// setTheRealGamerFirstTime(PlayerBuilderSigrid()) val worldSavefileName = savegameNickname
setTheRealGamerFirstTime(PlayerBuilderTestSubject1()) val playerSavefileName = actorGamer.actorValue.getAsString(AVKey.NAME) ?: "Player-${actorGamer.uuid}"
// setTheRealGamerFirstTime(PlayerBuilderWerebeastTest())
savegameArchive = VDUtil.createNewDisk( worldDisk = VDUtil.createNewDisk(
1L shl 60, 1L shl 60,
savegameNickname, worldSavefileName,
Common.CHARSET Common.CHARSET
) )
actorNowPlaying!!.setPosition( playerDisk = VDUtil.createNewDisk(
1L shl 60,
playerSavefileName,
Common.CHARSET
)
actorGamer.setPosition(
world.spawnX * TILE_SIZED, world.spawnX * TILE_SIZED,
world.spawnY * TILE_SIZED world.spawnY * TILE_SIZED
) )
// make initial savefile // make initial savefile
// we're not writing multiple files at one go because:
// 1. lighten the IO burden
// 2. cannot sync up the "counter" to determine whether both are finished
uiAutosaveNotifier.setAsOpen() uiAutosaveNotifier.setAsOpen()
WriteSavegame.immediate(savegameArchive, getSaveFileMain(), this, true) { WriteSavegame.immediate(WriteSavegame.SaveMode.PLAYER, playerDisk, getPlayerSaveFiledesc(playerSavefileName), this, false, true) {
makeSavegameBackupCopy() // don't put it on the postInit() or render(); must be called using callback makeSavegameBackupCopy(getPlayerSaveFiledesc(playerSavefileName))
uiAutosaveNotifier.setAsClose()
WriteSavegame.immediate(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()
}
} }
} }
/** /**
* Init instance by creating new world * Init instance by creating new world
*/ */
private fun enterCreateNewWorld(worldParams: NewWorldParameters) { private fun enterCreateNewWorld(newGameParams: NewGameParams) {
val player = newGameParams.player
val worldParams = newGameParams.newWorldParams
printdbg(this, "Ingame called") printdbg(this, "Ingame called")
printStackTrace(this) printStackTrace(this)
@@ -343,6 +361,7 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
} }
else { else {
App.getLoadScreen().addMessage("${App.GAME_NAME} version ${App.getVERSION_STRING()}") App.getLoadScreen().addMessage("${App.GAME_NAME} version ${App.getVERSION_STRING()}")
App.getLoadScreen().addMessage("Creating new world") App.getLoadScreen().addMessage("Creating new world")
@@ -364,6 +383,19 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
historicalFigureIDBucket = ArrayList<Int>() historicalFigureIDBucket = ArrayList<Int>()
savegameNickname = worldParams.savegameName savegameNickname = worldParams.savegameName
player.worldCurrentlyPlaying = UUID.fromString(world.worldIndex.toString())
world.worldCreator = UUID.fromString(player.uuid.toString())
printdbg(this, "new woridIndex: ${world.worldIndex}")
printdbg(this, "worldCurrentlyPlaying: ${player.worldCurrentlyPlaying}")
actorNowPlaying = player
actorGamer = player
addNewActor(player)
} }
KeyToggler.forceSet(Input.Keys.Q, false) KeyToggler.forceSet(Input.Keys.Q, false)
@@ -978,13 +1010,13 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
fun queueAutosave() { fun queueAutosave() {
val start = System.nanoTime() val start = System.nanoTime()
uiAutosaveNotifier.setAsOpen() /*uiAutosaveNotifier.setAsOpen()
makeSavegameBackupCopy() makeSavegameBackupCopy()
WriteSavegame.quick(savegameArchive, getSaveFileMain(), this, true) { WriteSavegame.quick(savegameArchive, getSaveFileMain(), this, true) {
uiAutosaveNotifier.setAsClose() uiAutosaveNotifier.setAsClose()
debugTimers.put("Last Autosave Duration", System.nanoTime() - start) debugTimers.put("Last Autosave Duration", System.nanoTime() - start)
} }*/
} }
@@ -1022,8 +1054,8 @@ open class TerrarumIngame(batch: SpriteBatch) : IngameInstance(batch) {
override fun removeActor(actor: Actor?) { override fun removeActor(actor: Actor?) {
if (actor == null) return if (actor == null) return
if (actor.referenceID == actorGamer.referenceID || actor.referenceID == 0x51621D) // do not delete this magic // if (actor.referenceID == actorGamer.referenceID || actor.referenceID == 0x51621D) // do not delete this magic
throw ProtectedActorRemovalException("Player") // throw ProtectedActorRemovalException("Player")
forceRemoveActor(actor) forceRemoveActor(actor)
} }

View File

@@ -24,7 +24,7 @@ object Save : ConsoleCommand {
val disk = VDUtil.createNewDisk(1L shl 60, savename, Common.CHARSET) val disk = VDUtil.createNewDisk(1L shl 60, savename, Common.CHARSET)
val file = File(App.saveDir + "/${args[1]}") val file = File(App.saveDir + "/${args[1]}")
WriteSavegame(disk, file, ingame, false) // WriteSavegame(disk, file, ingame, false)
} }
catch (e: IOException) { catch (e: IOException) {
@@ -47,9 +47,7 @@ object Quicksave : ConsoleCommand {
override fun execute(args: Array<String>) { override fun execute(args: Array<String>) {
val ingame = Terrarum.ingame!! as TerrarumIngame val ingame = Terrarum.ingame!! as TerrarumIngame
WriteSavegame.quick(ingame.savegameArchive, ingame.getSaveFileMain(), ingame, false) { // WriteSavegame.quick(ingame.savegameArchive, ingame.getSaveFileMain(), ingame, false) {}
}
} }
override fun printUsage() { override fun printUsage() {

View File

@@ -1,10 +1,15 @@
package net.torvald.terrarum.modulebasegame.gameactors package net.torvald.terrarum.modulebasegame.gameactors
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Texture
import net.torvald.spriteanimation.HasAssembledSprite import net.torvald.spriteanimation.HasAssembledSprite
import net.torvald.spriteanimation.SpriteAnimation
import net.torvald.spriteassembler.ADProperties import net.torvald.spriteassembler.ADProperties
import net.torvald.spriteassembler.AssembleSheetPixmap
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.gameactors.AVKey import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.tvda.SimpleFileSystem
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import java.util.* import java.util.*
@@ -14,12 +19,16 @@ import java.util.*
* Created by minjaesong on 2015-12-31. * Created by minjaesong on 2015-12-31.
*/ */
class IngamePlayer : ActorHumanoid, HasAssembledSprite { class IngamePlayer : ActorHumanoid {
var uuid = UUID.randomUUID(); private set
var worldCurrentlyPlaying: UUID = UUID(0L,0L) // only filled up on save and load; DO NOT USE THIS
/** ADL for main sprite. Necessary. */
@Transient var animDesc: ADProperties? = null
/** ADL for glow sprite. Optional. */
@Transient var animDescGlow: ADProperties? = null
var UUID = UUID(0L,0L); private set
internal var worldCurrentlyPlaying: UUID = UUID(0L,0L) // only filled up on save and load; DO NOT USE THIS
override var animDesc: ADProperties? = null
override var animDescGlow: ADProperties? = null
private constructor() private constructor()
@@ -38,7 +47,82 @@ class IngamePlayer : ActorHumanoid, HasAssembledSprite {
referenceID = Terrarum.PLAYER_REF_ID // TODO assign random ID referenceID = Terrarum.PLAYER_REF_ID // TODO assign random ID
density = BASE_DENSITY density = BASE_DENSITY
collisionType = COLLISION_KINEMATIC collisionType = COLLISION_KINEMATIC
worldCurrentlyPlaying = Terrarum.ingame?.world?.worldIndex ?: UUID(0L,0L)
} }
/**
* Example usage:
* ```
* this.animDescPath = "..."
* this.animDescPathGlow = "..."
* this.sprite = SpriteAnimation(actor)
* this.spriteGlow = SpriteAnimation(actor)
* reassembleSprite(this.sprite, this.spriteGlow)
* ```
*/
fun reassembleSprite(sprite: SpriteAnimation?, anim: ADProperties?, spriteGlow: SpriteAnimation? = null, animGlow: ADProperties? = null) {
if (anim != null && sprite != null)
_rebuild(anim, sprite)
if (animGlow != null && spriteGlow != null)
_rebuild(animGlow, spriteGlow)
}
fun reassembleSprite(disk: SimpleFileSystem, sprite: SpriteAnimation?, anim: ADProperties?, spriteGlow: SpriteAnimation? = null, animGlow: ADProperties? = null) {
if (anim != null && sprite != null)
_rebuild(disk, anim, sprite)
if (animGlow != null && spriteGlow != null)
_rebuild(disk, animGlow, spriteGlow)
}
private fun _rebuild(ad: ADProperties, sprite: SpriteAnimation) {
// TODO injecting held item/armour pictures? Would it be AssembleSheetPixmap's job?
val pixmap = AssembleSheetPixmap.fromAssetsDir(ad)
val texture = Texture(pixmap)
texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
pixmap.dispose()
val regionPack = TextureRegionPack(texture, ad.frameWidth, ad.frameHeight)
val newAnimDelays = FloatArray(ad.animations.size)
val newAnimFrames = IntArray(ad.animations.size)
ad.animations.forEach { t, u ->
val index = u.row - 1
newAnimDelays[index] = u.delay
newAnimFrames[index] = u.frames
}
sprite.setSpriteImage(regionPack)
sprite.delays = newAnimDelays
sprite.nFrames = newAnimFrames
sprite.nRows = newAnimDelays.size
}
private fun _rebuild(disk: SimpleFileSystem, ad: ADProperties, sprite: SpriteAnimation) {
// TODO injecting held item/armour pictures? Would it be AssembleSheetPixmap's job?
val pixmap = AssembleSheetPixmap.fromVirtualDisk(disk, ad)
val texture = Texture(pixmap)
texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
pixmap.dispose()
val regionPack = TextureRegionPack(texture, ad.frameWidth, ad.frameHeight)
val newAnimDelays = FloatArray(ad.animations.size)
val newAnimFrames = IntArray(ad.animations.size)
ad.animations.forEach { t, u ->
val index = u.row - 1
newAnimDelays[index] = u.delay
newAnimFrames[index] = u.frames
}
sprite.setSpriteImage(regionPack)
sprite.delays = newAnimDelays
sprite.nFrames = newAnimFrames
sprite.nRows = newAnimDelays.size
}
} }

View File

@@ -90,13 +90,13 @@ class UIInventoryEscMenu(val full: UIInventoryFull) : UICanvas() {
INGAME.makeSavegameBackupCopy() INGAME.makeSavegameBackupCopy()
// save the game // save the game
WriteSavegame(INGAME.savegameArchive, File(App.saveDir, INGAME.savegameNickname), Terrarum.ingame!! as TerrarumIngame, false) { /*WriteSavegame(INGAME.savegameArchive, File(App.saveDir, INGAME.savegameNickname), Terrarum.ingame!! as TerrarumIngame, false) {
// callback: // callback:
System.gc() System.gc()
screen = 0 screen = 0
full.handler.unlockToggle() full.handler.unlockToggle()
full.unlockTransition() full.unlockTransition()
} }*/
} }
2 -> { 2 -> {
screen = 4; gameMenuButtons.deselect() screen = 4; gameMenuButtons.deselect()

View File

@@ -9,6 +9,7 @@ import net.torvald.terrarum.Second
import net.torvald.terrarum.Terrarum import net.torvald.terrarum.Terrarum
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.WorldgenLoadScreen import net.torvald.terrarum.modulebasegame.WorldgenLoadScreen
import net.torvald.terrarum.modulebasegame.gameactors.PlayerBuilderTestSubject1
import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UICanvas
import net.torvald.terrarum.utils.RandomWordsName import net.torvald.terrarum.utils.RandomWordsName
@@ -36,9 +37,11 @@ class UIProxyNewRandomGame : UICanvas() {
override fun endOpening(delta: Float) { override fun endOpening(delta: Float) {
printdbg(this, "endOpening") printdbg(this, "endOpening")
val ingame = TerrarumIngame(App.batch) val ingame = TerrarumIngame(App.batch)
val worldParam = TerrarumIngame.NewWorldParameters(2880, 1350, HQRNG().nextLong(), RandomWordsName(4)) val worldParam = TerrarumIngame.NewGameParams(
PlayerBuilderTestSubject1(),
TerrarumIngame.NewWorldParameters(2880, 1350, HQRNG().nextLong(), RandomWordsName(4))
)
// val worldParam = TerrarumIngame.NewWorldParameters(2880, 1350, 0x51621D) // val worldParam = TerrarumIngame.NewWorldParameters(2880, 1350, 0x51621D)
// val worldParam = TerrarumIngame.NewWorldParameters(6030, 1800, HQRNG().nextLong()) // small // val worldParam = TerrarumIngame.NewWorldParameters(6030, 1800, HQRNG().nextLong()) // small
@@ -51,7 +54,7 @@ class UIProxyNewRandomGame : UICanvas() {
Terrarum.setCurrentIngameInstance(ingame) Terrarum.setCurrentIngameInstance(ingame)
//LoadScreen.screenToLoad = ingame //LoadScreen.screenToLoad = ingame
//AppLoader.setScreen(LoadScreen) //AppLoader.setScreen(LoadScreen)
val loadScreen = WorldgenLoadScreen(ingame, worldParam.width, worldParam.height) val loadScreen = WorldgenLoadScreen(ingame, worldParam.newWorldParams.width, worldParam.newWorldParams.height)
App.setLoadScreen(loadScreen) App.setLoadScreen(loadScreen)
} }

View File

@@ -43,6 +43,9 @@ object Common {
// install custom (de)serialiser // install custom (de)serialiser
init { init {
jsoner.ignoreUnknownFields = true
// BigInteger // BigInteger
jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer<BigInteger> { jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer<BigInteger> {
override fun write(json: Json, obj: BigInteger?, knownType: Class<*>?) { override fun write(json: Json, obj: BigInteger?, knownType: Class<*>?) {

View File

@@ -3,36 +3,31 @@ package net.torvald.terrarum.serialise
import net.torvald.gdx.graphics.PixmapIO2 import net.torvald.gdx.graphics.PixmapIO2
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.console.Echo import net.torvald.terrarum.console.Echo
import net.torvald.terrarum.gameworld.PhysicalStatus
import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.tvda.* import net.torvald.terrarum.tvda.*
import java.io.File import java.io.File
import java.util.zip.GZIPOutputStream 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)
}
/** /**
* Created by minjaesong on 2021-09-14. * Created by minjaesong on 2021-09-14.
*/ */
class GameSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : Runnable { class WorldSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : Runnable {
/**
* 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)
}
private val chunkProgressMultiplier = 1f
private val actorProgressMultiplier = 1f
override fun run() { override fun run() {
callback()
return
// TODO //
disk.saveMode = 2 * isAuto.toInt() // no quick disk.saveMode = 2 * isAuto.toInt() // no quick
@@ -42,15 +37,14 @@ class GameSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Ter
} }
} }
val playersList: List<IngamePlayer> = listOf(ingame.actorContainerActive, ingame.actorContainerInactive).flatMap{ it.filter { it is IngamePlayer } } as List<IngamePlayer>
val actorsList = listOf(ingame.actorContainerActive, ingame.actorContainerInactive).flatMap { it.filter { WriteWorld.actorAcceptable(it) } } val actorsList = listOf(ingame.actorContainerActive, ingame.actorContainerInactive).flatMap { it.filter { WriteWorld.actorAcceptable(it) } }
val layers = intArrayOf(0,1).map { ingame.world.getLayer(it) } val layers = intArrayOf(0,1).map { ingame.world.getLayer(it) }
val cw = ingame.world.width / LandUtil.CHUNK_W val cw = ingame.world.width / LandUtil.CHUNK_W
val ch = ingame.world.height / LandUtil.CHUNK_H val ch = ingame.world.height / LandUtil.CHUNK_H
WriteSavegame.saveProgress = 0f WriteSavegame.saveProgress = 0f
WriteSavegame.saveProgressMax = 2f + WriteSavegame.saveProgressMax = 2f + (cw * ch * layers.size) + actorsList.size
(cw * ch * layers.size) * chunkProgressMultiplier +
actorsList.size * actorProgressMultiplier
val tgaout = ByteArray64GrowableOutputStream() val tgaout = ByteArray64GrowableOutputStream()
@@ -62,11 +56,6 @@ class GameSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Ter
val time_t = App.getTIME_T() val time_t = App.getTIME_T()
// Write Meta //
val metaContent = EntryFile(WriteMeta.encodeToByteArray64(ingame, time_t))
val meta = DiskEntry(-1, 0, creation_t, time_t, metaContent)
addFile(disk, meta)
if (hasThumbnail) { if (hasThumbnail) {
PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true) PixmapIO2._writeTGA(gzout, IngameRenderer.fboRGBexport, true, true)
IngameRenderer.fboRGBexport.dispose() IngameRenderer.fboRGBexport.dispose()
@@ -114,10 +103,13 @@ class GameSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Ter
// addFile(disk, apocryphas) // addFile(disk, apocryphas)
// Write World // // Write World //
// val worldNum = ingame.world.worldIndex // record all player's last position
// val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t)) playersList.forEach {
// val world = DiskEntry(worldNum.toLong(), 0, creation_t, time_t, worldMeta) ingame.world.playersLastStatus[it.uuid] = PhysicalStatus(it)
// addFile(disk, world) }
val worldMeta = EntryFile(WriteWorld.encodeToByteArray64(ingame, time_t))
val world = DiskEntry(-1L, 0, creation_t, time_t, worldMeta)
addFile(disk, world)
WriteSavegame.saveProgress += 1f WriteSavegame.saveProgress += 1f
@@ -137,7 +129,7 @@ class GameSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Ter
// "W1L0-92,15" // "W1L0-92,15"
addFile(disk, entry) addFile(disk, entry)
WriteSavegame.saveProgress += chunkProgressMultiplier WriteSavegame.saveProgress += 1
} }
} }
} }
@@ -151,7 +143,7 @@ class GameSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Ter
val actor = DiskEntry(it.referenceID.toLong(), 0, creation_t, time_t, actorContent) val actor = DiskEntry(it.referenceID.toLong(), 0, creation_t, time_t, actorContent)
addFile(disk, actor) addFile(disk, actor)
WriteSavegame.saveProgress += actorProgressMultiplier WriteSavegame.saveProgress += 1
} }
@@ -172,6 +164,25 @@ class GameSavingThread(val disk: VirtualDisk, val outFile: File, val ingame: Ter
WriteSavegame.savingStatus = 255 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 disk: VirtualDisk, val outFile: File, val ingame: TerrarumIngame, val hasThumbnail: Boolean, val isAuto: Boolean, val callback: () -> Unit) : Runnable {
override fun run() {
disk.saveMode = 2 * isAuto.toInt() // no quick
disk.capacity = 0L
Echo("Writing The Player...")
WritePlayer(ingame.actorGamer, disk)
VDUtil.dumpToRealMachine(disk, outFile)
callback() callback()
} }
} }

View File

@@ -46,7 +46,7 @@ object WriteActor {
} }
/** /**
* Player-specific [WriteActor]. * Player-specific [WriteActor]. Will write JSON and Animation Description Languages
* *
* Created by minjaesong on 2021-10-07. * Created by minjaesong on 2021-10-07.
*/ */
@@ -74,12 +74,12 @@ object WritePlayer {
val adlContents = EntryFile(ByteArray64.fromByteArray(adl.toByteArray(Common.CHARSET))) val adlContents = EntryFile(ByteArray64.fromByteArray(adl.toByteArray(Common.CHARSET)))
val adlCreationDate = playerDisk.getEntry(-2)?.creationDate ?: time_t val adlCreationDate = playerDisk.getEntry(-2)?.creationDate ?: time_t
addFile(playerDisk, DiskEntry(-1L, 0L, adlCreationDate, time_t, adlContents)) addFile(playerDisk, DiskEntry(-2L, 0L, adlCreationDate, time_t, adlContents))
if (adlGlow != null) { if (adlGlow != null) {
val adlGlowContents = EntryFile(ByteArray64.fromByteArray(adlGlow.toByteArray(Common.CHARSET))) val adlGlowContents = EntryFile(ByteArray64.fromByteArray(adlGlow.toByteArray(Common.CHARSET)))
val adlGlowCreationDate = playerDisk.getEntry(-3)?.creationDate ?: time_t val adlGlowCreationDate = playerDisk.getEntry(-3)?.creationDate ?: time_t
addFile(playerDisk, DiskEntry(-1L, 0L, adlGlowCreationDate, time_t, adlGlowContents)) addFile(playerDisk, DiskEntry(-3L, 0L, adlGlowCreationDate, time_t, adlGlowContents))
} }

View File

@@ -23,11 +23,21 @@ import java.io.Reader
*/ */
object WriteSavegame { object WriteSavegame {
enum class SaveMode {
META, PLAYER, WORLD, SHARED
}
@Volatile var savingStatus = -1 // -1: not started, 0: saving in progress, 255: saving finished @Volatile var savingStatus = -1 // -1: not started, 0: saving in progress, 255: saving finished
@Volatile var saveProgress = 0f @Volatile var saveProgress = 0f
@Volatile var saveProgressMax = 1f @Volatile var saveProgressMax = 1f
operator fun invoke(disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, callback: () -> Unit = {}) { 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)
else -> throw IllegalArgumentException("$mode")
}
operator fun invoke(mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, callback: () -> Unit = {}) {
savingStatus = 0 savingStatus = 0
Echo("Save queued") Echo("Save queued")
@@ -47,7 +57,7 @@ object WriteSavegame {
} }
IngameRenderer.screencapRequested = true IngameRenderer.screencapRequested = true
val savingThread = Thread(GameSavingThread(disk, outFile, ingame, true, isAuto, callback), "TerrarumBasegameGameSaveThread") val savingThread = Thread(getSaveThread(mode, disk, outFile, ingame, true, isAuto, callback), "TerrarumBasegameGameSaveThread")
savingThread.start() savingThread.start()
// it is caller's job to keep the game paused or keep a "save in progress" ui up // it is caller's job to keep the game paused or keep a "save in progress" ui up
@@ -55,16 +65,13 @@ object WriteSavegame {
} }
fun immediate(disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, isAuto: Boolean, callback: () -> Unit = {}) { fun immediate(mode: SaveMode, disk: VirtualDisk, outFile: File, ingame: TerrarumIngame, hasThumbnail: Boolean, isAuto: Boolean, callback: () -> Unit = {}) {
return
// TODO //
savingStatus = 0 savingStatus = 0
Echo("Immediate save fired") Echo("Immediate save fired")
val savingThread = Thread(GameSavingThread(disk, outFile, ingame, false, isAuto, callback), "TerrarumBasegameGameSaveThread") val savingThread = Thread(getSaveThread(mode, disk, outFile, ingame, false, isAuto, callback), "TerrarumBasegameGameSaveThread")
savingThread.start() savingThread.start()
// it is caller's job to keep the game paused or keep a "save in progress" ui up // it is caller's job to keep the game paused or keep a "save in progress" ui up
@@ -134,7 +141,6 @@ object LoadSavegame {
world.layerTerrain = BlockLayer(world.width, world.height) world.layerTerrain = BlockLayer(world.width, world.height)
world.layerWall = BlockLayer(world.width, world.height) world.layerWall = BlockLayer(world.width, world.height)
newIngame.savegameArchive = disk
newIngame.creationTime = meta.creation_t newIngame.creationTime = meta.creation_t
newIngame.lastPlayTime = meta.lastplay_t newIngame.lastPlayTime = meta.lastplay_t
newIngame.totalPlayTime = meta.playtime_t newIngame.totalPlayTime = meta.playtime_t

View File

@@ -9,6 +9,7 @@ import net.torvald.terrarum.gameworld.BlockLayer
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.GameWorldTitleScreen import net.torvald.terrarum.gameworld.GameWorldTitleScreen
import net.torvald.terrarum.modulebasegame.TerrarumIngame import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.realestate.LandUtil import net.torvald.terrarum.realestate.LandUtil
import net.torvald.terrarum.tvda.ByteArray64 import net.torvald.terrarum.tvda.ByteArray64
import net.torvald.terrarum.tvda.ByteArray64Writer import net.torvald.terrarum.tvda.ByteArray64Writer
@@ -22,7 +23,8 @@ object WriteWorld {
fun actorAcceptable(actor: Actor): Boolean { fun actorAcceptable(actor: Actor): Boolean {
return actor.referenceID !in ReferencingRanges.ACTORS_WIRES && return actor.referenceID !in ReferencingRanges.ACTORS_WIRES &&
actor.referenceID !in ReferencingRanges.ACTORS_WIRES_HELPER && actor.referenceID !in ReferencingRanges.ACTORS_WIRES_HELPER &&
actor != (CommonResourcePool.get("blockmarking_actor") as BlockMarkerActor) actor != (CommonResourcePool.get("blockmarking_actor") as BlockMarkerActor) &&
actor !is IngamePlayer // IngamePlayers must not be saved with the world
} }
private fun preWrite(ingame: TerrarumIngame, time_t: Long): GameWorld { private fun preWrite(ingame: TerrarumIngame, time_t: Long): GameWorld {

View File

@@ -7,9 +7,12 @@ import net.torvald.terrarum.gameitem.ItemID
import net.torvald.terrarum.gameworld.BlockAddress import net.torvald.terrarum.gameworld.BlockAddress
import net.torvald.terrarum.gameworld.FluidType import net.torvald.terrarum.gameworld.FluidType
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.PhysicalStatus
import net.torvald.terrarum.tvda.ByteArray64Reader import net.torvald.terrarum.tvda.ByteArray64Reader
import net.torvald.terrarum.serialise.Common import net.torvald.terrarum.serialise.Common
import java.io.StringReader import java.io.StringReader
import java.util.*
import kotlin.collections.HashMap
/** /**
* Created by minjaesong on 2021-08-26. * Created by minjaesong on 2021-08-26.
@@ -22,7 +25,7 @@ class HashedFluidType: HashMap<BlockAddress, FluidType>()
class HashedWirings: HashMap<BlockAddress, GameWorld.WiringNode>() class HashedWirings: HashMap<BlockAddress, GameWorld.WiringNode>()
class HashedWiringGraph: HashMap<BlockAddress, WiringGraphMap>() class HashedWiringGraph: HashMap<BlockAddress, WiringGraphMap>()
class MetaModuleCSVPair: HashMap<String, ZipCodedStr>() class MetaModuleCSVPair: HashMap<String, ZipCodedStr>()
class PlayersLastPhysics: HashMap<UUID, PhysicalStatus>()
/** /**
* @param doc plaintext * @param doc plaintext
* *