Files
Mindustry/core/src/mindustry/io/SaveVersion.java

326 lines
12 KiB
Java
Raw Normal View History

2019-12-25 01:39:38 -05:00
package mindustry.io;
2019-05-05 22:09:02 -04:00
2019-12-25 01:39:38 -05:00
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.ctype.ContentType;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.world.*;
2019-05-05 22:09:02 -04:00
import java.io.*;
2019-12-25 01:39:38 -05:00
import static mindustry.Vars.*;
2019-05-05 22:09:02 -04:00
2019-05-06 14:34:21 -04:00
public abstract class SaveVersion extends SaveFileReader{
2019-09-30 20:48:02 -04:00
public int version;
2019-05-05 22:09:02 -04:00
2019-07-03 09:50:33 -04:00
//HACK stores the last read build of the save file, valid after read meta call
2019-07-03 10:11:22 -04:00
protected int lastReadBuild;
2019-07-03 09:50:33 -04:00
2019-05-06 14:34:21 -04:00
public SaveVersion(int version){
this.version = version;
}
public SaveMeta getMeta(DataInput stream) throws IOException{
stream.readInt(); //length of data, doesn't matter here
StringMap map = readStringMap(stream);
2019-08-15 22:44:37 -04:00
return new SaveMeta(map.getInt("version"), map.getLong("saved"), map.getLong("playtime"), map.getInt("build"), map.get("mapname"), map.getInt("wave"), JsonIO.read(Rules.class, map.get("rules", "{}")), map);
2019-05-05 22:09:02 -04:00
}
2019-05-10 00:01:48 -04:00
@Override
2019-05-06 18:32:18 -04:00
public final void write(DataOutputStream stream) throws IOException{
2019-05-07 11:56:17 -04:00
write(stream, new StringMap());
2019-05-05 22:09:02 -04:00
}
2019-05-10 00:01:48 -04:00
@Override
2019-05-10 12:57:45 -04:00
public final void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
2019-05-05 22:09:02 -04:00
region("meta", stream, counter, this::readMeta);
region("content", stream, counter, this::readContentHeader);
2019-06-07 16:06:23 -04:00
try{
region("map", stream, counter, in -> readMap(in, context));
region("entities", stream, counter, this::readEntities);
}finally{
content.setTemporaryMapper(null);
}
2019-05-05 22:09:02 -04:00
}
2019-05-10 00:01:48 -04:00
public final void write(DataOutputStream stream, StringMap extraTags) throws IOException{
2019-05-07 11:56:17 -04:00
region("meta", stream, out -> writeMeta(out, extraTags));
region("content", stream, this::writeContentHeader);
region("map", stream, this::writeMap);
region("entities", stream, this::writeEntities);
}
public void writeMeta(DataOutput stream, StringMap tags) throws IOException{
2019-05-06 14:34:21 -04:00
writeStringMap(stream, StringMap.of(
"saved", Time.millis(),
"playtime", headless ? 0 : control.saves.getTotalPlaytime(),
"build", Version.build,
2019-05-07 17:30:37 -04:00
"mapname", world.getMap() == null ? "unknown" : world.getMap().name(),
2019-05-06 14:34:21 -04:00
"wave", state.wave,
"wavetime", state.wavetime,
"stats", JsonIO.write(state.stats),
"rules", JsonIO.write(state.rules),
2019-10-02 21:23:29 -04:00
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
2019-05-06 17:03:53 -04:00
"width", world.width(),
"height", world.height()
2019-05-07 11:56:17 -04:00
).merge(tags));
2019-05-06 14:34:21 -04:00
}
public void readMeta(DataInput stream) throws IOException{
StringMap map = readStringMap(stream);
state.wave = map.getInt("wave");
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
2019-07-03 09:50:33 -04:00
lastReadBuild = map.getInt("build", -1);
2019-08-26 22:53:11 -04:00
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
2019-05-10 13:42:04 -04:00
world.setMap(worldmap == null ? new Map(StringMap.of(
"name", map.get("mapname", "Unknown"),
"width", 1,
"height", 1
)) : worldmap);
2019-05-06 14:34:21 -04:00
}
2019-05-05 22:09:02 -04:00
public void writeMap(DataOutput stream) throws IOException{
//write world size
stream.writeShort(world.width());
stream.writeShort(world.height());
//floor + overlay
for(int i = 0; i < world.width() * world.height(); i++){
Tile tile = world.rawTile(i % world.width(), i / world.width());
2019-05-05 22:09:02 -04:00
stream.writeShort(tile.floorID());
stream.writeShort(tile.overlayID());
int consecutives = 0;
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
Tile nextTile = world.rawTile(j % world.width(), j / world.width());
2019-05-05 22:09:02 -04:00
if(nextTile.floorID() != tile.floorID() || nextTile.overlayID() != tile.overlayID()){
break;
}
consecutives++;
}
stream.writeByte(consecutives);
i += consecutives;
}
//blocks
for(int i = 0; i < world.width() * world.height(); i++){
Tile tile = world.rawTile(i % world.width(), i / world.width());
2019-05-05 22:09:02 -04:00
stream.writeShort(tile.blockID());
if(tile.entity != null){
2019-05-06 14:34:21 -04:00
writeChunk(stream, true, out -> {
out.writeByte(tile.entity.version());
tile.entity.write(out);
2019-05-06 14:34:21 -04:00
});
2019-05-05 22:09:02 -04:00
}else{
//write consecutive non-entity blocks
int consecutives = 0;
for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){
Tile nextTile = world.rawTile(j % world.width(), j / world.width());
2019-05-05 22:09:02 -04:00
if(nextTile.blockID() != tile.blockID()){
break;
}
consecutives++;
}
stream.writeByte(consecutives);
i += consecutives;
}
}
}
2019-05-10 12:57:45 -04:00
public void readMap(DataInput stream, WorldContext context) throws IOException{
int width = stream.readUnsignedShort();
int height = stream.readUnsignedShort();
2019-05-05 22:09:02 -04:00
2019-05-10 12:57:45 -04:00
boolean generating = context.isGenerating();
2019-05-10 12:57:45 -04:00
if(!generating) context.begin();
2019-06-07 16:06:23 -04:00
try{
2019-05-05 22:09:02 -04:00
2019-06-07 16:06:23 -04:00
context.resize(width, height);
2019-05-05 22:09:02 -04:00
2019-06-07 16:06:23 -04:00
//read floor and create tiles first
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
short floorid = stream.readShort();
short oreid = stream.readShort();
2019-05-05 22:09:02 -04:00
int consecutives = stream.readUnsignedByte();
if(content.block(floorid) == Blocks.air) floorid = Blocks.stone.id;
2019-05-05 22:09:02 -04:00
2019-06-07 16:06:23 -04:00
context.create(x, y, floorid, oreid, (short)0);
2019-05-05 22:09:02 -04:00
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
2019-06-07 16:06:23 -04:00
context.create(newx, newy, floorid, oreid, (short)0);
2019-05-05 22:09:02 -04:00
}
i += consecutives;
}
2019-06-07 16:06:23 -04:00
//read blocks
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
Block block = content.block(stream.readShort());
Tile tile = context.tile(x, y);
if(block == null) block = Blocks.air;
2019-06-07 16:06:23 -04:00
tile.setBlock(block);
if(tile.entity != null){
try{
readChunk(stream, true, in -> {
byte version = in.readByte();
tile.entity.read(in, version);
});
}catch(Exception e){
throw new IOException("Failed to read tile entity of block: " + block, e);
}
}else{
int consecutives = stream.readUnsignedByte();
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
context.tile(newx, newy).setBlock(block);
}
i += consecutives;
}
}
}finally{
if(!generating) context.end();
}
2019-05-05 22:09:02 -04:00
}
public void writeEntities(DataOutput stream) throws IOException{
2019-09-30 20:48:02 -04:00
//write team data with entities.
Array<TeamData> data = state.teams.getActive();
stream.writeInt(data.size);
for(TeamData team : data){
2019-12-25 19:07:04 -05:00
stream.writeInt((int) team.team.id);
2019-09-30 20:48:02 -04:00
stream.writeInt(team.brokenBlocks.size);
for(BrokenBlock block : team.brokenBlocks){
stream.writeShort(block.x);
stream.writeShort(block.y);
stream.writeShort(block.rotation);
stream.writeShort(block.block);
stream.writeInt(block.config);
}
}
2019-05-05 22:09:02 -04:00
//write entity chunk
int groups = 0;
2019-08-30 16:00:09 -04:00
for(EntityGroup<?> group : entities.all()){
2019-05-05 22:09:02 -04:00
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
groups++;
}
}
stream.writeByte(groups);
2019-08-30 16:00:09 -04:00
for(EntityGroup<?> group : entities.all()){
2019-05-05 22:09:02 -04:00
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
stream.writeInt(group.size());
for(Entity entity : group.all()){
2019-05-06 14:34:21 -04:00
SaveTrait save = (SaveTrait)entity;
2019-05-05 22:09:02 -04:00
//each entity is a separate chunk.
writeChunk(stream, true, out -> {
2019-07-03 09:50:33 -04:00
out.writeByte(save.getTypeID().id);
out.writeByte(save.version());
2019-05-06 14:34:21 -04:00
save.writeSave(out);
2019-05-05 22:09:02 -04:00
});
}
}
}
}
public void readEntities(DataInput stream) throws IOException{
2019-09-30 20:48:02 -04:00
int teamc = stream.readInt();
for(int i = 0; i < teamc; i++){
Team team = Team.all[stream.readInt()];
TeamData data = state.teams.get(team);
int blocks = stream.readInt();
for(int j = 0; j < blocks; j++){
2019-10-07 17:17:01 -04:00
data.brokenBlocks.addLast(new BrokenBlock(stream.readShort(), stream.readShort(), stream.readShort(), content.block(stream.readShort()).id, stream.readInt()));
2019-09-30 20:48:02 -04:00
}
}
2019-05-05 22:09:02 -04:00
byte groups = stream.readByte();
for(int i = 0; i < groups; i++){
int amount = stream.readInt();
for(int j = 0; j < amount; j++){
2019-05-06 14:34:21 -04:00
//TODO throw exception on read fail
readChunk(stream, true, in -> {
byte typeid = in.readByte();
byte version = in.readByte();
2019-07-03 09:50:33 -04:00
SaveTrait trait = (SaveTrait)content.<TypeID>getByID(ContentType.typeid, typeid).constructor.get();
trait.readSave(in, version);
2019-05-06 14:34:21 -04:00
});
2019-05-05 22:09:02 -04:00
}
}
}
public void readContentHeader(DataInput stream) throws IOException{
byte mapped = stream.readByte();
2019-10-22 18:55:15 -04:00
MappableContent[][] map = new MappableContent[ContentType.values().length][0];
2019-05-05 22:09:02 -04:00
for(int i = 0; i < mapped; i++){
ContentType type = ContentType.values()[stream.readByte()];
short total = stream.readShort();
2019-10-22 18:55:15 -04:00
map[type.ordinal()] = new MappableContent[total];
2019-05-05 22:09:02 -04:00
for(int j = 0; j < total; j++){
String name = stream.readUTF();
map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name));
}
}
content.setTemporaryMapper(map);
}
public void writeContentHeader(DataOutput stream) throws IOException{
2019-10-22 18:55:15 -04:00
Array<Content>[] map = content.getContentMap();
2019-05-05 22:09:02 -04:00
int mappable = 0;
2019-10-22 18:55:15 -04:00
for(Array<Content> arr : map){
if(arr.size > 0 && arr.first() instanceof MappableContent){
2019-05-05 22:09:02 -04:00
mappable++;
}
}
stream.writeByte(mappable);
2019-10-22 18:55:15 -04:00
for(Array<Content> arr : map){
if(arr.size > 0 && arr.first() instanceof MappableContent){
2019-05-05 22:09:02 -04:00
stream.writeByte(arr.first().getContentType().ordinal());
stream.writeShort(arr.size);
2019-10-22 18:55:15 -04:00
for(Content c : arr){
2019-05-05 22:09:02 -04:00
stream.writeUTF(((MappableContent)c).name);
}
}
}
}
}