diff --git a/core/src/Mindustry.gwt.xml b/core/src/Mindustry.gwt.xml index d9e0d528c1..c664bbb5f5 100644 --- a/core/src/Mindustry.gwt.xml +++ b/core/src/Mindustry.gwt.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/Control.java b/core/src/io/anuke/mindustry/Control.java index 13071f5051..97c27b14b3 100644 --- a/core/src/io/anuke/mindustry/Control.java +++ b/core/src/io/anuke/mindustry/Control.java @@ -16,6 +16,7 @@ import io.anuke.mindustry.entities.enemies.*; import io.anuke.mindustry.input.AndroidInput; import io.anuke.mindustry.input.GestureHandler; import io.anuke.mindustry.input.Input; +import io.anuke.mindustry.io.SaveIO; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.ProductionBlocks; import io.anuke.ucore.core.*; @@ -33,7 +34,6 @@ public class Control extends RendererModule{ boolean hiscore = false; final Array weapons = new Array<>(); - //final ObjectMap weapons = new ObjectMap(); int wave = 1; float wavetime; @@ -149,6 +149,12 @@ public class Control extends RendererModule{ return weapons; } + public void setWaveData(int enemies, int wave, float wavetime){ + this.wave = wave; + this.wavetime = wavetime; + this.enemies = enemies; + } + void runWave(){ int amount = wave; Sounds.play("spawn"); @@ -269,13 +275,26 @@ public class Control extends RendererModule{ @Override public void update(){ - if(Inputs.keyUp(Keys.ESCAPE) && debug) - Gdx.app.exit(); - - //camera.zoom = MathUtils.lerp(camera.zoom, targetzoom, 0.5f*delta()); - - if(Inputs.keyUp(Keys.SPACE) && debug) - Effects.sound("shoot", World.core.worldx(), World.core.worldy()); + if(debug){ + if(Inputs.keyUp(Keys.ESCAPE)) + Gdx.app.exit(); + + if(Inputs.keyUp(Keys.SPACE)) + Effects.sound("shoot", World.core.worldx(), World.core.worldy()); + + if(Inputs.keyUp(Keys.O)){ + Timers.mark(); + SaveIO.write(Gdx.files.local("mapsave.mds")); + log("Save time taken: " + Timers.elapsed()); + } + + if(Inputs.keyUp(Keys.P)){ + Timers.mark(); + SaveIO.load(Gdx.files.local("mapsave.mds")); + log("Load time taken: " + Timers.elapsed()); + Renderer.clearTiles(); + } + } if(GameState.is(State.menu)){ clearScreen(); diff --git a/core/src/io/anuke/mindustry/Inventory.java b/core/src/io/anuke/mindustry/Inventory.java index 9acfa05b63..a7c82e4364 100644 --- a/core/src/io/anuke/mindustry/Inventory.java +++ b/core/src/io/anuke/mindustry/Inventory.java @@ -57,4 +57,8 @@ public class Inventory{ items.put(req.item, items.get(req.item, 0)-req.amount); ui.updateItems(); } + + public static ObjectMap getItems(){ + return items; + } } diff --git a/core/src/io/anuke/mindustry/Renderer.java b/core/src/io/anuke/mindustry/Renderer.java index dc951edb8d..ec718d179a 100644 --- a/core/src/io/anuke/mindustry/Renderer.java +++ b/core/src/io/anuke/mindustry/Renderer.java @@ -135,6 +135,13 @@ public class Renderer{ Draw.color(valid ? Color.PURPLE : Color.SCARLET); Draw.thickness(2f); Draw.square(x, y, tilesize / 2 + MathUtils.sin(Timers.time() / 6f) + 1); + + if(android){ + //TODO + Draw.thickness(1f); + Draw.color(Color.ORANGE); + Draw.square(x + tilesize/2, y + tilesize/2, tilesize/4); + } if(player.recipe.result.rotate){ Draw.color("orange"); diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 1d51afc1ab..65d0a5bf40 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -18,8 +18,7 @@ public class Vars{ public static final int baseCameraScale = Math.round(Unit.dp.inPixels(4)); public static final int zoomScale = Math.round(Unit.dp.inPixels(1)); - //not marked as final, because of warnings - public static boolean debug = false; + public static final boolean debug = true; //turret and enemy shoot speed inverse multiplier public static final int multiplier = android ? 3 : 1; diff --git a/core/src/io/anuke/mindustry/World.java b/core/src/io/anuke/mindustry/World.java index 97202efd8b..f640722bcc 100644 --- a/core/src/io/anuke/mindustry/World.java +++ b/core/src/io/anuke/mindustry/World.java @@ -5,6 +5,7 @@ import static io.anuke.mindustry.Vars.*; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; @@ -26,6 +27,7 @@ import io.anuke.ucore.util.Mathf; public class World{ public static int worldsize = 128; public static int pixsize = worldsize*tilesize; + private static int seed; private static Pixmap[] mapPixmaps; private static Texture[] mapTextures; @@ -87,6 +89,10 @@ public class World{ } public static void loadMap(int id){ + loadMap(id, MathUtils.random(0, 99999)); + } + + public static void loadMap(int id, int seed){ spawnpoints.clear(); @@ -104,6 +110,7 @@ public class World{ Entities.resizeTree(0, 0, pixsize, pixsize); + World.seed = seed; Generator.generate(mapPixmaps[id]); Pathfind.reset(); @@ -137,6 +144,10 @@ public class World{ tiles[x][y].rotation = rot; } + public static int getSeed(){ + return seed; + } + public static boolean validPlace(int x, int y, Block type){ if(!cursorNear() && !android) diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index a1bb0309e9..c002770da8 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -18,7 +18,6 @@ public class TileEntity extends Entity{ public int maxhealth, health; public boolean dead = false; - public TileEntity init(Tile tile){ this.tile = tile; x = tile.worldx(); diff --git a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java b/core/src/io/anuke/mindustry/entities/enemies/Enemy.java index 104d11ca83..06620d9782 100644 --- a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java +++ b/core/src/io/anuke/mindustry/entities/enemies/Enemy.java @@ -15,20 +15,22 @@ import io.anuke.ucore.entities.*; import io.anuke.ucore.util.Timers; public class Enemy extends DestructibleEntity{ - public Vector2 direction = new Vector2(); + protected float speed = 0.3f; + protected float reload = 40; + protected float range = 60; + protected float length = 4; + protected float rotatespeed = 8f; + protected BulletType bullet = BulletType.small; + protected String shootsound = "enemyshoot"; + public Tile[] path; - public float xvelocity, yvelocity; - public float speed = 0.3f; - public int node = -1; - public Entity target; public int spawn; - public float reload = 40; - public float range = 60; - public BulletType bullet = BulletType.small; - public float length = 4; - public float rotatespeed = 8f; - public String shootsound = "enemyshoot"; - public boolean dead = false; + public int node = -1; + + public Vector2 direction = new Vector2(); + public float xvelocity, yvelocity; + public Entity target; + public Enemy(int spawn){ this.spawn = spawn; @@ -64,7 +66,7 @@ public class Enemy extends DestructibleEntity{ } } - public void shoot(){ + void shoot(){ vector.set(length, 0).rotate(direction.angle()); Bullet out = new Bullet(bullet, this, x+vector.x, y+vector.y, direction.angle()).add(); out.damage = bullet.damage*Vars.multiplier; @@ -97,7 +99,7 @@ public class Enemy extends DestructibleEntity{ move(); xvelocity = x - lastx; - yvelocity = y-lasty; + yvelocity = y - lasty; if(target == null){ direction.add(xvelocity, yvelocity); diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java new file mode 100644 index 0000000000..1731b9e69f --- /dev/null +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -0,0 +1,299 @@ +package io.anuke.mindustry.io; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.reflect.ClassReflection; + +import io.anuke.mindustry.Inventory; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.World; +import io.anuke.mindustry.entities.Weapon; +import io.anuke.mindustry.entities.enemies.*; +import io.anuke.mindustry.resource.Item; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.Blocks; +import io.anuke.ucore.entities.Entities; +import io.anuke.ucore.entities.Entity; + +/* + * Save format: + * + * --STATE DATA-- + * Wave (int) + * Wave countdown time (float) + * + * Player X (float) + * Player Y (float) + * Player health (int) + * + * Amount of weapons (byte) + * (weapon list) + * Weapon enum ordinal (byte) + * + * Amount of items (byte) + * (item list) + * Item ID (byte) + * Item amount (int) + * + * --ENEMY DATA-- + * Amount of enemies (int) + * (enemy list) + * enemy type ID (byte) + * spawn lane (byte) + * x (float) + * y (float) + * health (int) + * + * + * --MAP DATA-- + * Map ID (byte) + * Seed (int) + * Amount of tiles (int) + * (tile list) + * Tile position, as a single integer, in the format x+y*width + * Tile type (boolean)- whether the block has a tile entity attached + * Block ID - the block ID + * (the following only applies to tile entity blocks) + * Block health (int) + * Amount of items (byte) + * (item list) + * Item ID (byte) + * Item amount (int) + * + */ +public class SaveIO{ + //TODO automatic registration of types? + private static final ObjectMap, Byte> enemyIDs = new ObjectMap, Byte>(){{ + put(Enemy.class, (byte)0); + put(FastEnemy.class, (byte)1); + put(BossEnemy.class, (byte)2); + put(FlameEnemy.class, (byte)3); + }}; + + private static final ObjectMap> idEnemies = new ObjectMap>(){{ + for(Class value : enemyIDs.keys()) + put(enemyIDs.get(value), value); + }}; + + public static void write(FileHandle file){ + + try(DataOutputStream stream = new DataOutputStream(file.write(false))){ + + //--GENERAL STATE-- + stream.writeInt(Vars.control.getWave()); //wave + stream.writeFloat(Vars.control.getWaveCountdown()); //wave countdown + + 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-- + + stream.writeByte(Inventory.getItems().size); //amount of items + + for(Item item : Inventory.getItems().keys()){ + stream.writeByte(item.ordinal()); //item ID + stream.writeInt(Inventory.getAmount(item)); //item amount + } + + //--ENEMIES-- + + int totalEnemies = 0; + + for(Entity entity : Entities.all()){ + if(entity instanceof Enemy){ + totalEnemies ++; + } + } + + stream.writeInt(totalEnemies); //enemy amount + + for(Entity entity : Entities.all()){ + if(entity instanceof Enemy){ + Enemy enemy = (Enemy)entity; + stream.writeByte(enemyIDs.get(enemy.getClass())); //type + stream.writeByte(enemy.spawn); //lane + stream.writeFloat(enemy.x); //x + stream.writeFloat(enemy.y); //y + stream.writeInt(enemy.health); //health + } + } + + //--MAP DATA-- + + //map ID + stream.writeByte(World.getMap()); + + //seed + stream.writeInt(World.getSeed()); + + int totalblocks = 0; + + for(int x = 0; x < World.width(); x ++){ + for(int y = 0; y < World.height(); y ++){ + Tile tile = World.tile(x, y); + + if(tile.breakable()){ + totalblocks ++; + } + } + } + + //tile amount + stream.writeInt(totalblocks); + + for(int x = 0; x < World.width(); x ++){ + for(int y = 0; y < World.height(); y ++){ + Tile tile = World.tile(x, y); + + if(tile.breakable()){ + + stream.writeInt(x + y*World.width()); //tile pos + stream.writeBoolean(tile.entity != null); //whether it has a tile entity + stream.writeInt(tile.block().id); //block ID + + if(tile.entity != null){ + stream.writeInt(tile.entity.health); //health + stream.writeByte(tile.entity.items.size); //amount of items + + for(Item item : tile.entity.items.keys()){ + stream.writeByte(item.ordinal()); //item ID + stream.writeInt(tile.entity.items.get(item)); //item amount + } + } + } + } + } + + }catch (IOException e){ + throw new RuntimeException(e); + } + } + + public static void load(FileHandle file){ + + try(DataInputStream stream = new DataInputStream(file.read())){ + Item[] itemEnums = Item.values(); + + //general state + + int wave = stream.readInt(); + float wavetime = stream.readFloat(); + + 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.camera.position.set(playerx, playery, 0); + + //weapons + + 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(); + + Inventory.getItems().clear(); + + for(int i = 0; i < totalItems; i ++){ + Item item = itemEnums[stream.readByte()]; + int amount = stream.readInt(); + Inventory.getItems().put(item, amount); + } + + Vars.ui.updateItems(); + + //enemies + + int enemies = stream.readInt(); + + for(int i = 0; i < enemies; i ++){ + byte type = stream.readByte(); + int lane = stream.readByte(); + float x = stream.readFloat(); + float y = stream.readFloat(); + int health = stream.readInt(); + + try{ + Enemy enemy = (Enemy)ClassReflection.getConstructor(idEnemies.get(type), int.class).newInstance(lane); + enemy.health = health; + enemy.x = x; + enemy.y = y; + enemy.add(); + }catch (Exception e){ + throw new RuntimeException(e); + } + } + + Vars.control.setWaveData(enemies, wave, wavetime); + + //map + + int mapid = stream.readByte(); + int seed = stream.readInt(); + int tiles = stream.readInt(); + + World.loadMap(mapid, seed); + + for(int x = 0; x < World.width(); x ++){ + for(int y = 0; y < World.height(); y ++){ + Tile tile = World.tile(x, y); + + //remove breakables like rocks + if(tile.breakable()){ + World.tile(x, y).setBlock(Blocks.air); + } + } + } + + for(int i = 0; i < tiles; i ++){ + int pos = stream.readInt(); + boolean hasEntity = stream.readBoolean(); + int blockid = stream.readInt(); + + Tile tile = World.tile(pos % World.width(), pos / World.width()); + tile.setBlock(Block.getByID(blockid)); + + if(hasEntity){ + int health = stream.readInt(); + int items = stream.readByte(); + + tile.entity.health = health; + + for(int j = 0; j < items; j ++){ + int itemid = stream.readByte(); + int itemamount = stream.readInt(); + tile.entity.items.put(itemEnums[itemid], itemamount); + } + } + } + + }catch (IOException e){ + throw new RuntimeException(e); + } + } +} diff --git a/core/src/io/anuke/mindustry/io/SaveState.java b/core/src/io/anuke/mindustry/io/SaveState.java deleted file mode 100644 index ab5bc9ee68..0000000000 --- a/core/src/io/anuke/mindustry/io/SaveState.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.anuke.mindustry.io; - -import io.anuke.mindustry.world.Tile; - -public class SaveState{ - public Tile[][] tiles; -} diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index fbed23f177..4b3b6afb92 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -3,6 +3,7 @@ package io.anuke.mindustry.world; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; import io.anuke.mindustry.World; import io.anuke.mindustry.entities.TileEntity; @@ -13,6 +14,7 @@ import io.anuke.ucore.util.Mathf; public class Block{ private static int lastid; + private static Array blocks = new Array(); protected static Vector2 vector = new Vector2(); protected static Vector2 vector2 = new Vector2(); @@ -30,6 +32,8 @@ public class Block{ public boolean vary = true; public Block(String name) { + blocks.add(this); + this.name = name; this.solid = false; this.id = lastid++; @@ -186,5 +190,13 @@ public class Block{ Draw.rect(name(), tile.worldx(), tile.worldy(), rotate ? tile.rotation * 90 : 0); } } - + + + public static Array getAllBlocks(){ + return blocks; + } + + public static Block getByID(int id){ + return blocks.get(id); + } } diff --git a/core/src/io/anuke/mindustry/world/Generator.java b/core/src/io/anuke/mindustry/world/Generator.java index 7dba5be4ff..3b27118e18 100644 --- a/core/src/io/anuke/mindustry/world/Generator.java +++ b/core/src/io/anuke/mindustry/world/Generator.java @@ -2,7 +2,6 @@ package io.anuke.mindustry.world; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.ObjectMap; import io.anuke.mindustry.World; @@ -28,7 +27,8 @@ public class Generator{ /**Returns world size.*/ public static void generate(Pixmap pixmap){ - Noise.setSeed(MathUtils.random(0, 99999)); + Noise.setSeed(World.getSeed()); + for(int x = 0; x < pixmap.getWidth(); x ++){ for(int y = 0; y < pixmap.getHeight(); y ++){ Block floor = Blocks.stone; diff --git a/desktop/mapsave.mds b/desktop/mapsave.mds new file mode 100644 index 0000000000..35b843f084 Binary files /dev/null and b/desktop/mapsave.mds differ diff --git a/html/src/io/anuke/mindustry/GdxDefinition.gwt.xml b/html/src/io/anuke/mindustry/GdxDefinition.gwt.xml index 4c353cf771..d79d1ffccc 100644 --- a/html/src/io/anuke/mindustry/GdxDefinition.gwt.xml +++ b/html/src/io/anuke/mindustry/GdxDefinition.gwt.xml @@ -1,5 +1,5 @@ - + diff --git a/html/src/io/anuke/mindustry/GdxDefinitionSuperdev.gwt.xml b/html/src/io/anuke/mindustry/GdxDefinitionSuperdev.gwt.xml index 59fcbe6a4d..2066c6820b 100644 --- a/html/src/io/anuke/mindustry/GdxDefinitionSuperdev.gwt.xml +++ b/html/src/io/anuke/mindustry/GdxDefinitionSuperdev.gwt.xml @@ -1,5 +1,5 @@ - +