mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
adding loadorder to savegame
This commit is contained in:
82
work_files/DataFormats/SAVE_FORMAT.md
Normal file
82
work_files/DataFormats/SAVE_FORMAT.md
Normal file
@@ -0,0 +1,82 @@
|
||||
## Introduction
|
||||
|
||||
On the main game, any player can access any generated worlds, and thus players data and worlds are saved separately.
|
||||
|
||||
The main game directory is composed of following directories:
|
||||
|
||||
```
|
||||
.Terrarum
|
||||
+ Players
|
||||
- "${PlayerName}-${UUID}", TVDA {
|
||||
[-1] player JSON,
|
||||
[-2] spritedef,
|
||||
[-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
|
||||
*disk name is player's name encoded in UTF-8
|
||||
+ Shared
|
||||
- <e.g. Disk GUID>, TEVD { * }
|
||||
- <this directory can have anything>
|
||||
+ Worlds
|
||||
- "${WorldName}-${UUID}", TVDA {
|
||||
[-1] world JSON with Player Data,
|
||||
[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
|
||||
```
|
||||
|
||||
(TEVD stands for Terrarum Virtual Disk spec version 3, TVDA stands for spec version 254; both have MAGIC header of `TEVd`)
|
||||
|
||||
Do not rely on filename to look for a world; players can change the filename
|
||||
|
||||
## Handling The Player Data
|
||||
|
||||
Some of the "player assets" are stored to the world, such assets include:
|
||||
- Physical Status (last position and size as in scale)
|
||||
- Inventory (instance of ActorInventory)
|
||||
- Actorvalues (only on Multiplayer)
|
||||
|
||||
### Loading Procedure
|
||||
|
||||
1. Load the Actor completely first
|
||||
2. Load the World
|
||||
3. Overwrite player data with the World's
|
||||
|
||||
If the World has the Actorvalue, World's value will be used; otherwise use incoming Player's
|
||||
|
||||
Multiplayer world will initialise Actorvalue pool using incoming value -- or they may choose to use
|
||||
their own Actorvalue called "gamerules" to either implement their own "gamemode" or prevent cheating)
|
||||
|
||||
For Singleplayer, only the xy-position is saved to the world for later load.
|
||||
|
||||
Worlds must overwrite new Actor's position to make them spawn in right place.
|
||||
|
||||
### Remarks
|
||||
|
||||
Making `inventory` transient is impossible as it would render Storage Chests unusable.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Player ID must not be strictly 9545698 (0x91A7E2)
|
||||
1. Use classname `net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer` to check
|
||||
2. Each World and Player has to be uniquely identifiable via GUID
|
||||
3. `ActorNowPlaying` must be drawn on top of other actors of same RenderOrder
|
||||
|
||||
## To-dos After the Initial Implementation
|
||||
|
||||
1. Modify Savegame Crackers and Disk Crackers to work with the new scheme
|
||||
2. Create Player Creator Tool for avatar-makers
|
||||
|
||||
## Goals
|
||||
|
||||
1. Allow multiple players share the same world
|
||||
2. Make multiplayer possible
|
||||
3. Make Players distributable (like VRChat avatars)
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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: ...,
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
47
work_files/DataFormats/terrarum_advanced_lang_file.txt
Normal file
47
work_files/DataFormats/terrarum_advanced_lang_file.txt
Normal 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: "L’Internationale"
|
||||
|
||||
|
||||
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'
|
||||
|
||||
Reference in New Issue
Block a user