adding loadorder to savegame

This commit is contained in:
minjaesong
2022-08-31 02:40:46 +09:00
parent 0310c71c74
commit e4caf29791
11 changed files with 95 additions and 321 deletions

View File

@@ -65,4 +65,6 @@ module Terrarum {
exports net.torvald.terrarum.modulebasegame.gameworld;
exports net.torvald.terrarum.modulebasegame.ui;
exports net.torvald.terrarum.modulebasegame.worldgenerator;
exports net.torvald.terrarum.debuggerapp;
}

View File

@@ -147,13 +147,12 @@ class SavegameCracker(
@Command("Lists contents of the disk")
fun ls(args: List<String>) {
letdisk {
it.entries.forEach { i, entry ->
if (i != 0L)
println(
ccNoun + i.toString(10).padStart(11, ' ') + " " +
ccNoun2 + (diskIDtoReadableFilename(entry.entryID) + cc0).padEnd(24) { if (it == 0) ' ' else '.' } +
ccConst + " " + entry.contents.getSizePure() + " bytes"
)
it.entries.toSortedMap().forEach { (i, entry) ->
if (i != 0L) println(
ccNoun + i.toString(10).padStart(11, ' ') + " " +
ccNoun2 + (diskIDtoReadableFilename(entry.entryID) + cc0).padEnd(24) { if (it == 0) ' ' else '.' } +
ccConst + " " + entry.contents.getSizePure() + " bytes"
)
}
val entryCount = it.entries.size - 1
println("${cc0}$entryCount entries, total ${it.usedBytes} bytes")

View File

@@ -236,18 +236,22 @@ class VirtualDisk(
fun diskIDtoReadableFilename(id: EntryID): String = when (id) {
0L -> "root"
-1L -> "savegameinfo.json"
-2L -> "thumbnail.tga.gz"
-16L -> "blockcodex.json.gz"
-17L -> "itemcodex.json.gz"
-18L -> "wirecodex.json.gz"
-19L -> "materialcodex.json.gz"
-20L -> "factioncodex.json.gz"
-1024L -> "apocryphas.json.gz"
in 1..65535 -> "worldinfo-$id.json"
in 1048576..2147483647 -> "actor-$id.json"
-2L -> "thumbnail.tga.gz (world)/spritedef (player)"
-3L -> "spritedef-glow (player)"
-4L -> "loadOrder.txt"
// -16L -> "blockcodex.json.gz"
// -17L -> "itemcodex.json.gz"
// -18L -> "wirecodex.json.gz"
// -19L -> "materialcodex.json.gz"
// -20L -> "factioncodex.json.gz"
// -1024L -> "apocryphas.json.gz"
-1025L -> "bodypart-to-entry.map"
-1026L -> "bodypartglow-to-entry.map"
in 1..65535 -> "bodypart #$id.tga.gz (player)"
in 1048576..2147483647 -> "actor #$id.json"
in 0x0000_0001_0000_0000L..0x0000_FFFF_FFFF_FFFFL ->
"World${id.ushr(32)}-L${id.and(0xFF00_0000).ushr(24)}-C${id.and(0xFFFFFF)}.gz"
else -> "file-$id"
else -> "file #$id"
}
class DiskEntry(

View File

@@ -3,6 +3,7 @@ package net.torvald.terrarum.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.gameitems.ItemID
import net.torvald.terrarum.itemproperties.ItemRemapTable
@@ -172,6 +173,16 @@ class WorldSavingThread(
}
// 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(-4L, 0L, creation_t, time_t, loadOrderContents))
// Echo("Writing file to disk...")
disk.entries[0]!!.modificationDate = time_t

View File

@@ -2,19 +2,15 @@ package net.torvald.terrarum.serialise
import net.torvald.spriteanimation.AssembledSpriteAnimation
import net.torvald.spriteanimation.HasAssembledSprite
import net.torvald.spriteanimation.SpriteAnimation
import net.torvald.terrarum.spriteassembler.ADProperties
import net.torvald.terrarum.ItemCodex
import net.torvald.terrarum.ModMgr
import net.torvald.terrarum.ReferencingRanges.PREFIX_DYNAMICITEM
import net.torvald.terrarum.gameactors.Actor
import net.torvald.terrarum.gameactors.ActorWithBody
import net.torvald.terrarum.gameitems.GameItem
import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.itemproperties.ItemRemapTable
import net.torvald.terrarum.modulebasegame.TerrarumIngame
import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer
import net.torvald.terrarum.printStackTrace
import net.torvald.terrarum.savegame.*
import net.torvald.terrarum.spriteassembler.ADProperties
import java.io.Reader
import java.util.*
@@ -111,7 +107,13 @@ object WritePlayer {
addFile(playerDisk, DiskEntry(-3L, 0L, adlGlowCreationDate, time_t, adlGlowContents))
}
// 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(playerDisk, DiskEntry(-4L, 0L, jsonCreationDate, time_t, loadOrderContents))
}
}

View File

@@ -10,10 +10,11 @@ The main game directory is composed of following directories:
- "${PlayerName}-${UUID}", TVDA {
[-1] player JSON,
[-2] spritedef,
[-3] optional spritedef-glow,
[-1025] sprite-bodypart-name-to-entry-number-map.properties,
[-1026] spriteglow-bodypart-name-to-entry-number-map.properties,
[1+] optional bodyparts tga.gz
[-3] !optional! spritedef-glow,
[-4] loadOrder.txt
[-1025] !optional! sprite-bodypart-name-to-entry-number-map.properties,
[-1026] !optional! spriteglow-bodypart-name-to-entry-number-map.properties,
[1+] !optional! bodyparts tga.gz
}
*if file -1025 is not there, read bodyparts from assets directory
*optionally encrypt the files other than -1
@@ -27,6 +28,7 @@ The main game directory is composed of following directories:
[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
[-4] loadOrder.txt
}
*disk name is world's name encoded in UTF-8
```

View File

@@ -1,27 +0,0 @@
A savegame consists of a Playable Character Information, Savegame Metadata, and other files.
A savegame is a single file in the format of TerranVirtualDisk.
Files contained the TerranVirtualDisk is as follows:
(root)
worldinfo0 -- Savegame Metadata (TESV)
Has fixed Entry ID of 32766
worldinfo1 -- (TODO Copy of blocks.csv OR BlockCodex in JSON) -- will use this from the next load
Has fixed Entry ID of 32765
worldinfo2 -- (TODO Copy of items.csv OR ItemCodex in JSON, static only) -- will use this from the next load
Has fixed Entry ID of 32764
worldinfo3 -- (TODO Copy of materials.csv OR MaterialCodex in JSON) -- will use this from the next load
Has fixed Entry ID of 32763
world[n] -- Layer Data (TEMD); [n] is a serial number of the world (starts at 1)
Has fixed Entry ID of [n]
(any random number in Hex ACTORID_MIN..FFFFFFFF) -- Serialised Entity Information (including Player), Entry ID is random
(PLAYER_REF_ID in Hex -- 91A7E2) -- Player Character Information (Serialised--JSON'd--Entity Information), Entry ID is random
(51621D) -- The Debug Player (Serialised Entity Information), Entry ID is random
load_order.txt -- LoadOrder.csv (NOT zipped)
Has fixed Entry ID of 32767
// TODO select one of following:
(any random number in Hex 32768..ACTORID_MIN - 1) -- Serialised Dynamic Item?
worldinfo4 -- dynamic item codex in JSON, has fixed Entry ID of 32762
Remarks: world history is created at the load time by scanning all the actors' corresponding ActorValue

View File

@@ -1,45 +0,0 @@
Savegame metadata
* Endianness: LITTLE
* Filename: 'worldinfo0'
Ord Hex Description
00 54 T
01 45 E
02 4D S
03 44 V
04 01 Descriptor version number
05 03 Number of hashes
06 Name of the world in UTF-8 (arbitrary length, must not contain NULL)
n-1 00 String terminator
(Ord is now offset from n)
00 Terrain seed (8 bytes)
08 Randomiser s0 (8 bytes)
10 Randomiser s1 (8 bytes)
18 Weather s0 (8 bytes)
20 Weather s1 (8 bytes)
28 ReferenceID of the player (4 bytes, a fixed value of 91A7E2)
2C Current world's time_t (the ingame time, 8 bytes)
34 Creation time in time_t (6 bytes)
3A Last play time in time_t (6 bytes)
40 Total playtime in time_t (4 bytes) // will record 136.1 years of playtime
44 SHA-256 hash of worldinfo1 (32 bytes)
72 SHA-256 hash of worldinfo2 (32 bytes)
A4 SHA-256 hash of worldinfo3 (32 bytes)
D6 Uncompressed size (2 bytes)
D8 Deflated thumbnail image in TGA format
p-2 (it's deflated so that it saves faster, so no Lzma)
p-2 0xFF
p-1 0xFE
Note: if you're going to add more footer beyond this point, DON'T;
instead pack the thumbnail.tga and other footers in TEVD container.

View File

@@ -1,94 +0,0 @@
Terrarum Game Map Format
* Endianness: LITTLE
Ord Hex Description
00 54 T
01 45 E
02 4D M
03 7A z # 'z' because it's compressed
04 03 Version revision number of this format (unreleased numbers also count)
05 03 Number of layers, NOT the number of payload
06 05 Number of payloads
07 01 Compression algorithm, 0 for none, 1 for DEFLATE, 2 for LZMA, otherwise undefined (maybe LZMA2 for the future?)
Value of 01 (DEFLATE) is recommended for its faster compression
08 World generator version. If the generator adds new feature (e.g. new ores, new buildings)
09 this number must be incremented by one.
0A World width
0B World width
0C World width
0D World width
0E World height
0F World height
10 World height
11 World height
12 Default spawn coord in Absolute Tile Number
13 Default spawn coord in Absolute Tile Number
14 Default spawn coord in Absolute Tile Number
15 Default spawn coord in Absolute Tile Number
16 Default spawn coord in Absolute Tile Number
17 Default spawn coord in Absolute Tile Number
# Payload
#
# Each layer and other information are stored as a "payload"
# A payload is consisted as follows:
#
# Literal Description
# "\0pLd" Payload header [00, 70, 4C, 64]
# [4] Identifier. 4 lettres ASCII string
# [6] Uncompressed size of DEFLATEd binary (max size 256 TB)
# [6] Length of the actual payload (max size 256 TB)
# [..] DEFLATEd binary (begins with one of these: 0x789C, 0x78DA, 0x7801)
Payload "TERR" -- world terrain data in Uint16
Uncompressed size will be 2x of (width * height)
Payload "WALL" -- world walls data in Uint16
Uncompressed size will be 2x of (width * height)
Payload "TdMG" -- world terrain damage data, array of: (Int48 tileAddress, Float32 damage)
Uncompressed size will be arbitrary (multiple of tens)
Payload "WdMG" -- world walls damage data, array of: (Int48 tileAddress, Float32 damage)
Uncompressed size will be arbitrary (multiple of tens)
Payload "FlTP" -- world fluid types, array of: (Int48 tileAddress, Signed Int16 type)
Uncompressed size will be arbitrary (multiple of eights)
Payload "FlFL" -- world fluid fills, array of: (Int48 tileAddress, Float32 amount)
Uncompressed size will be arbitrary (multiple of tens)
If the 'amount' < 0.0001f (WorldSimulator.FLUID_MIN_MASS), the entry must be discarded
Payload "WiNt" -- wiring nodes, in JSON format
Payload "TMaP" -- tile number to name map, array of: (Int32, tileNumber, String itemID)
String is null-terminated byte array
TODO need a format that can store arbitrary number of conduits, not just limited to 32
/*Payload "CtYP" -- conduit types, array of: (Int48 tileAddress, Uint32 bitarray)
can hold 32 different wires simultaneously
Payload "CfL0" -- conduit fills, aka size of liquid/gas packet, array of: (Int48 tileAddress, Float32 fill)
CfL0..CfL9, CfLa..CfLf are available to store values for 16 different things.*/
EOF 45 E
EOF 6E n
EOF 64 d
EOF 54 T
EOF 45 E
EOF 4D M
EOF FF Byte order mark
EOF FE Byte order mark

View File

@@ -1,127 +0,0 @@
## Savegame Structure
- The Savegame is a TerranVirtualDisk archive that stores multiple files in the disk's root directory
- Savegame stores metadata, Worlds and Actors in the game
- A player gets one unique Savegame
- A player can have Multiple worlds
- Worlds are identified using integer ranged 1 through 32767 (inclusive)
- Actor ID is unique within the scope of the Savegame
- A World stores list of Actor IDs that resides in the world
### File Structure
Each file on the Savegame has following convention:
|Type|Filename|ID|
|---|---|---|
|Metadata|savegame|-1|
|Blocks Properties|blocks|-16|
|Items Properties|items|-17|
|Wires Properties|wires|-18|
|Materials Properties|materials|-19|
|Factions Properties|factions|-20|
|Other Properties used by modules|modprops|-1024|
|Worlds|world$n ($n is a world index)|$n|
|Actors|actor$n ($n is an Actor ID)|$n|
User formats can have ID of -2147483648..-65536
### Solving Problems
#### How do I determine which world to read in?
Load the player (always has the entry ID of 9545698) and the property "worldCurrentlyPlaying" should
contain an integer that is a world index. Only the actors that are instance of IngamePlayer will have
the property.
### Save File Examples
Following code is an example Savegame JSON files.
#### savegame.json
```
{
savename: "Test World 1",
genver: 0x00030001, /* generator version in integer; always use TerrarumAppConfiguration.VERSION_RAW */
terrseed: "84088805e145b555",
randseed: "19b25856e1c150ca834cffc8b59b23ad",
weatseed: "e5e72beb4e3c6926d3dc9e3e2ef7833b",
playerid: 9545698,
creation_t: <creation time in real-world unix time>,
lastplay_t: <last play time in real-world unix time>,
playtime_t: <total play time in real-world unix time>,
thumb: <Ascii85-encoded gzipped thumbnail image in TGA>,
loadorder: <LoadOrder serialised>,
worlds: [1,2,6,7]
}
```
#### world1.json
File is named as `"world"+world_index+".json"`.
The fields are auto-generated by GDX's JSON serialiser.
```
{
worldName: "New World",
worldIndex: 1,
width: 9000,
height: 2250,
spawnX: 4500,
spawnY: 248,
creationTime: 1629857065,
lastPlayTime: 1629857065,
totalPlayTime: 0,
layerTerrain: {
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped terrain layerdata>,
x: 9000,
y: 2250
},
layerWall: {
h: <SHA-256 hash of 'b'>,
b: <Ascii85-encoded gzipped wall layerdata>,
x: 9000,
y: 2250
},
wallDamages:{},
terrainDamages: {},
fluidTypes: {}
fluidFills: {},
wirings: {},
wiringGraph: {},
gravitation: {y:9.8}
globalLight: {
r:0.8826928,
g:0.8901961,
b:0.9055425,
a:0.93691504
},
averageTemperature: 288,
generatorSeed: 0,
worldTime: 27874,
tileNumberToNameMap: {},
extraFields: {},
genver: 4
comp: 1
}
```
#### actors.json
The fields are auto-generated by GDX's JSON serialiser.
```
[
{ /* actor serialised in JSON *
class: "net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer", /* depends on the actor */
referenceID: 1342111743,
actorValue: { /* actorValue serialised in JSON */ },
hitbox: ...,
...
},
...
]
```

View File

@@ -0,0 +1,47 @@
# example lang file
meta language:fiFI
meta nounclasslabel:nom,par,accnom,accgen,gen,iness,ela,illa,adess,abla,alla,ess,trans,inst,abess,comit,pnom,ppar,paccnom,paccgen,pgen,piness,pela,pilla,padess,pabla,palla,pess,ptrans,pinst,pabess,pcomit
meta nounseries:follow-nounclasslabel # basically tells the parser that 'nounclasslabel' has everything you need
CONTERT_HOUSE_NOUN:talo,taloa,talo,talon,talon,talossa,talosta,taloon,talolla,talolta,talolle,talona,taloksi,,talotta,,talot,taloja,talot,talot,talojen,taloissa,taloista,taloihin,taloilla,taloilta,taloille,taloina,taloiksi,taloin,taloitta,taloineen
CONTEXT_GO_TO_VERB:mennä <1:illa> # with CONTERT_HOUSE_NOUN: "mennä taloon"
meta language:koKR
meta nounclasslabel:use korean # built-in automation for korean
meta nounseries:undefined # the grammar of this language does not take noun's count into account
CONTEXT_HOUSE_NOUN:집
CONTEXT_TOWARDS_VERB:<1>{1:로,으로,로} 가기 # when 'korean' is used for nounclasslabel, the character code of the hangul letter is taken into account. GIVEN_WORD_SET.get(i), where i = ((char - 44032) % 28 == 0) ? 0 : ((char - 44032) % 28 == 8) 2 : 1
meta language:enUS
meta nounseries:singular-plural # tells the parser that first element is singular, and the second is plural
CONTEXT_HOUSE_NOUN:House,Houses
CONTEXT_TOWARDS_VERB:Go to <1>
meta language:frFR
meta nounclasslabel:m,f,pm,pf,vm,vf
meta nounseries:singular-plural # tells the parser that first element is singular, and the second is plural
CONTEXT_COVID_NOUN:f:Covid,Covid
CONTEXT_INTERNATIONALE_NOUN:vf:Internationale,Internationale
CONTEXT_THE_STH:{1:Le ,La ,L,L}<1> # with CONTEXT_COVID_NOUN: "La Covid"; with CONTEXT_INTERNATIONALE_NOUN: "LInternationale"
meta language:la
meta nounclasslabel:m,f,n
meta nounseries:singular-plural # tells the parser that first element is singular, and the second is plural
CONTEXT_CHICKEN_NOUN:m:Gallus,Galli
CONTEXT_VERITAS_NOUN:f:Veritas,Veritates
CONTEXT_X_IS_MY_LIGHT:<1:plural> lux {1:meus,mea,meum} # 'plural' is pre-defined name that comes with 'singular-plural' nounseries
## the preamble
meta nounclasslabel:m,f,n,mp,fp,np # for german: masculine singular, feminine singular, neuter singular, masculine plural, feminine plural, neuter plural
meta nounclasslabel:m,f,n,md,fd,nd,mp,fp,np # for sanskrit: masc./fem./neu. singular, dual and plural
meta nounclasslabel:use korean # a pragma to use built-in automation labeled 'korean'