it is done
This commit is contained in:
151
core/src/mindustry/io/JsonIO.java
Normal file
151
core/src/mindustry/io/JsonIO.java
Normal file
@@ -0,0 +1,151 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.Json.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.ctype.ContentType;
|
||||
import mindustry.game.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class JsonIO{
|
||||
private static CustomJson jsonBase = new CustomJson();
|
||||
private static Json json = new Json(){
|
||||
{ apply(this); }
|
||||
|
||||
@Override
|
||||
public void writeValue(Object value, Class knownType, Class elementType){
|
||||
if(value instanceof mindustry.ctype.MappableContent){
|
||||
try{
|
||||
getWriter().value(((MappableContent)value).name);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}else{
|
||||
super.writeValue(value, knownType, elementType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertToString(Object object){
|
||||
if(object instanceof MappableContent){
|
||||
return ((MappableContent)object).name;
|
||||
}
|
||||
return super.convertToString(object);
|
||||
}
|
||||
};
|
||||
|
||||
public static Json json(){
|
||||
return json;
|
||||
}
|
||||
|
||||
public static String write(Object object){
|
||||
return json.toJson(object, object.getClass());
|
||||
}
|
||||
|
||||
public static <T> T copy(T object){
|
||||
return read((Class<T>)object.getClass(), write(object));
|
||||
}
|
||||
|
||||
public static <T> T read(Class<T> type, String string){
|
||||
return json.fromJson(type, string);
|
||||
}
|
||||
|
||||
public static <T> T read(Class<T> type, T base, String string){
|
||||
return jsonBase.fromBaseJson(type, base, string);
|
||||
}
|
||||
|
||||
public static String print(String in){
|
||||
return json.prettyPrint(in);
|
||||
}
|
||||
|
||||
private static void apply(Json json){
|
||||
json.setIgnoreUnknownFields(true);
|
||||
json.setElementType(Rules.class, "spawns", SpawnGroup.class);
|
||||
json.setElementType(Rules.class, "loadout", ItemStack.class);
|
||||
|
||||
json.setSerializer(Zone.class, new Serializer<Zone>(){
|
||||
@Override
|
||||
public void write(Json json, Zone object, Class knownType){
|
||||
json.writeValue(object.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Zone read(Json json, JsonValue jsonData, Class type){
|
||||
return Vars.content.getByName(ContentType.zone, jsonData.asString());
|
||||
}
|
||||
});
|
||||
|
||||
json.setSerializer(Item.class, new Serializer<Item>(){
|
||||
@Override
|
||||
public void write(Json json, Item object, Class knownType){
|
||||
json.writeValue(object.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item read(Json json, JsonValue jsonData, Class type){
|
||||
if(jsonData.asString() == null) return Items.copper;
|
||||
Item i = Vars.content.getByName(ContentType.item, jsonData.asString());
|
||||
return i == null ? Items.copper : i;
|
||||
}
|
||||
});
|
||||
|
||||
json.setSerializer(Block.class, new Serializer<Block>(){
|
||||
@Override
|
||||
public void write(Json json, Block object, Class knownType){
|
||||
json.writeValue(object.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block read(Json json, JsonValue jsonData, Class type){
|
||||
return Vars.content.getByName(ContentType.block, jsonData.asString());
|
||||
}
|
||||
});
|
||||
|
||||
json.setSerializer(ItemStack.class, new Serializer<ItemStack>(){
|
||||
@Override
|
||||
public void write(Json json, ItemStack object, Class knownType){
|
||||
json.writeObjectStart();
|
||||
json.writeValue("item", object.item);
|
||||
json.writeValue("amount", object.amount);
|
||||
json.writeObjectEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack read(Json json, JsonValue jsonData, Class type){
|
||||
return new ItemStack(json.getSerializer(Item.class).read(json, jsonData.get("item"), Item.class), jsonData.getInt("amount"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class CustomJson extends Json{
|
||||
private Object baseObject;
|
||||
|
||||
{
|
||||
apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T fromJson(Class<T> type, String json){
|
||||
return fromBaseJson(type, null, json);
|
||||
}
|
||||
|
||||
public <T> T fromBaseJson(Class<T> type, T base, String json){
|
||||
this.baseObject = base;
|
||||
return readValue(type, null, new JsonReader().parse(json));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object newInstance(Class type){
|
||||
if(baseObject == null || baseObject.getClass() != type){
|
||||
return super.newInstance(type);
|
||||
}
|
||||
return baseObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
core/src/mindustry/io/LegacyMapIO.java
Normal file
214
core/src/mindustry/io/LegacyMapIO.java
Normal file
@@ -0,0 +1,214 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.graphics.*;
|
||||
import arc.util.*;
|
||||
import arc.util.serialization.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.ContentType;
|
||||
import mindustry.game.*;
|
||||
import mindustry.io.MapIO.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.LegacyColorMapper.*;
|
||||
import mindustry.world.blocks.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Map IO for the "old" .mmap format.
|
||||
* Differentiate between legacy maps and new maps by checking the extension (or the header).*/
|
||||
public class LegacyMapIO{
|
||||
private static final ObjectMap<String, String> fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad");
|
||||
private static final Json json = new Json();
|
||||
|
||||
/* Convert a map from the old format to the new format. */
|
||||
public static void convertMap(Fi in, Fi out) throws IOException{
|
||||
Map map = readMap(in, true);
|
||||
|
||||
String waves = map.tags.get("waves", "[]");
|
||||
Array<SpawnGroup> groups = new Array<>(json.fromJson(SpawnGroup[].class, waves));
|
||||
|
||||
Tile[][] tiles = world.createTiles(map.width, map.height);
|
||||
for(int x = 0; x < map.width; x++){
|
||||
for(int y = 0; y < map.height; y++){
|
||||
tiles[x][y] = new CachedTile();
|
||||
tiles[x][y].x = (short)x;
|
||||
tiles[x][y].y = (short)y;
|
||||
}
|
||||
}
|
||||
state.rules.spawns = groups;
|
||||
readTiles(map, tiles);
|
||||
MapIO.writeMap(out, map);
|
||||
}
|
||||
|
||||
public static Map readMap(Fi file, boolean custom) throws IOException{
|
||||
try(DataInputStream stream = new DataInputStream(file.read(1024))){
|
||||
StringMap tags = new StringMap();
|
||||
|
||||
//meta is uncompressed
|
||||
int version = stream.readInt();
|
||||
if(version != 1){
|
||||
throw new IOException("Outdated legacy map format");
|
||||
}
|
||||
int build = stream.readInt();
|
||||
short width = stream.readShort(), height = stream.readShort();
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
String name = stream.readUTF();
|
||||
String value = stream.readUTF();
|
||||
tags.put(name, value);
|
||||
}
|
||||
|
||||
return new Map(file, width, height, tags, custom, version, build);
|
||||
}
|
||||
}
|
||||
|
||||
public static void readTiles(Map map, Tile[][] tiles) throws IOException{
|
||||
readTiles(map, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
public static void readTiles(Map map, TileProvider tiles) throws IOException{
|
||||
readTiles(map.file, map.width, map.height, tiles);
|
||||
}
|
||||
|
||||
private static void readTiles(Fi file, int width, int height, Tile[][] tiles) throws IOException{
|
||||
readTiles(file, width, height, (x, y) -> tiles[x][y]);
|
||||
}
|
||||
|
||||
private static void readTiles(Fi file, int width, int height, TileProvider tiles) throws IOException{
|
||||
try(BufferedInputStream input = file.read(bufferSize)){
|
||||
|
||||
//read map
|
||||
{
|
||||
DataInputStream stream = new DataInputStream(input);
|
||||
|
||||
stream.readInt(); //version
|
||||
stream.readInt(); //build
|
||||
stream.readInt(); //width + height
|
||||
byte tagAmount = stream.readByte();
|
||||
|
||||
for(int i = 0; i < tagAmount; i++){
|
||||
stream.readUTF(); //key
|
||||
stream.readUTF(); //val
|
||||
}
|
||||
}
|
||||
|
||||
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
|
||||
|
||||
try{
|
||||
byte mapped = stream.readByte();
|
||||
IntMap<Block> idmap = new IntMap<>();
|
||||
IntMap<String> namemap = new IntMap<>();
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
byte type = stream.readByte();
|
||||
short total = stream.readShort();
|
||||
|
||||
for(int j = 0; j < total; j++){
|
||||
String name = stream.readUTF();
|
||||
if(type == 1){
|
||||
Block res = content.getByName(ContentType.block, fallback.get(name, name));
|
||||
idmap.put(j, res == null ? Blocks.air : res);
|
||||
namemap.put(j, fallback.get(name, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//read floor and create tiles first
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
int floorid = stream.readUnsignedByte();
|
||||
int oreid = stream.readUnsignedByte();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
tile.setFloor((Floor)idmap.get(floorid));
|
||||
tile.setOverlay(idmap.get(oreid));
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
Tile newTile = tiles.get(newx, newy);
|
||||
newTile.setFloor((Floor)idmap.get(floorid));
|
||||
newTile.setOverlay(idmap.get(oreid));
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//read blocks
|
||||
for(int i = 0; i < width * height; i++){
|
||||
int x = i % width, y = i / width;
|
||||
int id = stream.readUnsignedByte();
|
||||
Block block = idmap.get(id);
|
||||
if(block == null) block = Blocks.air;
|
||||
|
||||
Tile tile = tiles.get(x, y);
|
||||
//the spawn block is saved in the block tile layer in older maps, shift it to the overlay
|
||||
if(block != Blocks.spawn){
|
||||
tile.setBlock(block);
|
||||
}else{
|
||||
tile.setOverlay(block);
|
||||
}
|
||||
|
||||
if(namemap.get(id, "").equals("part")){
|
||||
stream.readByte(); //link
|
||||
}else if(tile.entity != null){
|
||||
byte tr = stream.readByte();
|
||||
stream.readShort(); //read health (which is actually irrelevant)
|
||||
|
||||
byte team = Pack.leftByte(tr);
|
||||
byte rotation = Pack.rightByte(tr);
|
||||
|
||||
tile.setTeam(Team.all[team]);
|
||||
tile.entity.health = tile.block().health;
|
||||
tile.rotation(rotation);
|
||||
|
||||
if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){
|
||||
stream.readByte(); //these blocks have an extra config byte, read it
|
||||
}
|
||||
}else{ //no entity/part, read consecutives
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
tiles.get(newx, newy).setBlock(block);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Reads a pixmap in the 3.5 pixmap format. */
|
||||
public static void readPixmap(Pixmap pixmap, Tile[][] tiles){
|
||||
for(int x = 0; x < pixmap.getWidth(); x++){
|
||||
for(int y = 0; y < pixmap.getHeight(); y++){
|
||||
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
|
||||
LegacyBlock block = LegacyColorMapper.get(color);
|
||||
Tile tile = tiles[x][y];
|
||||
|
||||
tile.setFloor(block.floor);
|
||||
tile.setBlock(block.wall);
|
||||
if(block.ore != null) tile.setOverlay(block.ore);
|
||||
|
||||
//place core
|
||||
if(color == Color.rgba8888(Color.green)){
|
||||
//actual core parts
|
||||
tile.setBlock(Blocks.coreShard);
|
||||
tile.setTeam(Team.sharded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
core/src/mindustry/io/MapIO.java
Normal file
156
core/src/mindustry/io/MapIO.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Pixmap.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Reads and writes map files. */
|
||||
//TODO does this class even need to exist??? move to Maps?
|
||||
public class MapIO{
|
||||
private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
|
||||
|
||||
public static boolean isImage(Fi file){
|
||||
try(InputStream stream = file.read(32)){
|
||||
for(int i1 : pngHeader){
|
||||
if(stream.read() != i1){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}catch(IOException e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map createMap(Fi file, boolean custom) throws IOException{
|
||||
try(InputStream is = new InflaterInputStream(file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
SaveIO.readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
StringMap tags = new StringMap();
|
||||
ver.region("meta", stream, counter, in -> tags.putAll(ver.readStringMap(in)));
|
||||
return new Map(file, tags.getInt("width"), tags.getInt("height"), tags, custom, version, Version.build);
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeMap(Fi file, Map map) throws IOException{
|
||||
try{
|
||||
SaveIO.write(file, map.tags);
|
||||
}catch(Exception e){
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadMap(Map map){
|
||||
SaveIO.load(map.file);
|
||||
}
|
||||
|
||||
public static void loadMap(Map map, WorldContext cons){
|
||||
SaveIO.load(map.file, cons);
|
||||
}
|
||||
|
||||
public static Pixmap generatePreview(Map map) throws IOException{
|
||||
map.spawns = 0;
|
||||
map.teams.clear();
|
||||
|
||||
try(InputStream is = new InflaterInputStream(map.file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
SaveIO.readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = SaveIO.getSaveWriter(version);
|
||||
ver.region("meta", stream, counter, ver::readStringMap);
|
||||
|
||||
Pixmap floors = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
Pixmap walls = new Pixmap(map.width, map.height, Format.RGBA8888);
|
||||
int black = Color.rgba8888(Color.black);
|
||||
int shade = Color.rgba8888(0f, 0f, 0f, 0.5f);
|
||||
CachedTile tile = new CachedTile(){
|
||||
@Override
|
||||
public void setBlock(Block type){
|
||||
super.setBlock(type);
|
||||
int c = colorFor(Blocks.air, block(), Blocks.air, getTeam());
|
||||
if(c != black){
|
||||
walls.draw(x, floors.getHeight() - 1 - y, c);
|
||||
floors.draw(x, floors.getHeight() - 1 - y + 1, shade);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTeam(Team team){
|
||||
super.setTeam(team);
|
||||
if(block instanceof CoreBlock){
|
||||
map.teams.add(team.ordinal());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ver.region("content", stream, counter, ver::readContentHeader);
|
||||
ver.region("preview_map", stream, counter, in -> ver.readMap(in, new WorldContext(){
|
||||
@Override public void resize(int width, int height){}
|
||||
@Override public boolean isGenerating(){return false;}
|
||||
@Override public void begin(){}
|
||||
@Override public void end(){}
|
||||
|
||||
@Override
|
||||
public Tile tile(int x, int y){
|
||||
tile.x = (short)x;
|
||||
tile.y = (short)y;
|
||||
return tile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
|
||||
if(overlayID != 0){
|
||||
floors.draw(x, floors.getHeight() - 1 - y, colorFor(Blocks.air, Blocks.air, content.block(overlayID), Team.derelict));
|
||||
}else{
|
||||
floors.draw(x, floors.getHeight() - 1 - y, colorFor(content.block(floorID), Blocks.air, Blocks.air, Team.derelict));
|
||||
}
|
||||
if(content.block(overlayID) == Blocks.spawn){
|
||||
map.spawns ++;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
}));
|
||||
|
||||
floors.drawPixmap(walls, 0, 0);
|
||||
walls.dispose();
|
||||
return floors;
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static Pixmap generatePreview(Tile[][] tiles){
|
||||
Pixmap pixmap = new Pixmap(tiles.length, tiles[0].length, Format.RGBA8888);
|
||||
for(int x = 0; x < pixmap.getWidth(); x++){
|
||||
for(int y = 0; y < pixmap.getHeight(); y++){
|
||||
Tile tile = tiles[x][y];
|
||||
pixmap.draw(x, pixmap.getHeight() - 1 - y, colorFor(tile.floor(), tile.block(), tile.overlay(), tile.getTeam()));
|
||||
}
|
||||
}
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
public static int colorFor(Block floor, Block wall, Block ore, Team team){
|
||||
if(wall.synthetic()){
|
||||
return team.intColor;
|
||||
}
|
||||
return Color.rgba8888(wall.solid ? wall.color : ore == Blocks.air ? floor.color : ore.color);
|
||||
}
|
||||
|
||||
interface TileProvider{
|
||||
Tile get(int x, int y);
|
||||
}
|
||||
}
|
||||
113
core/src/mindustry/io/SaveFileReader.java
Normal file
113
core/src/mindustry/io/SaveFileReader.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.struct.ObjectMap;
|
||||
import arc.struct.ObjectMap.Entry;
|
||||
import arc.struct.StringMap;
|
||||
import arc.util.io.CounterInputStream;
|
||||
import arc.util.io.ReusableByteOutStream;
|
||||
import mindustry.world.WorldContext;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public abstract class SaveFileReader{
|
||||
protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput);
|
||||
protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream();
|
||||
protected final DataOutputStream dataBytesSmall = new DataOutputStream(byteOutputSmall);
|
||||
protected final ObjectMap<String, String> fallback = ObjectMap.of();
|
||||
|
||||
protected void region(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{
|
||||
counter.resetCount();
|
||||
int length;
|
||||
try{
|
||||
length = readChunk(stream, cons);
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error reading region \"" + name + "\".", e);
|
||||
}
|
||||
|
||||
if(length != counter.count() - 4){
|
||||
throw new IOException("Error reading region \"" + name + "\": read length mismatch. Expected: " + length + "; Actual: " + (counter.count() - 4));
|
||||
}
|
||||
}
|
||||
|
||||
protected void region(String name, DataOutput stream, IORunner<DataOutput> cons) throws IOException{
|
||||
try{
|
||||
writeChunk(stream, cons);
|
||||
}catch(Throwable e){
|
||||
throw new IOException("Error writing region \"" + name + "\".", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeChunk(DataOutput output, IORunner<DataOutput> runner) throws IOException{
|
||||
writeChunk(output, false, runner);
|
||||
}
|
||||
|
||||
/** Write a chunk of input to the stream. An integer of some length is written first, followed by the data. */
|
||||
public void writeChunk(DataOutput output, boolean isByte, IORunner<DataOutput> runner) throws IOException{
|
||||
ReusableByteOutStream dout = isByte ? byteOutputSmall : byteOutput;
|
||||
//reset output position
|
||||
dout.reset();
|
||||
//write the needed info
|
||||
runner.accept(isByte ? dataBytesSmall : dataBytes);
|
||||
int length = dout.size();
|
||||
//write length (either int or byte) followed by the output bytes
|
||||
if(!isByte){
|
||||
output.writeInt(length);
|
||||
}else{
|
||||
if(length > Short.MAX_VALUE){
|
||||
throw new IOException("Byte write length exceeded: " + length + " > " + Short.MAX_VALUE);
|
||||
}
|
||||
output.writeShort(length);
|
||||
}
|
||||
output.write(dout.getBytes(), 0, length);
|
||||
}
|
||||
|
||||
public int readChunk(DataInput input, IORunner<DataInput> runner) throws IOException{
|
||||
return readChunk(input, false, runner);
|
||||
}
|
||||
|
||||
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
|
||||
public int readChunk(DataInput input, boolean isByte, IORunner<DataInput> runner) throws IOException{
|
||||
int length = isByte ? input.readUnsignedShort() : input.readInt();
|
||||
runner.accept(input);
|
||||
return length;
|
||||
}
|
||||
|
||||
public void skipRegion(DataInput input) throws IOException{
|
||||
skipRegion(input, false);
|
||||
}
|
||||
|
||||
/** Skip a region completely. */
|
||||
public void skipRegion(DataInput input, boolean isByte) throws IOException{
|
||||
int length = readChunk(input, isByte, t -> {});
|
||||
int skipped = input.skipBytes(length);
|
||||
if(length != skipped){
|
||||
throw new IOException("Could not skip bytes. Expected length: " + length + "; Actual length: " + skipped);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeStringMap(DataOutput stream, ObjectMap<String, String> map) throws IOException{
|
||||
stream.writeShort(map.size);
|
||||
for(Entry<String, String> entry : map.entries()){
|
||||
stream.writeUTF(entry.key);
|
||||
stream.writeUTF(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
public StringMap readStringMap(DataInput stream) throws IOException{
|
||||
StringMap map = new StringMap();
|
||||
short size = stream.readShort();
|
||||
for(int i = 0; i < size; i++){
|
||||
map.put(stream.readUTF(), stream.readUTF());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public abstract void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException;
|
||||
|
||||
public abstract void write(DataOutputStream stream) throws IOException;
|
||||
|
||||
protected interface IORunner<T>{
|
||||
void accept(T stream) throws IOException;
|
||||
}
|
||||
}
|
||||
178
core/src/mindustry/io/SaveIO.java
Normal file
178
core/src/mindustry/io/SaveIO.java
Normal file
@@ -0,0 +1,178 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.files.Fi;
|
||||
import arc.util.io.CounterInputStream;
|
||||
import arc.util.io.FastDeflaterOutputStream;
|
||||
import mindustry.Vars;
|
||||
import mindustry.io.versions.*;
|
||||
import mindustry.world.WorldContext;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class SaveIO{
|
||||
/** Format header. This is the string 'MSAV' in ASCII. */
|
||||
public static final byte[] header = {77, 83, 65, 86};
|
||||
public static final IntMap<SaveVersion> versions = new IntMap<>();
|
||||
public static final Array<SaveVersion> versionArray = Array.with(new Save1(), new Save2(), new Save3());
|
||||
|
||||
static{
|
||||
for(SaveVersion version : versionArray){
|
||||
versions.put(version.version, version);
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveVersion getSaveWriter(){
|
||||
return versionArray.peek();
|
||||
}
|
||||
|
||||
public static SaveVersion getSaveWriter(int version){
|
||||
return versions.get(version);
|
||||
}
|
||||
|
||||
public static void save(Fi file){
|
||||
boolean exists = file.exists();
|
||||
if(exists) file.moveTo(backupFileFor(file));
|
||||
try{
|
||||
write(file);
|
||||
}catch(Exception e){
|
||||
if(exists) backupFileFor(file).moveTo(file);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static DataInputStream getStream(Fi file){
|
||||
return new DataInputStream(new InflaterInputStream(file.read(bufferSize)));
|
||||
}
|
||||
|
||||
public static DataInputStream getBackupStream(Fi file){
|
||||
return new DataInputStream(new InflaterInputStream(backupFileFor(file).read(bufferSize)));
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(Fi file){
|
||||
try{
|
||||
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize))));
|
||||
}catch(Exception e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSaveValid(DataInputStream stream){
|
||||
try{
|
||||
getMeta(stream);
|
||||
return true;
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveMeta getMeta(Fi file){
|
||||
try{
|
||||
return getMeta(getStream(file));
|
||||
}catch(Exception e){
|
||||
return getMeta(getBackupStream(file));
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveMeta getMeta(DataInputStream stream){
|
||||
|
||||
try{
|
||||
readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveMeta meta = versions.get(version).getMeta(stream);
|
||||
stream.close();
|
||||
return meta;
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Fi fileFor(int slot){
|
||||
return saveDirectory.child(slot + "." + Vars.saveExtension);
|
||||
}
|
||||
|
||||
public static Fi backupFileFor(Fi file){
|
||||
return file.sibling(file.name() + "-backup." + file.extension());
|
||||
}
|
||||
|
||||
public static void write(Fi file, StringMap tags){
|
||||
write(new FastDeflaterOutputStream(file.write(false, bufferSize)), tags);
|
||||
}
|
||||
|
||||
public static void write(Fi file){
|
||||
write(file, null);
|
||||
}
|
||||
|
||||
public static void write(OutputStream os, StringMap tags){
|
||||
try(DataOutputStream stream = new DataOutputStream(os)){
|
||||
stream.write(header);
|
||||
stream.writeInt(getVersion().version);
|
||||
if(tags == null){
|
||||
getVersion().write(stream);
|
||||
}else{
|
||||
getVersion().write(stream, tags);
|
||||
}
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void load(Fi file) throws SaveException{
|
||||
load(file, world.context);
|
||||
}
|
||||
|
||||
public static void load(Fi file, WorldContext context) throws SaveException{
|
||||
try{
|
||||
//try and load; if any exception at all occurs
|
||||
load(new InflaterInputStream(file.read(bufferSize)), context);
|
||||
}catch(SaveException e){
|
||||
e.printStackTrace();
|
||||
Fi backup = file.sibling(file.name() + "-backup." + file.extension());
|
||||
if(backup.exists()){
|
||||
load(new InflaterInputStream(backup.read(bufferSize)), context);
|
||||
}else{
|
||||
throw new SaveException(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads from a deflated (!) input stream.*/
|
||||
public static void load(InputStream is, WorldContext context) throws SaveException{
|
||||
try(CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
|
||||
logic.reset();
|
||||
readHeader(stream);
|
||||
int version = stream.readInt();
|
||||
SaveVersion ver = versions.get(version);
|
||||
|
||||
ver.read(stream, counter, context);
|
||||
}catch(Exception e){
|
||||
throw new SaveException(e);
|
||||
}finally{
|
||||
world.setGenerating(false);
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static SaveVersion getVersion(){
|
||||
return versionArray.peek();
|
||||
}
|
||||
|
||||
public static void readHeader(DataInput input) throws IOException{
|
||||
byte[] bytes = new byte[header.length];
|
||||
input.readFully(bytes);
|
||||
if(!Arrays.equals(bytes, header)){
|
||||
throw new IOException("Incorrect header! Expecting: " + Arrays.toString(header) + "; Actual: " + Arrays.toString(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveException extends RuntimeException{
|
||||
public SaveException(Throwable throwable){
|
||||
super(throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
core/src/mindustry/io/SaveMeta.java
Normal file
31
core/src/mindustry/io/SaveMeta.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.maps.*;
|
||||
|
||||
import static mindustry.Vars.maps;
|
||||
|
||||
public class SaveMeta{
|
||||
public int version;
|
||||
public int build;
|
||||
public long timestamp;
|
||||
public long timePlayed;
|
||||
public Map map;
|
||||
public int wave;
|
||||
public Rules rules;
|
||||
public StringMap tags;
|
||||
public String[] mods;
|
||||
|
||||
public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, StringMap tags){
|
||||
this.version = version;
|
||||
this.build = build;
|
||||
this.timestamp = timestamp;
|
||||
this.timePlayed = timePlayed;
|
||||
this.map = maps.all().find(m -> m.name().equals(map));
|
||||
this.wave = wave;
|
||||
this.rules = rules;
|
||||
this.tags = tags;
|
||||
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
|
||||
}
|
||||
}
|
||||
23
core/src/mindustry/io/SavePreviewLoader.java
Normal file
23
core/src/mindustry/io/SavePreviewLoader.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package mindustry.io;
|
||||
|
||||
import arc.assets.*;
|
||||
import arc.assets.loaders.*;
|
||||
import arc.assets.loaders.resolvers.*;
|
||||
import arc.files.*;
|
||||
|
||||
public class SavePreviewLoader extends TextureLoader{
|
||||
|
||||
public SavePreviewLoader(){
|
||||
super(new AbsoluteFileHandleResolver());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAsync(AssetManager manager, String fileName, Fi file, TextureParameter parameter){
|
||||
try{
|
||||
super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter);
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
file.sibling(file.nameWithoutExtension()).delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
325
core/src/mindustry/io/SaveVersion.java
Normal file
325
core/src/mindustry/io/SaveVersion.java
Normal file
@@ -0,0 +1,325 @@
|
||||
package mindustry.io;
|
||||
|
||||
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.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public abstract class SaveVersion extends SaveFileReader{
|
||||
public int version;
|
||||
|
||||
//HACK stores the last read build of the save file, valid after read meta call
|
||||
protected int lastReadBuild;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(DataOutputStream stream) throws IOException{
|
||||
write(stream, new StringMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void read(DataInputStream stream, CounterInputStream counter, WorldContext context) throws IOException{
|
||||
region("meta", stream, counter, this::readMeta);
|
||||
region("content", stream, counter, this::readContentHeader);
|
||||
|
||||
try{
|
||||
region("map", stream, counter, in -> readMap(in, context));
|
||||
region("entities", stream, counter, this::readEntities);
|
||||
}finally{
|
||||
content.setTemporaryMapper(null);
|
||||
}
|
||||
}
|
||||
|
||||
public final void write(DataOutputStream stream, StringMap extraTags) throws IOException{
|
||||
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{
|
||||
writeStringMap(stream, StringMap.of(
|
||||
"saved", Time.millis(),
|
||||
"playtime", headless ? 0 : control.saves.getTotalPlaytime(),
|
||||
"build", Version.build,
|
||||
"mapname", world.getMap() == null ? "unknown" : world.getMap().name(),
|
||||
"wave", state.wave,
|
||||
"wavetime", state.wavetime,
|
||||
"stats", JsonIO.write(state.stats),
|
||||
"rules", JsonIO.write(state.rules),
|
||||
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
||||
"width", world.width(),
|
||||
"height", world.height()
|
||||
).merge(tags));
|
||||
}
|
||||
|
||||
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();
|
||||
lastReadBuild = map.getInt("build", -1);
|
||||
|
||||
Map worldmap = maps.byName(map.get("mapname", "\\\\\\"));
|
||||
world.setMap(worldmap == null ? new Map(StringMap.of(
|
||||
"name", map.get("mapname", "Unknown"),
|
||||
"width", 1,
|
||||
"height", 1
|
||||
)) : worldmap);
|
||||
}
|
||||
|
||||
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());
|
||||
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());
|
||||
|
||||
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());
|
||||
stream.writeShort(tile.blockID());
|
||||
|
||||
if(tile.entity != null){
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(tile.entity.version());
|
||||
tile.entity.write(out);
|
||||
});
|
||||
}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());
|
||||
|
||||
if(nextTile.blockID() != tile.blockID()){
|
||||
break;
|
||||
}
|
||||
|
||||
consecutives++;
|
||||
}
|
||||
|
||||
stream.writeByte(consecutives);
|
||||
i += consecutives;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readMap(DataInput stream, WorldContext context) throws IOException{
|
||||
int width = stream.readUnsignedShort();
|
||||
int height = stream.readUnsignedShort();
|
||||
|
||||
boolean generating = context.isGenerating();
|
||||
|
||||
if(!generating) context.begin();
|
||||
try{
|
||||
|
||||
context.resize(width, height);
|
||||
|
||||
//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();
|
||||
int consecutives = stream.readUnsignedByte();
|
||||
if(content.block(floorid) == Blocks.air) floorid = Blocks.stone.id;
|
||||
|
||||
context.create(x, y, floorid, oreid, (short)0);
|
||||
|
||||
for(int j = i + 1; j < i + 1 + consecutives; j++){
|
||||
int newx = j % width, newy = j / width;
|
||||
context.create(newx, newy, floorid, oreid, (short)0);
|
||||
}
|
||||
|
||||
i += consecutives;
|
||||
}
|
||||
|
||||
//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;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeEntities(DataOutput stream) throws IOException{
|
||||
//write team data with entities.
|
||||
Array<TeamData> data = state.teams.getActive();
|
||||
stream.writeInt(data.size);
|
||||
for(TeamData team : data){
|
||||
stream.writeInt(team.team.ordinal());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//write entity chunk
|
||||
int groups = 0;
|
||||
|
||||
for(EntityGroup<?> group : entities.all()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
groups++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(groups);
|
||||
|
||||
for(EntityGroup<?> group : entities.all()){
|
||||
if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){
|
||||
stream.writeInt(group.size());
|
||||
for(Entity entity : group.all()){
|
||||
SaveTrait save = (SaveTrait)entity;
|
||||
//each entity is a separate chunk.
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(save.getTypeID().id);
|
||||
out.writeByte(save.version());
|
||||
save.writeSave(out);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readEntities(DataInput stream) throws IOException{
|
||||
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++){
|
||||
data.brokenBlocks.addLast(new BrokenBlock(stream.readShort(), stream.readShort(), stream.readShort(), content.block(stream.readShort()).id, stream.readInt()));
|
||||
}
|
||||
}
|
||||
|
||||
byte groups = stream.readByte();
|
||||
|
||||
for(int i = 0; i < groups; i++){
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
//TODO throw exception on read fail
|
||||
readChunk(stream, true, in -> {
|
||||
byte typeid = in.readByte();
|
||||
byte version = in.readByte();
|
||||
SaveTrait trait = (SaveTrait)content.<TypeID>getByID(ContentType.typeid, typeid).constructor.get();
|
||||
trait.readSave(in, version);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readContentHeader(DataInput stream) throws IOException{
|
||||
byte mapped = stream.readByte();
|
||||
|
||||
MappableContent[][] map = new MappableContent[ContentType.values().length][0];
|
||||
|
||||
for(int i = 0; i < mapped; i++){
|
||||
ContentType type = ContentType.values()[stream.readByte()];
|
||||
short total = stream.readShort();
|
||||
map[type.ordinal()] = new MappableContent[total];
|
||||
|
||||
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{
|
||||
Array<Content>[] map = content.getContentMap();
|
||||
|
||||
int mappable = 0;
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
mappable++;
|
||||
}
|
||||
}
|
||||
|
||||
stream.writeByte(mappable);
|
||||
for(Array<Content> arr : map){
|
||||
if(arr.size > 0 && arr.first() instanceof MappableContent){
|
||||
stream.writeByte(arr.first().getContentType().ordinal());
|
||||
stream.writeShort(arr.size);
|
||||
for(Content c : arr){
|
||||
stream.writeUTF(((MappableContent)c).name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
367
core/src/mindustry/io/TypeIO.java
Normal file
367
core/src/mindustry/io/TypeIO.java
Normal file
@@ -0,0 +1,367 @@
|
||||
package mindustry.io;
|
||||
|
||||
import mindustry.annotations.Annotations.ReadClass;
|
||||
import mindustry.annotations.Annotations.WriteClass;
|
||||
import arc.graphics.Color;
|
||||
import mindustry.ctype.ContentType;
|
||||
import mindustry.entities.Effects;
|
||||
import mindustry.entities.Effects.Effect;
|
||||
import mindustry.entities.type.Bullet;
|
||||
import mindustry.entities.bullet.BulletType;
|
||||
import mindustry.entities.traits.BuilderTrait.BuildRequest;
|
||||
import mindustry.entities.traits.ShooterTrait;
|
||||
import mindustry.entities.type.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.net.Administration.TraceInfo;
|
||||
import mindustry.net.Packets.AdminAction;
|
||||
import mindustry.net.Packets.KickReason;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Class for specifying read/write methods for code generation. */
|
||||
@SuppressWarnings("unused")
|
||||
public class TypeIO{
|
||||
|
||||
@WriteClass(Player.class)
|
||||
public static void writePlayer(ByteBuffer buffer, Player player){
|
||||
if(player == null){
|
||||
buffer.putInt(-1);
|
||||
}else{
|
||||
buffer.putInt(player.id);
|
||||
}
|
||||
}
|
||||
|
||||
@ReadClass(Player.class)
|
||||
public static Player readPlayer(ByteBuffer buffer){
|
||||
int id = buffer.getInt();
|
||||
return id == -1 ? null : playerGroup.getByID(id);
|
||||
}
|
||||
|
||||
@WriteClass(Unit.class)
|
||||
public static void writeUnit(ByteBuffer buffer, Unit unit){
|
||||
if(unit.getGroup() == null){
|
||||
buffer.put((byte)-1);
|
||||
return;
|
||||
}
|
||||
buffer.put((byte)unit.getGroup().getID());
|
||||
buffer.putInt(unit.getID());
|
||||
}
|
||||
|
||||
@ReadClass(Unit.class)
|
||||
public static Unit readUnit(ByteBuffer buffer){
|
||||
byte gid = buffer.get();
|
||||
if(gid == -1) return null;
|
||||
int id = buffer.getInt();
|
||||
return (Unit)entities.get(gid).getByID(id);
|
||||
}
|
||||
|
||||
@WriteClass(ShooterTrait.class)
|
||||
public static void writeShooter(ByteBuffer buffer, ShooterTrait trait){
|
||||
buffer.put((byte)trait.getGroup().getID());
|
||||
buffer.putInt(trait.getID());
|
||||
}
|
||||
|
||||
@ReadClass(ShooterTrait.class)
|
||||
public static ShooterTrait readShooter(ByteBuffer buffer){
|
||||
byte gid = buffer.get();
|
||||
int id = buffer.getInt();
|
||||
return (ShooterTrait)entities.get(gid).getByID(id);
|
||||
}
|
||||
|
||||
@WriteClass(Bullet.class)
|
||||
public static void writeBullet(ByteBuffer buffer, Bullet bullet){
|
||||
buffer.putInt(bullet.getID());
|
||||
}
|
||||
|
||||
@ReadClass(Bullet.class)
|
||||
public static Bullet readBullet(ByteBuffer buffer){
|
||||
int id = buffer.getInt();
|
||||
return bulletGroup.getByID(id);
|
||||
}
|
||||
|
||||
@WriteClass(BaseUnit.class)
|
||||
public static void writeBaseUnit(ByteBuffer buffer, BaseUnit unit){
|
||||
buffer.put((byte)unit.getTeam().ordinal());
|
||||
buffer.putInt(unit.getID());
|
||||
}
|
||||
|
||||
@ReadClass(BaseUnit.class)
|
||||
public static BaseUnit readBaseUnit(ByteBuffer buffer){
|
||||
byte tid = buffer.get();
|
||||
int id = buffer.getInt();
|
||||
return unitGroups[tid].getByID(id);
|
||||
}
|
||||
|
||||
@WriteClass(Tile.class)
|
||||
public static void writeTile(ByteBuffer buffer, Tile tile){
|
||||
buffer.putInt(tile == null ? Pos.get(-1, -1) : tile.pos());
|
||||
}
|
||||
|
||||
@ReadClass(Tile.class)
|
||||
public static Tile readTile(ByteBuffer buffer){
|
||||
return world.tile(buffer.getInt());
|
||||
}
|
||||
|
||||
@WriteClass(Block.class)
|
||||
public static void writeBlock(ByteBuffer buffer, Block block){
|
||||
buffer.putShort(block.id);
|
||||
}
|
||||
|
||||
@ReadClass(Block.class)
|
||||
public static Block readBlock(ByteBuffer buffer){
|
||||
return content.block(buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(BuildRequest[].class)
|
||||
public static void writeRequests(ByteBuffer buffer, BuildRequest[] requests){
|
||||
buffer.putShort((short)requests.length);
|
||||
for(BuildRequest request : requests){
|
||||
buffer.put(request.breaking ? (byte)1 : 0);
|
||||
buffer.putInt(Pos.get(request.x, request.y));
|
||||
if(!request.breaking){
|
||||
buffer.putShort(request.block.id);
|
||||
buffer.put((byte)request.rotation);
|
||||
buffer.put(request.hasConfig ? (byte)1 : 0);
|
||||
buffer.putInt(request.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReadClass(BuildRequest[].class)
|
||||
public static BuildRequest[] readRequests(ByteBuffer buffer){
|
||||
short reqamount = buffer.getShort();
|
||||
BuildRequest[] reqs = new BuildRequest[reqamount];
|
||||
for(int i = 0; i < reqamount; i++){
|
||||
byte type = buffer.get();
|
||||
int position = buffer.getInt();
|
||||
BuildRequest currentRequest;
|
||||
|
||||
if(world.tile(position) == null){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(type == 1){ //remove
|
||||
currentRequest = new BuildRequest(Pos.x(position), Pos.y(position));
|
||||
}else{ //place
|
||||
short block = buffer.getShort();
|
||||
byte rotation = buffer.get();
|
||||
boolean hasConfig = buffer.get() == 1;
|
||||
int config = buffer.getInt();
|
||||
currentRequest = new BuildRequest(Pos.x(position), Pos.y(position), rotation, content.block(block));
|
||||
if(hasConfig){
|
||||
currentRequest.configure(config);
|
||||
}
|
||||
}
|
||||
|
||||
reqs[i] = (currentRequest);
|
||||
}
|
||||
|
||||
return reqs;
|
||||
}
|
||||
|
||||
@WriteClass(KickReason.class)
|
||||
public static void writeKick(ByteBuffer buffer, KickReason reason){
|
||||
buffer.put((byte)reason.ordinal());
|
||||
}
|
||||
|
||||
@ReadClass(KickReason.class)
|
||||
public static KickReason readKick(ByteBuffer buffer){
|
||||
return KickReason.values()[buffer.get()];
|
||||
}
|
||||
|
||||
@WriteClass(Rules.class)
|
||||
public static void writeRules(ByteBuffer buffer, Rules rules){
|
||||
String string = JsonIO.write(rules);
|
||||
byte[] bytes = string.getBytes(charset);
|
||||
buffer.putInt(bytes.length);
|
||||
buffer.put(bytes);
|
||||
}
|
||||
|
||||
@ReadClass(Rules.class)
|
||||
public static Rules readRules(ByteBuffer buffer){
|
||||
int length = buffer.getInt();
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
String string = new String(bytes, charset);
|
||||
return JsonIO.read(Rules.class, string);
|
||||
}
|
||||
|
||||
@WriteClass(Team.class)
|
||||
public static void writeTeam(ByteBuffer buffer, Team reason){
|
||||
buffer.put((byte)reason.ordinal());
|
||||
}
|
||||
|
||||
@ReadClass(Team.class)
|
||||
public static Team readTeam(ByteBuffer buffer){
|
||||
return Team.all[buffer.get()];
|
||||
}
|
||||
|
||||
@WriteClass(UnitCommand.class)
|
||||
public static void writeUnitCommand(ByteBuffer buffer, UnitCommand reason){
|
||||
buffer.put((byte)reason.ordinal());
|
||||
}
|
||||
|
||||
@ReadClass(UnitCommand.class)
|
||||
public static UnitCommand readUnitCommand(ByteBuffer buffer){
|
||||
return UnitCommand.all[buffer.get()];
|
||||
}
|
||||
|
||||
@WriteClass(AdminAction.class)
|
||||
public static void writeAction(ByteBuffer buffer, AdminAction reason){
|
||||
buffer.put((byte)reason.ordinal());
|
||||
}
|
||||
|
||||
@ReadClass(AdminAction.class)
|
||||
public static AdminAction readAction(ByteBuffer buffer){
|
||||
return AdminAction.values()[buffer.get()];
|
||||
}
|
||||
|
||||
@WriteClass(Effect.class)
|
||||
public static void writeEffect(ByteBuffer buffer, Effect effect){
|
||||
buffer.putShort((short)effect.id);
|
||||
}
|
||||
|
||||
@ReadClass(Effect.class)
|
||||
public static Effect readEffect(ByteBuffer buffer){
|
||||
return Effects.getEffect(buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(UnitType.class)
|
||||
public static void writeUnitType(ByteBuffer buffer, UnitType effect){
|
||||
buffer.putShort(effect.id);
|
||||
}
|
||||
|
||||
@ReadClass(UnitType.class)
|
||||
public static UnitType readUnitType(ByteBuffer buffer){
|
||||
return content.getByID(ContentType.unit, buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(Color.class)
|
||||
public static void writeColor(ByteBuffer buffer, Color color){
|
||||
buffer.putInt(Color.rgba8888(color));
|
||||
}
|
||||
|
||||
@ReadClass(Color.class)
|
||||
public static Color readColor(ByteBuffer buffer){
|
||||
return new Color(buffer.getInt());
|
||||
}
|
||||
|
||||
@WriteClass(Mech.class)
|
||||
public static void writeMech(ByteBuffer buffer, Mech mech){
|
||||
buffer.put((byte)mech.id);
|
||||
}
|
||||
|
||||
@ReadClass(Mech.class)
|
||||
public static Mech readMech(ByteBuffer buffer){
|
||||
return content.getByID(ContentType.mech, buffer.get());
|
||||
}
|
||||
|
||||
@WriteClass(Liquid.class)
|
||||
public static void writeLiquid(ByteBuffer buffer, Liquid liquid){
|
||||
buffer.putShort(liquid == null ? -1 : liquid.id);
|
||||
}
|
||||
|
||||
@ReadClass(Liquid.class)
|
||||
public static Liquid readLiquid(ByteBuffer buffer){
|
||||
short id = buffer.getShort();
|
||||
return id == -1 ? null : content.liquid(id);
|
||||
}
|
||||
|
||||
@WriteClass(BulletType.class)
|
||||
public static void writeBulletType(ByteBuffer buffer, BulletType type){
|
||||
buffer.putShort(type.id);
|
||||
}
|
||||
|
||||
@ReadClass(BulletType.class)
|
||||
public static BulletType readBulletType(ByteBuffer buffer){
|
||||
return content.getByID(ContentType.bullet, buffer.getShort());
|
||||
}
|
||||
|
||||
@WriteClass(Item.class)
|
||||
public static void writeItem(ByteBuffer buffer, Item item){
|
||||
buffer.putShort(item == null ? -1 : item.id);
|
||||
}
|
||||
|
||||
@ReadClass(Item.class)
|
||||
public static Item readItem(ByteBuffer buffer){
|
||||
short id = buffer.getShort();
|
||||
return id == -1 ? null : content.item(id);
|
||||
}
|
||||
|
||||
@WriteClass(String.class)
|
||||
public static void writeString(ByteBuffer buffer, String string){
|
||||
if(string != null){
|
||||
byte[] bytes = string.getBytes(charset);
|
||||
buffer.putShort((short)bytes.length);
|
||||
buffer.put(bytes);
|
||||
}else{
|
||||
buffer.putShort((short)-1);
|
||||
}
|
||||
}
|
||||
|
||||
@ReadClass(String.class)
|
||||
public static String readString(ByteBuffer buffer){
|
||||
short slength = buffer.getShort();
|
||||
if(slength != -1){
|
||||
byte[] bytes = new byte[slength];
|
||||
buffer.get(bytes);
|
||||
return new String(bytes, charset);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@WriteClass(byte[].class)
|
||||
public static void writeBytes(ByteBuffer buffer, byte[] bytes){
|
||||
buffer.putShort((short)bytes.length);
|
||||
buffer.put(bytes);
|
||||
}
|
||||
|
||||
@ReadClass(byte[].class)
|
||||
public static byte[] readBytes(ByteBuffer buffer){
|
||||
short length = buffer.getShort();
|
||||
byte[] bytes = new byte[length];
|
||||
buffer.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@WriteClass(TraceInfo.class)
|
||||
public static void writeTraceInfo(ByteBuffer buffer, TraceInfo trace){
|
||||
writeString(buffer, trace.ip);
|
||||
writeString(buffer, trace.uuid);
|
||||
buffer.put(trace.modded ? (byte)1 : 0);
|
||||
buffer.put(trace.mobile ? (byte)1 : 0);
|
||||
}
|
||||
|
||||
@ReadClass(TraceInfo.class)
|
||||
public static TraceInfo readTraceInfo(ByteBuffer buffer){
|
||||
return new TraceInfo(readString(buffer), readString(buffer), buffer.get() == 1, buffer.get() == 1);
|
||||
}
|
||||
|
||||
public static void writeStringData(DataOutput buffer, String string) throws IOException{
|
||||
if(string != null){
|
||||
byte[] bytes = string.getBytes(charset);
|
||||
buffer.writeShort((short)bytes.length);
|
||||
buffer.write(bytes);
|
||||
}else{
|
||||
buffer.writeShort((short)-1);
|
||||
}
|
||||
}
|
||||
|
||||
public static String readStringData(DataInput buffer) throws IOException{
|
||||
short slength = buffer.readShort();
|
||||
if(slength != -1){
|
||||
byte[] bytes = new byte[slength];
|
||||
buffer.readFully(bytes);
|
||||
return new String(bytes, charset);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
145
core/src/mindustry/io/versions/LegacyTypeTable.java
Normal file
145
core/src/mindustry/io/versions/LegacyTypeTable.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import arc.func.Prov;
|
||||
import mindustry.entities.type.Bullet;
|
||||
import mindustry.entities.effect.*;
|
||||
import mindustry.entities.type.Player;
|
||||
import mindustry.entities.type.base.*;
|
||||
|
||||
/*
|
||||
Latest data: [build 81]
|
||||
|
||||
0 = Player
|
||||
1 = Fire
|
||||
2 = Puddle
|
||||
3 = MinerDrone
|
||||
4 = RepairDrone
|
||||
5 = BuilderDrone
|
||||
6 = GroundUnit
|
||||
7 = GroundUnit
|
||||
8 = GroundUnit
|
||||
9 = GroundUnit
|
||||
10 = GroundUnit
|
||||
11 = FlyingUnit
|
||||
12 = FlyingUnit
|
||||
13 = Revenant
|
||||
|
||||
Before removal of lightining/bullet: [build 80]
|
||||
|
||||
0 = Player
|
||||
1 = Fire
|
||||
2 = Puddle
|
||||
3 = Bullet
|
||||
4 = Lightning
|
||||
5 = MinerDrone
|
||||
6 = RepairDrone
|
||||
7 = BuilderDrone
|
||||
8 = GroundUnit
|
||||
9 = GroundUnit
|
||||
10 = GroundUnit
|
||||
11 = GroundUnit
|
||||
12 = GroundUnit
|
||||
13 = FlyingUnit
|
||||
14 = FlyingUnit
|
||||
15 = Revenant
|
||||
|
||||
Before addition of new units: [build 79 and below]
|
||||
|
||||
0 = Player
|
||||
1 = Fire
|
||||
2 = Puddle
|
||||
3 = Bullet
|
||||
4 = Lightning
|
||||
5 = RepairDrone
|
||||
6 = GroundUnit
|
||||
7 = GroundUnit
|
||||
8 = GroundUnit
|
||||
9 = GroundUnit
|
||||
10 = GroundUnit
|
||||
11 = FlyingUnit
|
||||
12 = FlyingUnit
|
||||
13 = BuilderDrone
|
||||
14 = Revenant
|
||||
*/
|
||||
public class LegacyTypeTable{
|
||||
/*
|
||||
0 = Player
|
||||
1 = Fire
|
||||
2 = Puddle
|
||||
3 = Draug
|
||||
4 = Spirit
|
||||
5 = Phantom
|
||||
6 = Dagger
|
||||
7 = Crawler
|
||||
8 = Titan
|
||||
9 = Fortress
|
||||
10 = Eruptor
|
||||
11 = Wraith
|
||||
12 = Ghoul
|
||||
13 = Revenant
|
||||
*/
|
||||
private static final Prov[] build81Table = {
|
||||
Player::new,
|
||||
Fire::new,
|
||||
Puddle::new,
|
||||
MinerDrone::new,
|
||||
RepairDrone::new,
|
||||
BuilderDrone::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
FlyingUnit::new,
|
||||
FlyingUnit::new,
|
||||
HoverUnit::new
|
||||
};
|
||||
|
||||
private static final Prov[] build80Table = {
|
||||
Player::new,
|
||||
Fire::new,
|
||||
Puddle::new,
|
||||
Bullet::new,
|
||||
Lightning::new,
|
||||
MinerDrone::new,
|
||||
RepairDrone::new,
|
||||
BuilderDrone::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
FlyingUnit::new,
|
||||
FlyingUnit::new,
|
||||
HoverUnit::new
|
||||
};
|
||||
|
||||
private static final Prov[] build79Table = {
|
||||
Player::new,
|
||||
Fire::new,
|
||||
Puddle::new,
|
||||
Bullet::new,
|
||||
Lightning::new,
|
||||
RepairDrone::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
GroundUnit::new,
|
||||
FlyingUnit::new,
|
||||
FlyingUnit::new,
|
||||
BuilderDrone::new,
|
||||
HoverUnit::new
|
||||
};
|
||||
|
||||
public static Prov[] getTable(int build){
|
||||
if(build == -1 || build == 81){
|
||||
//return most recent one since that's probably it; not guaranteed
|
||||
return build81Table;
|
||||
}else if(build == 80){
|
||||
return build80Table;
|
||||
}else{
|
||||
return build79Table;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
core/src/mindustry/io/versions/Save1.java
Normal file
32
core/src/mindustry/io/versions/Save1.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import arc.func.*;
|
||||
import mindustry.entities.traits.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class Save1 extends Save2{
|
||||
|
||||
public Save1(){
|
||||
version = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntities(DataInput stream) throws IOException{
|
||||
Prov[] table = LegacyTypeTable.getTable(lastReadBuild);
|
||||
|
||||
byte groups = stream.readByte();
|
||||
|
||||
for(int i = 0; i < groups; i++){
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
readChunk(stream, true, in -> {
|
||||
byte typeid = in.readByte();
|
||||
byte version = in.readByte();
|
||||
SaveTrait trait = (SaveTrait)table[typeid].get();
|
||||
trait.readSave(in, version);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
core/src/mindustry/io/versions/Save2.java
Normal file
35
core/src/mindustry/io/versions/Save2.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.ctype.ContentType;
|
||||
import mindustry.entities.traits.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.type.TypeID;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static mindustry.Vars.content;
|
||||
|
||||
public class Save2 extends SaveVersion{
|
||||
|
||||
public Save2(){
|
||||
super(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readEntities(DataInput stream) throws IOException{
|
||||
byte groups = stream.readByte();
|
||||
|
||||
for(int i = 0; i < groups; i++){
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
//TODO throw exception on read fail
|
||||
readChunk(stream, true, in -> {
|
||||
byte typeid = in.readByte();
|
||||
byte version = in.readByte();
|
||||
SaveTrait trait = (SaveTrait)content.<TypeID>getByID(ContentType.typeid, typeid).constructor.get();
|
||||
trait.readSave(in, version);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
core/src/mindustry/io/versions/Save3.java
Normal file
9
core/src/mindustry/io/versions/Save3.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package mindustry.io.versions;
|
||||
|
||||
import mindustry.io.*;
|
||||
|
||||
public class Save3 extends SaveVersion{
|
||||
public Save3(){
|
||||
super(3);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user