diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 67c1c57664..05a0864c55 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -230,7 +230,7 @@ data.export = Export Data data.import = Import Data data.exported = Data exported. data.invalid = This isn't valid game data. -data.import.confirm = Importing external data will erase[scarlet] all[] your current game data.\n[accent]This cannot be undone![]\n\nOnce the data is imported, your game will exit immediately. +data.import.confirm = Importing external data will overwrite[scarlet] all[] your current game data.\n[accent]This cannot be undone![]\n\nOnce the data is imported, your game will exit immediately. classic.export = Export Classic Data classic.export.text = [accent]Mindustry[] has just had a major update.\nClassic (v3.5 build 40) save or map data has been detected. Would you like to export these saves to your phone's home folder, for use in the Mindustry Classic app? quit.confirm = Are you sure you want to quit? diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 6491a639e4..869338ffc7 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -185,6 +185,10 @@ public class World{ Events.fire(new WorldLoadEvent()); } + public void setGenerating(boolean gen){ + this.generating = gen; + } + public boolean isGenerating(){ return generating; } diff --git a/core/src/io/anuke/mindustry/entities/type/Player.java b/core/src/io/anuke/mindustry/entities/type/Player.java index eeaebc5fef..80c9cded3a 100644 --- a/core/src/io/anuke/mindustry/entities/type/Player.java +++ b/core/src/io/anuke/mindustry/entities/type/Player.java @@ -357,8 +357,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ public void drawOver(){ if(dead) return; - if(isBuilding()){ - if(!state.isPaused() && isBuilding){ + if(isBuilding() && isBuilding){ + if(!state.isPaused()){ drawBuilding(); } }else{ @@ -458,7 +458,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ } //mine only when not building - if(buildRequest() == null){ + if(buildRequest() == null || !isBuilding){ updateMining(); } } diff --git a/core/src/io/anuke/mindustry/game/GlobalData.java b/core/src/io/anuke/mindustry/game/GlobalData.java index 6746425d82..908173e751 100644 --- a/core/src/io/anuke/mindustry/game/GlobalData.java +++ b/core/src/io/anuke/mindustry/game/GlobalData.java @@ -66,14 +66,8 @@ public class GlobalData{ throw new IllegalArgumentException("Not valid save data."); } - //purge existing data - for(FileHandle f : base.list()){ - if(f.isDirectory()){ - f.deleteDirectory(); - }else if(!f.name().equals("zipdata.zip")){ - f.delete(); - } - } + //purge existing tmp data, keep everything else + tmpDirectory.deleteDirectory(); zipped.walk(f -> f.copyTo(base.child(f.path()))); dest.delete(); diff --git a/core/src/io/anuke/mindustry/game/Saves.java b/core/src/io/anuke/mindustry/game/Saves.java index ff22a74210..25bc7720c9 100644 --- a/core/src/io/anuke/mindustry/game/Saves.java +++ b/core/src/io/anuke/mindustry/game/Saves.java @@ -22,13 +22,12 @@ import java.util.*; import static io.anuke.mindustry.Vars.*; public class Saves{ - private int nextSlot; private Array saves = new Array<>(); - private IntMap saveMap = new IntMap<>(); private SaveSlot current; private AsyncExecutor previewExecutor = new AsyncExecutor(1); private boolean saving; private float time; + private FileHandle zoneFile; private long totalPlaytime; private long lastTimestamp; @@ -47,16 +46,13 @@ public class Saves{ public void load(){ saves.clear(); - IntArray slots = Core.settings.getObject("save-slots", IntArray.class, IntArray::new); + zoneFile = saveDirectory.child("-1.msav"); - for(int i = 0; i < slots.size; i++){ - int index = slots.get(i); - if(SaveIO.isSaveValid(index)){ - SaveSlot slot = new SaveSlot(index); + for(FileHandle file : saveDirectory.list()){ + if(!file.name().contains("backup") && SaveIO.isSaveValid(file)){ + SaveSlot slot = new SaveSlot(file); saves.add(slot); - saveMap.put(slot.index, slot); - slot.meta = SaveIO.getMeta(index); - nextSlot = Math.max(index + 1, nextSlot); + slot.meta = SaveIO.getMeta(file); } } } @@ -110,73 +106,63 @@ public class Saves{ } public void zoneSave(){ - SaveSlot slot = new SaveSlot(-1); + SaveSlot slot = new SaveSlot(zoneFile); slot.setName("zone"); - saves.remove(s -> s.index == -1); + saves.remove(s -> s.file.equals(zoneFile)); saves.add(slot); - saveMap.put(slot.index, slot); slot.save(); - saveSlots(); } public SaveSlot addSave(String name){ - SaveSlot slot = new SaveSlot(nextSlot); - nextSlot++; + SaveSlot slot = new SaveSlot(getNextSlotFile()); slot.setName(name); saves.add(slot); - saveMap.put(slot.index, slot); slot.save(); - saveSlots(); return slot; } public SaveSlot importSave(FileHandle file) throws IOException{ - SaveSlot slot = new SaveSlot(nextSlot); + SaveSlot slot = new SaveSlot(getNextSlotFile()); slot.importFile(file); - nextSlot++; slot.setName(file.nameWithoutExtension()); saves.add(slot); - saveMap.put(slot.index, slot); - slot.meta = SaveIO.getMeta(slot.index); + slot.meta = SaveIO.getMeta(slot.file); current = slot; - saveSlots(); return slot; } public SaveSlot getZoneSlot(){ - SaveSlot slot = getByID(-1); + SaveSlot slot = getSaveSlots().find(s -> s.file.equals(zoneFile)); return slot == null || slot.getZone() == null ? null : slot; } - public SaveSlot getByID(int id){ - return saveMap.get(id); + public FileHandle getNextSlotFile(){ + int i = 0; + FileHandle file; + while((file = saveDirectory.child(i + "." + saveExtension)).exists()){ + i ++; + } + return file; } public Array getSaveSlots(){ return saves; } - private void saveSlots(){ - IntArray result = new IntArray(saves.size); - for(int i = 0; i < saves.size; i++) result.add(saves.get(i).index); - - Core.settings.putObject("save-slots", result); - Core.settings.save(); - } - public class SaveSlot{ - public final int index; + //public final int index; + public final FileHandle file; boolean requestedPreview; SaveMeta meta; - public SaveSlot(int index){ - this.index = index; + public SaveSlot(FileHandle file){ + this.file = file; } public void load() throws SaveException{ try{ - SaveIO.loadFromSlot(index); - meta = SaveIO.getMeta(index); + SaveIO.load(file); + meta = SaveIO.getMeta(file); current = this; totalPlaytime = meta.timePlayed; savePreview(); @@ -190,8 +176,8 @@ public class Saves{ long prev = totalPlaytime; totalPlaytime = time; - SaveIO.saveToSlot(index); - meta = SaveIO.getMeta(index); + SaveIO.save(file); + meta = SaveIO.getMeta(file); if(!state.is(State.menu)){ current = this; } @@ -226,8 +212,12 @@ public class Saves{ return null; } + private String index(){ + return file.nameWithoutExtension(); + } + private FileHandle previewFile(){ - return mapPreviewDirectory.child("save_slot_" + index + ".png"); + return mapPreviewDirectory.child("save_slot_" + index() + ".png"); } private FileHandle loadPreviewFile(){ @@ -266,11 +256,11 @@ public class Saves{ } public String getName(){ - return Core.settings.getString("save-" + index + "-name", "untitled"); + return Core.settings.getString("save-" + index() + "-name", "untitled"); } public void setName(String name){ - Core.settings.put("save-" + index + "-name", name); + Core.settings.put("save-" + index() + "-name", name); Core.settings.save(); } @@ -295,34 +285,33 @@ public class Saves{ } public boolean isAutosave(){ - return Core.settings.getBool("save-" + index + "-autosave", true); + return Core.settings.getBool("save-" + index() + "-autosave", true); } public void setAutosave(boolean save){ - Core.settings.put("save-" + index + "-autosave", save); + Core.settings.put("save-" + index() + "-autosave", save); Core.settings.save(); } - public void importFile(FileHandle file) throws IOException{ + public void importFile(FileHandle from) throws IOException{ try{ - file.copyTo(SaveIO.fileFor(index)); + from.copyTo(file); }catch(Exception e){ throw new IOException(e); } } - public void exportFile(FileHandle file) throws IOException{ + public void exportFile(FileHandle to) throws IOException{ try{ - SaveIO.fileFor(index).copyTo(file); + file.copyTo(to); }catch(Exception e){ throw new IOException(e); } } public void delete(){ - SaveIO.fileFor(index).delete(); + file.delete(); saves.removeValue(this, true); - saveMap.remove(index); if(this == current){ current = null; } @@ -330,8 +319,6 @@ public class Saves{ if(Core.assets.isLoaded(loadPreviewFile().path())){ Core.assets.unload(loadPreviewFile().path()); } - - saveSlots(); } } } diff --git a/core/src/io/anuke/mindustry/game/Schematics.java b/core/src/io/anuke/mindustry/game/Schematics.java index 24e2721694..2236fe484c 100644 --- a/core/src/io/anuke/mindustry/game/Schematics.java +++ b/core/src/io/anuke/mindustry/game/Schematics.java @@ -19,6 +19,7 @@ import io.anuke.mindustry.input.*; import io.anuke.mindustry.input.PlaceUtils.*; import io.anuke.mindustry.type.*; import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.blocks.*; import java.io.*; import java.util.zip.*; @@ -205,7 +206,7 @@ public class Schematics implements Loadable{ /** Creates an array of build requests from a schematic's data, centered on the provided x+y coordinates. */ public Array toRequests(Schematic schem, int x, int y){ - return schem.tiles.map(t -> new BuildRequest(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block).original(t.x, t.y, schem.width, schem.height).configure(t.config)).removeAll(s -> !s.block.isVisible()); + return schem.tiles.map(t -> new BuildRequest(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block).original(t.x, t.y, schem.width, schem.height).configure(t.config)).removeAll(s -> !s.block.isVisible() || !s.block.unlocked()); } /** Adds a schematic to the list, also copying it into the files.*/ @@ -251,7 +252,7 @@ public class Schematics implements Loadable{ for(int cy = y; cy <= y2; cy++){ Tile linked = world.ltile(cx, cy); - if(linked != null && linked.entity != null && linked.entity.block.isVisible()){ + if(linked != null && linked.entity != null && linked.entity.block.isVisible() && !(linked.block() instanceof BuildBlock)){ int top = linked.block().size/2; int bot = linked.block().size % 2 == 1 ? -linked.block().size/2 : -(linked.block().size - 1)/2; minx = Math.min(linked.x + bot, minx); @@ -279,7 +280,7 @@ public class Schematics implements Loadable{ for(int cy = oy; cy <= oy2; cy++){ Tile tile = world.ltile(cx, cy); - if(tile != null && tile.entity != null && !counted.contains(tile.pos())){ + if(tile != null && tile.entity != null && !counted.contains(tile.pos()) && !(tile.block() instanceof BuildBlock) && tile.entity.block.isVisible()){ int config = tile.entity.config(); if(tile.block().posConfig){ config = Pos.get(Pos.x(config) + offsetX, Pos.y(config) + offsetY); diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index 3948f0efad..f41cea2e93 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -182,7 +182,7 @@ public class DesktopInput extends InputHandler{ mode = none; } - if(mode != none || isPlacing()){ + if(mode == placing || isPlacing()){ selectRequests.clear(); lastSchematic = null; } @@ -379,7 +379,7 @@ public class DesktopInput extends InputHandler{ deleting = true; }else if(selected != null){ //only begin shooting if there's no cursor event - if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && player.buildQueue().size == 0 && !droppingItem && + if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && (player.buildQueue().size == 0 || !player.isBuilding) && !droppingItem && !tryBeginMine(selected) && player.getMineTile() == null && !ui.chatfrag.chatOpen()){ player.isShooting = true; } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 8a6035363a..341682746f 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -34,37 +34,23 @@ public class SaveIO{ return versions.get(version); } - public static void saveToSlot(int slot){ - FileHandle file = fileFor(slot); + public static void save(FileHandle file){ boolean exists = file.exists(); if(exists) file.moveTo(backupFileFor(file)); try{ - write(fileFor(slot)); + write(file); }catch(Exception e){ if(exists) backupFileFor(file).moveTo(file); throw new RuntimeException(e); } } - public static void loadFromSlot(int slot) throws SaveException{ - load(fileFor(slot)); + public static DataInputStream getStream(FileHandle file){ + return new DataInputStream(new InflaterInputStream(file.read(bufferSize))); } - public static DataInputStream getSlotStream(int slot){ - return new DataInputStream(new InflaterInputStream(fileFor(slot).read(bufferSize))); - } - - public static DataInputStream getBackupSlotStream(int slot){ - return new DataInputStream(new InflaterInputStream(backupFileFor(fileFor(slot)).read(bufferSize))); - } - - public static boolean isSaveValid(int slot){ - try{ - getMeta(slot); - return true; - }catch(Exception e){ - return false; - } + public static DataInputStream getBackupStream(FileHandle file){ + return new DataInputStream(new InflaterInputStream(backupFileFor(file).read(bufferSize))); } public static boolean isSaveValid(FileHandle file){ @@ -85,11 +71,11 @@ public class SaveIO{ } } - public static SaveMeta getMeta(int slot){ + public static SaveMeta getMeta(FileHandle file){ try{ - return getMeta(getSlotStream(slot)); + return getMeta(getStream(file)); }catch(Exception e){ - return getMeta(getBackupSlotStream(slot)); + return getMeta(getBackupStream(file)); } } @@ -167,6 +153,7 @@ public class SaveIO{ }catch(Exception e){ throw new SaveException(e); }finally{ + world.setGenerating(false); content.setTemporaryMapper(null); } } diff --git a/core/src/io/anuke/mindustry/io/SaveVersion.java b/core/src/io/anuke/mindustry/io/SaveVersion.java index b02fc92fa7..f3b994abae 100644 --- a/core/src/io/anuke/mindustry/io/SaveVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveVersion.java @@ -3,6 +3,7 @@ package io.anuke.mindustry.io; import io.anuke.arc.collection.*; import io.anuke.arc.util.*; import io.anuke.arc.util.io.*; +import io.anuke.mindustry.content.*; import io.anuke.mindustry.core.*; import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.entities.*; @@ -166,6 +167,7 @@ public abstract class SaveVersion extends SaveFileReader{ 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); @@ -182,6 +184,7 @@ public abstract class SaveVersion extends SaveFileReader{ 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){ diff --git a/core/src/io/anuke/mindustry/world/Tile.java b/core/src/io/anuke/mindustry/world/Tile.java index f34d236afe..2dfe5a8669 100644 --- a/core/src/io/anuke/mindustry/world/Tile.java +++ b/core/src/io/anuke/mindustry/world/Tile.java @@ -4,6 +4,7 @@ import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.math.*; import io.anuke.arc.math.geom.*; +import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.type.*; @@ -152,7 +153,7 @@ public class Tile implements Position, TargetTrait{ return team; } - public void setBlock(Block type, Team team, int rotation){ + public void setBlock(@NonNull Block type, Team team, int rotation){ preChanged(); this.block = type; this.team = (byte)team.ordinal(); @@ -160,11 +161,12 @@ public class Tile implements Position, TargetTrait{ changed(); } - public void setBlock(Block type, Team team){ + public void setBlock(@NonNull Block type, Team team){ setBlock(type, team, 0); } - public void setBlock(Block type){ + public void setBlock(@NonNull Block type){ + if(type == null) throw new IllegalArgumentException("Block cannot be null."); preChanged(); this.block = type; this.rotation = 0; @@ -172,13 +174,13 @@ public class Tile implements Position, TargetTrait{ } /**This resets the overlay!*/ - public void setFloor(Floor type){ + public void setFloor(@NonNull Floor type){ this.floor = type; this.overlay = (Floor)Blocks.air; } /** Sets the floor, preserving overlay.*/ - public void setFloorUnder(Floor floor){ + public void setFloorUnder(@NonNull Floor floor){ Block overlay = this.overlay; setFloor(floor); setOverlay(overlay); diff --git a/server/src/io/anuke/mindustry/server/ServerControl.java b/server/src/io/anuke/mindustry/server/ServerControl.java index d8a06ac6e0..4240ce65d2 100644 --- a/server/src/io/anuke/mindustry/server/ServerControl.java +++ b/server/src/io/anuke/mindustry/server/ServerControl.java @@ -670,28 +670,25 @@ public class ServerControl implements ApplicationListener{ if(state.is(State.playing)){ err("Already hosting. Type 'stop' to stop hosting first."); return; - }else if(!Strings.canParseInt(arg[0])){ - err("Invalid save slot '{0}'.", arg[0]); - return; } - int slot = Strings.parseInt(arg[0]); + FileHandle file = saveDirectory.child(arg[0] + "." + saveExtension); - if(!SaveIO.isSaveValid(slot)){ + if(!SaveIO.isSaveValid(file)){ err("No (valid) save data found for slot."); return; } Core.app.post(() -> { try{ - SaveIO.loadFromSlot(slot); + SaveIO.load(file); state.rules.zone = null; + info("Save loaded."); + host(); + state.set(State.playing); }catch(Throwable t){ err("Failed to load save. Outdated or corrupt file."); } - info("Save loaded."); - host(); - state.set(State.playing); }); }); @@ -699,18 +696,25 @@ public class ServerControl implements ApplicationListener{ if(!state.is(State.playing)){ err("Not hosting. Host a game first."); return; - }else if(!Strings.canParseInt(arg[0])){ - err("Invalid save slot '{0}'.", arg[0]); - return; } + FileHandle file = saveDirectory.child(arg[0] + "." + saveExtension); + Core.app.post(() -> { - int slot = Strings.parseInt(arg[0]); - SaveIO.saveToSlot(slot); - info("Saved to slot {0}.", slot); + SaveIO.save(file); + info("Saved to {0}.", file); }); }); + handler.register("saves", "List all saves in the save directory.", arg -> { + info("Save files: "); + for(FileHandle file : saveDirectory.list()){ + if(file.extension().equals(saveExtension)){ + info("| &ly{0}", file.nameWithoutExtension()); + } + } + }); + handler.register("gameover", "Force a game over.", arg -> { if(state.is(State.menu)){ info("Not playing a map."); diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index dff351c59c..83f6ed971b 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -198,7 +198,7 @@ public class ApplicationTests{ void save(){ world.loadMap(testMap); assertTrue(state.teams.get(defaultTeam).cores.size > 0); - SaveIO.saveToSlot(0); + SaveIO.save(saveDirectory.child("0.msav")); } @Test @@ -206,9 +206,9 @@ public class ApplicationTests{ world.loadMap(testMap); Map map = world.getMap(); - SaveIO.saveToSlot(0); + SaveIO.save(saveDirectory.child("0.msav")); resetWorld(); - SaveIO.loadFromSlot(0); + SaveIO.load(saveDirectory.child("0.msav")); assertEquals(world.width(), map.width); assertEquals(world.height(), map.height);