From d68768e24a28b37e8286788ad94368978828d6f5 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 3 Jan 2018 11:28:07 -0500 Subject: [PATCH] Fixed save file crash, implemented save ID mapping --- .gitignore | 1 + .../anuke/mindustry/io/SaveFileVersion.java | 12 + core/src/io/anuke/mindustry/io/SaveIO.java | 26 +- core/src/io/anuke/mindustry/io/Saves.java | 1 + .../anuke/mindustry/io/versions/Save12.java | 4 +- .../anuke/mindustry/io/versions/Save13.java | 7 +- .../anuke/mindustry/io/versions/Save14.java | 357 ++++++++++++++++++ core/src/io/anuke/mindustry/world/Block.java | 11 + .../io/anuke/mindustry/world/BlockLoader.java | 122 ++++++ .../world/blocks/ProductionBlocks.java | 24 +- 10 files changed, 538 insertions(+), 27 deletions(-) create mode 100644 core/src/io/anuke/mindustry/io/versions/Save14.java diff --git a/.gitignore b/.gitignore index c47b756d8a..58d2076cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /desktop/mindustry-maps/ /desktop/gifexport/ /core/lib/ +/kryonet/build/ /android/assets/mindustry-maps/ /android/assets/mindustry-saves/ /core/assets/gifexport/ diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index 8c9e03e874..49a63d59b9 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -46,4 +46,16 @@ public abstract class SaveFileVersion { public abstract void read(DataInputStream stream) throws IOException; public abstract void write(DataOutputStream stream) throws IOException; + + public static void writeString(DataOutputStream stream, String string) throws IOException{ + stream.writeByte(string.length()); + stream.writeBytes(string); + } + + public static String readString(DataInputStream stream) throws IOException{ + int length = stream.readByte(); + byte[] result = new byte[length]; + stream.read(result); + return new String(result); + } } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index e8079388d8..c5181d1d1d 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -1,22 +1,31 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Base64Coder; -import com.badlogic.gdx.utils.OrderedMap; +import com.badlogic.gdx.utils.IntMap; import io.anuke.mindustry.Vars; import io.anuke.mindustry.io.versions.Save12; import io.anuke.mindustry.io.versions.Save13; +import io.anuke.mindustry.io.versions.Save14; import io.anuke.ucore.UCore; import io.anuke.ucore.core.Settings; import java.io.*; public class SaveIO{ + public static final IntMap versions = new IntMap<>(); + public static final Array versionArray = Array.with( + new Save12(), + new Save13(), + new Save14() + ); - public static final OrderedMap versions = new OrderedMap(){{ - put(12, new Save12()); - put(13, new Save13()); - }}; + static{ + for(SaveFileVersion version : versionArray){ + versions.put(version.version, version); + } + } public static void saveToSlot(int slot){ if(Vars.gwt){ @@ -26,11 +35,12 @@ public class SaveIO{ Settings.save(); }else{ FileHandle file = fileFor(slot); - file.moveTo(file.sibling(file.name() + "-backup." + file.extension())); + boolean exists = file.exists(); + if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension())); try { write(fileFor(slot)); }catch (Exception e){ - file.sibling(file.name() + "-backup." + file.extension()).moveTo(file); + if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file); throw new RuntimeException(e); } } @@ -156,6 +166,6 @@ public class SaveIO{ } public static SaveFileVersion getVersion(){ - return versions.get(versions.orderedKeys().peek()); + return versionArray.peek(); } } diff --git a/core/src/io/anuke/mindustry/io/Saves.java b/core/src/io/anuke/mindustry/io/Saves.java index 5dceee5cc1..61374e4506 100644 --- a/core/src/io/anuke/mindustry/io/Saves.java +++ b/core/src/io/anuke/mindustry/io/Saves.java @@ -82,6 +82,7 @@ public class Saves { saves.add(slot); SaveIO.saveToSlot(slot.index); slot.meta = SaveIO.getData(slot.index); + current = slot; } public Array getSaveSlots(){ diff --git a/core/src/io/anuke/mindustry/io/versions/Save12.java b/core/src/io/anuke/mindustry/io/versions/Save12.java index 763c9f520a..9aab74821f 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save12.java +++ b/core/src/io/anuke/mindustry/io/versions/Save12.java @@ -8,7 +8,7 @@ import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.io.SaveFileVersion; import io.anuke.mindustry.resource.Item; import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.BlockLoader; import io.anuke.mindustry.world.GameMode; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.Blocks; @@ -149,7 +149,7 @@ public class Save12 extends SaveFileVersion { int blockid = stream.readInt(); Tile tile = Vars.world.tile(pos % Vars.world.width(), pos / Vars.world.width()); - tile.setBlock(Block.getByID(blockid)); + tile.setBlock(BlockLoader.getByOldID(blockid)); tile.link = link; if(hasEntity){ diff --git a/core/src/io/anuke/mindustry/io/versions/Save13.java b/core/src/io/anuke/mindustry/io/versions/Save13.java index c79a5c8428..220daf1bc6 100644 --- a/core/src/io/anuke/mindustry/io/versions/Save13.java +++ b/core/src/io/anuke/mindustry/io/versions/Save13.java @@ -8,10 +8,7 @@ import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.io.SaveFileVersion; import io.anuke.mindustry.resource.Item; import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.GameMode; -import io.anuke.mindustry.world.Generator; -import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.blocks.Blocks; import io.anuke.mindustry.world.blocks.types.BlockPart; import io.anuke.mindustry.world.blocks.types.Rock; @@ -160,7 +157,7 @@ public class Save13 extends SaveFileVersion { int blockid = stream.readInt(); Tile tile = Vars.world.tile(pos % Vars.world.width(), pos / Vars.world.width()); - tile.setBlock(Block.getByID(blockid)); + tile.setBlock(BlockLoader.getByOldID(blockid)); if(blockid == Blocks.blockpart.id){ tile.link = stream.readByte(); diff --git a/core/src/io/anuke/mindustry/io/versions/Save14.java b/core/src/io/anuke/mindustry/io/versions/Save14.java new file mode 100644 index 0000000000..84f0aa150b --- /dev/null +++ b/core/src/io/anuke/mindustry/io/versions/Save14.java @@ -0,0 +1,357 @@ +package io.anuke.mindustry.io.versions; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.TimeUtils; +import com.badlogic.gdx.utils.reflect.ClassReflection; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.entities.enemies.Enemy; +import io.anuke.mindustry.io.SaveFileVersion; +import io.anuke.mindustry.resource.Item; +import io.anuke.mindustry.resource.Weapon; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.GameMode; +import io.anuke.mindustry.world.Generator; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.Blocks; +import io.anuke.mindustry.world.blocks.types.BlockPart; +import io.anuke.mindustry.world.blocks.types.Rock; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.entities.Entities; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import static io.anuke.mindustry.Vars.android; + +public class Save14 extends SaveFileVersion{ + + + public Save14(){ + super(14); + } + + @Override + public void read(DataInputStream stream) throws IOException { + + int version = stream.readInt(); + /*long loadTime = */stream.readLong(); + + if(version != this.version){ + throw new RuntimeException("Save file version mismatch!"); + } + + //general state + byte mode = stream.readByte(); + byte mapid = stream.readByte(); + + int wave = stream.readInt(); + float wavetime = stream.readFloat(); + + //block header + + int blocksize = stream.readInt(); + + IntMap map = new IntMap<>(); + + for(int i = 0; i < blocksize; i ++){ + String name = readString(stream); + int id = stream.readShort(); + + map.put(id, Block.getByName(name)); + } + + float playerx = stream.readFloat(); + float playery = stream.readFloat(); + + int playerhealth = stream.readInt(); + + Vars.player.x = playerx; + Vars.player.y = playery; + Vars.player.health = playerhealth; + Vars.control.setMode(GameMode.values()[mode]); + Core.camera.position.set(playerx, playery, 0); + + //weapons + + Vars.control.getWeapons().clear(); + Vars.control.getWeapons().add(Weapon.blaster); + Vars.player.weapon = Weapon.blaster; + + int weapons = stream.readByte(); + + for(int i = 0; i < weapons; i ++){ + Vars.control.addWeapon(Weapon.values()[stream.readByte()]); + } + + Vars.ui.updateWeapons(); + + //inventory + + int totalItems = stream.readByte(); + + Arrays.fill(Vars.control.getItems(), 0); + + for(int i = 0; i < totalItems; i ++){ + Item item = Item.getByID(stream.readByte()); + int amount = stream.readInt(); + Vars.control.getItems()[item.id] = amount; + } + + Vars.ui.updateItems(); + + //enemies + + Entities.clear(); + + int enemies = stream.readInt(); + + Array enemiesToUpdate = new Array<>(); + + for(int i = 0; i < enemies; i ++){ + byte type = stream.readByte(); + int lane = stream.readByte(); + float x = stream.readFloat(); + float y = stream.readFloat(); + byte tier = stream.readByte(); + int health = stream.readShort(); + + try{ + Enemy enemy = ClassReflection.newInstance(enemyIDs.get(type)); + enemy.lane = lane; + enemy.health = health; + enemy.x = x; + enemy.y = y; + enemy.tier = tier; + enemy.add(Vars.control.enemyGroup); + enemiesToUpdate.add(enemy); + }catch (Exception e){ + throw new RuntimeException(e); + } + } + + Vars.control.setWaveData(enemies, wave, wavetime); + + if(!android) + Vars.player.add(); + + //map + + int seed = stream.readInt(); + + Vars.world.loadMap(Vars.world.maps().getMap(mapid), seed); + Vars.renderer.clearTiles(); + + for(Enemy enemy : enemiesToUpdate){ + enemy.node = -2; + } + + int rocks = stream.readInt(); + + for(int x = 0; x < Vars.world.width(); x ++){ + for(int y = 0; y < Vars.world.height(); y ++){ + Tile tile = Vars.world.tile(x, y); + + //remove breakables like rocks + if(tile.breakable()){ + Vars.world.tile(x, y).setBlock(Blocks.air); + } + } + } + + for(int i = 0; i < rocks; i ++){ + int pos = stream.readInt(); + Tile tile = Vars.world.tile(pos % Vars.world.width(), pos / Vars.world.width()); + Block result = Generator.rocks.get(tile.floor()); + if(result != null) tile.setBlock(result); + } + + int tiles = stream.readInt(); + + for(int i = 0; i < tiles; i ++){ + int pos = stream.readInt(); + int blockid = stream.readInt(); + + Tile tile = Vars.world.tile(pos % Vars.world.width(), pos / Vars.world.width()); + tile.setBlock(map.get(blockid)); + + if(blockid == Blocks.blockpart.id){ + tile.link = stream.readByte(); + } + + if(tile.entity != null){ + byte rotation = stream.readByte(); + short health = stream.readShort(); + int items = stream.readByte(); + + tile.entity.health = health; + tile.setRotation(rotation); + + for(int j = 0; j < items; j ++){ + int itemid = stream.readByte(); + int itemamount = stream.readInt(); + tile.entity.items[itemid] = itemamount; + } + + tile.entity.read(stream); + } + } + } + + @Override + public void write(DataOutputStream stream) throws IOException { + //--META-- + stream.writeInt(version); //version id + stream.writeLong(TimeUtils.millis()); //last saved + + //--GENERAL STATE-- + stream.writeByte(Vars.control.getMode().ordinal()); //gamemode + stream.writeByte(Vars.world.getMap().id); //map ID + + stream.writeInt(Vars.control.getWave()); //wave + stream.writeFloat(Vars.control.getWaveCountdown()); //wave countdown + + //--BLOCK HEADER-- + + stream.writeInt(Block.getAllBlocks().size); + + for(int i = 0; i < Block.getAllBlocks().size; i ++){ + Block block = Block.getAllBlocks().get(i); + writeString(stream, block.name); + stream.writeShort(block.id); + } + + stream.writeFloat(Vars.player.x); //player x/y + stream.writeFloat(Vars.player.y); + + stream.writeInt(Vars.player.health); //player health + + stream.writeByte(Vars.control.getWeapons().size - 1); //amount of weapons + + //start at 1, because the first weapon is always the starter - ignore that + for(int i = 1; i < Vars.control.getWeapons().size; i ++){ + stream.writeByte(Vars.control.getWeapons().get(i).ordinal()); //weapon ordinal + } + + //--INVENTORY-- + + int l = Vars.control.getItems().length; + int itemsize = 0; + + for(int i = 0; i < l; i ++){ + if(Vars.control.getItems()[i] > 0){ + itemsize ++; + } + } + + stream.writeByte(itemsize); //amount of items + + for(int i = 0; i < l; i ++){ + if(Vars.control.getItems()[i] > 0){ + stream.writeByte(i); //item ID + stream.writeInt(Vars.control.getItems()[i]); //item amount + } + } + + //--ENEMIES-- + + int totalEnemies = 0; + + Array enemies = Vars.control.enemyGroup.all(); + + for(int i = 0; i < enemies.size; i ++){ + Enemy enemy = enemies.get(i); + if(idEnemies.containsKey(enemy.getClass())){ + totalEnemies ++; + } + } + + stream.writeInt(totalEnemies); //enemy amount + + for(int i = 0; i < enemies.size; i ++){ + Enemy enemy = enemies.get(i); + if(idEnemies.containsKey(enemy.getClass())){ + stream.writeByte(idEnemies.get(enemy.getClass())); //type + stream.writeByte(enemy.lane); //lane + stream.writeFloat(enemy.x); //x + stream.writeFloat(enemy.y); //y + stream.writeByte(enemy.tier); //tier + stream.writeShort(enemy.health); //health + } + } + + //--MAP DATA-- + + //seed + stream.writeInt(Vars.world.getSeed()); + + int totalblocks = 0; + int totalrocks = 0; + + for(int x = 0; x < Vars.world.width(); x ++){ + for(int y = 0; y < Vars.world.height(); y ++){ + Tile tile = Vars.world.tile(x, y); + + if(tile.breakable()){ + if(tile.block() instanceof Rock){ + totalrocks ++; + }else{ + totalblocks ++; + } + } + } + } + + //amount of rocks + stream.writeInt(totalrocks); + + //write all rocks + for(int x = 0; x < Vars.world.width(); x ++) { + for (int y = 0; y < Vars.world.height(); y++) { + Tile tile = Vars.world.tile(x, y); + + if (tile.block() instanceof Rock) { + stream.writeInt(tile.packedPosition()); + } + } + } + + //write all blocks + stream.writeInt(totalblocks); + + for(int x = 0; x < Vars.world.width(); x ++){ + for(int y = 0; y < Vars.world.height(); y ++){ + Tile tile = Vars.world.tile(x, y); + + if(tile.breakable() && !(tile.block() instanceof Rock)){ + + stream.writeInt(x + y*Vars.world.width()); //tile pos + stream.writeInt(tile.block().id); //block ID + + if(tile.block() instanceof BlockPart) stream.writeByte(tile.link); + + if(tile.entity != null){ + stream.writeByte(tile.getRotation()); //placerot + stream.writeShort(tile.entity.health); //health + byte amount = 0; + for(int i = 0; i < tile.entity.items.length; i ++){ + if(tile.entity.items[i] > 0) amount ++; + } + stream.writeByte(amount); //amount of items + + for(int i = 0; i < tile.entity.items.length; i ++){ + if(tile.entity.items[i] > 0){ + stream.writeByte(i); //item ID + stream.writeInt(tile.entity.items[i]); //item amount + } + } + + tile.entity.write(stream); + } + } + } + } + } +} diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index edd96df28b..ceb5fe8fc1 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.GameState; import io.anuke.mindustry.core.GameState.State; @@ -21,6 +22,7 @@ import io.anuke.ucore.util.Tmp; public class Block{ private static int lastid; private static Array blocks = new Array(); + private static ObjectMap map = new ObjectMap<>(); protected static TextureRegion temp = new TextureRegion(); @@ -88,6 +90,11 @@ public class Block{ this.solid = false; this.id = lastid++; + if(map.containsKey(name)){ + throw new RuntimeException("Two blocks cannot have the same names! Problematic block: " + name); + } + + map.put(name, this); blocks.add(this); } @@ -258,6 +265,10 @@ public class Block{ public static Array getAllBlocks(){ return blocks; } + + public static Block getByName(String name){ + return map.get(name); + } public static Block getByID(int id){ return blocks.get(id); diff --git a/core/src/io/anuke/mindustry/world/BlockLoader.java b/core/src/io/anuke/mindustry/world/BlockLoader.java index f58935a811..0b3c96bb1d 100644 --- a/core/src/io/anuke/mindustry/world/BlockLoader.java +++ b/core/src/io/anuke/mindustry/world/BlockLoader.java @@ -1,8 +1,109 @@ package io.anuke.mindustry.world; +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.ObjectIntMap; import io.anuke.mindustry.world.blocks.*; +import io.anuke.ucore.UCore; public class BlockLoader { + static final ObjectIntMap defaultMap = map( + "air", 0, + "blockpart", 1, + "deepwater", 2, + "water", 3, + "lava", 4, + "oil", 5, + "stone", 6, + "blackstone", 7, + "iron", 8, + "coal", 9, + "titanium", 10, + "uranium", 11, + "dirt", 12, + "sand", 13, + "ice", 14, + "snow", 15, + "grass", 16, + "sandblock", 17, + "snowblock", 18, + "stoneblock", 19, + "blackstoneblock", 20, + "grassblock", 21, + "mossblock", 22, + "shrub", 23, + "rock", 24, + "icerock", 25, + "blackrock", 26, + "dirtblock", 27, + "stonewall", 28, + "ironwall", 29, + "steelwall", 30, + "titaniumwall", 31, + "duriumwall", 32, + "compositewall", 33, + "steelwall-large", 34, + "titaniumwall-large", 35, + "duriumwall-large", 36, + "titaniumshieldwall", 37, + "repairturret", 38, + "megarepairturret", 39, + "shieldgenerator", 40, + "door", 41, + "door-large", 42, + "conduit", 43, + "pulseconduit", 44, + "liquidrouter", 45, + "conveyor", 46, + "steelconveyor", 47, + "poweredconveyor", 48, + "router", 49, + "junction", 50, + "conveyortunnel", 51, + "liquidjunction", 52, + "liquiditemjunction", 53, + "powerbooster", 54, + "powerlaser", 55, + "powerlaserrouter", 56, + "powerlasercorner", 57, + "teleporter", 58, + "sorter", 59, + "core", 60, + "pump", 61, + "fluxpump", 62, + "smelter", 63, + "crucible", 64, + "coalpurifier", 65, + "titaniumpurifier", 66, + "oilrefinery", 67, + "stoneformer", 68, + "lavasmelter", 69, + "stonedrill", 70, + "irondrill", 71, + "coaldrill", 72, + "uraniumdrill", 73, + "titaniumdrill", 74, + "omnidrill", 75, + "coalgenerator", 76, + "thermalgenerator", 77, + "combustiongenerator", 78, + "rtgenerator", 79, + "nuclearreactor", 80, + "turret", 81, + "doubleturret", 82, + "machineturret", 83, + "shotgunturret", 84, + "flameturret", 85, + "sniperturret", 86, + "mortarturret", 87, + "laserturret", 88, + "waveturret", 89, + "plasmaturret", 90, + "chainturret", 91, + "titancannon", 92, + "playerspawn", 93, + "enemyspawn", 94 + ); + static final IntMap blockmap = new IntMap<>(); public static void load(){ @@ -15,5 +116,26 @@ public class BlockLoader { SpecialBlocks.enemySpawn //add any new block sections here }; + + for(Block block : Block.getAllBlocks()){ + UCore.log("\""+block.name+"\"", block.id, ""); + } + + for(String string : defaultMap.keys()){ + Block block = Block.getByName(string); + blockmap.put(defaultMap.get(string, -1), block); + } + } + + public static Block getByOldID(int id){ + return blockmap.get(id); + } + + private static ObjectIntMap map(Object... objects){ + ObjectIntMap map = new ObjectIntMap<>(); + for(int i = 0; i < objects.length/2; i ++){ + map.put((String)objects[i*2], (int)objects[i*2+1]); + } + return map; } } diff --git a/core/src/io/anuke/mindustry/world/blocks/ProductionBlocks.java b/core/src/io/anuke/mindustry/world/blocks/ProductionBlocks.java index fcf21fbf72..cfc555ae62 100644 --- a/core/src/io/anuke/mindustry/world/blocks/ProductionBlocks.java +++ b/core/src/io/anuke/mindustry/world/blocks/ProductionBlocks.java @@ -102,6 +102,18 @@ public class ProductionBlocks{ craftEffect = Fx.purifystone; } }, + + siliconextractor = new LiquidCrafter("siliconextractor"){ + { + input = Item.sand; + inputAmount = 5; + inputLiquid = Liquid.water; + liquidAmount = 18.99f; + output = Item.sand; + health = 50; + purifyTime = 50; + } + }, stonedrill = new Drill("stonedrill"){ { @@ -207,16 +219,4 @@ public class ProductionBlocks{ breaktime *= 2.3f; } }; - /* - siliconextractor = new LiquidCrafter("siliconextractor"){ - { - input = Item.sand; - inputAmount = 5; - inputLiquid = Liquid.water; - liquidAmount = 18.99f; - output = Item.sand; - health = 50; - purifyTime = 50; - } - }*/; }