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 extends Enemy> 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 @@
-
+