package mindustry.game; import arc.*; import arc.assets.*; import arc.struct.*; import arc.files.*; import arc.graphics.*; import arc.util.*; import arc.util.async.*; import mindustry.*; import mindustry.core.GameState.*; import mindustry.game.EventType.*; import mindustry.io.*; import mindustry.io.SaveIO.*; import mindustry.maps.Map; import mindustry.type.*; import java.io.*; import java.text.*; import java.util.*; import static mindustry.Vars.*; public class Saves{ private Array saves = new Array<>(); private SaveSlot current; private AsyncExecutor previewExecutor = new AsyncExecutor(1); private boolean saving; private float time; private Fi zoneFile; private long totalPlaytime; private long lastTimestamp; public Saves(){ Core.assets.setLoader(Texture.class, ".spreview", new SavePreviewLoader()); Events.on(StateChangeEvent.class, event -> { if(event.to == State.menu){ totalPlaytime = 0; lastTimestamp = 0; current = null; } }); } public void load(){ saves.clear(); zoneFile = saveDirectory.child("-1.msav"); for(Fi file : saveDirectory.list()){ if(!file.name().contains("backup") && SaveIO.isSaveValid(file)){ SaveSlot slot = new SaveSlot(file); saves.add(slot); slot.meta = SaveIO.getMeta(file); } } } public SaveSlot getCurrent(){ return current; } public void update(){ SaveSlot current = this.current; if(current != null && !state.is(State.menu) && !(state.isPaused() && Core.scene.hasDialog())){ if(lastTimestamp != 0){ totalPlaytime += Time.timeSinceMillis(lastTimestamp); } lastTimestamp = Time.millis(); } if(!state.is(State.menu) && !state.gameOver && current != null && current.isAutosave() && !state.rules.tutorial){ time += Time.delta(); if(time > Core.settings.getInt("saveinterval") * 60){ saving = true; Time.runTask(2f, () -> { try{ current.save(); }catch(Exception e){ e.printStackTrace(); } saving = false; }); time = 0; } }else{ time = 0; } } public long getTotalPlaytime(){ return totalPlaytime; } public void resetSave(){ current = null; } public boolean isSaving(){ return saving; } public void zoneSave(){ SaveSlot slot = new SaveSlot(zoneFile); slot.setName("zone"); saves.remove(s -> s.file.equals(zoneFile)); saves.add(slot); slot.save(); } public SaveSlot addSave(String name){ SaveSlot slot = new SaveSlot(getNextSlotFile()); slot.setName(name); saves.add(slot); slot.save(); return slot; } public SaveSlot importSave(Fi file) throws IOException{ SaveSlot slot = new SaveSlot(getNextSlotFile()); slot.importFile(file); slot.setName(file.nameWithoutExtension()); saves.add(slot); slot.meta = SaveIO.getMeta(slot.file); current = slot; return slot; } public SaveSlot getZoneSlot(){ SaveSlot slot = getSaveSlots().find(s -> s.file.equals(zoneFile)); return slot == null || slot.getZone() == null ? null : slot; } public Fi getNextSlotFile(){ int i = 0; Fi file; while((file = saveDirectory.child(i + "." + saveExtension)).exists()){ i ++; } return file; } public Array getSaveSlots(){ return saves; } public class SaveSlot{ //public final int index; public final Fi file; boolean requestedPreview; SaveMeta meta; public SaveSlot(Fi file){ this.file = file; } public void load() throws SaveException{ try{ SaveIO.load(file); meta = SaveIO.getMeta(file); current = this; totalPlaytime = meta.timePlayed; savePreview(); }catch(Exception e){ throw new SaveException(e); } } public void save(){ long time = totalPlaytime; long prev = totalPlaytime; totalPlaytime = time; SaveIO.save(file); meta = SaveIO.getMeta(file); if(!state.is(State.menu)){ current = this; } totalPlaytime = prev; savePreview(); } private void savePreview(){ if(Core.assets.isLoaded(loadPreviewFile().path())){ Core.assets.unload(loadPreviewFile().path()); } previewExecutor.submit(() -> { try{ previewFile().writePNG(renderer.minimap.getPixmap()); requestedPreview = false; }catch(Throwable t){ t.printStackTrace(); } }); } public Texture previewTexture(){ if(!previewFile().exists()){ return null; }else if(Core.assets.isLoaded(loadPreviewFile().path())){ return Core.assets.get(loadPreviewFile().path()); }else if(!requestedPreview){ Core.assets.load(new AssetDescriptor<>(loadPreviewFile(), Texture.class)); requestedPreview = true; } return null; } private String index(){ return file.nameWithoutExtension(); } private Fi previewFile(){ return mapPreviewDirectory.child("save_slot_" + index() + ".png"); } private Fi loadPreviewFile(){ return previewFile().sibling(previewFile().name() + ".spreview"); } public boolean isHidden(){ return getZone() != null; } public String getPlayTime(){ return Strings.formatMillis(current == this ? totalPlaytime : meta.timePlayed); } public long getTimestamp(){ return meta.timestamp; } public String getDate(){ return SimpleDateFormat.getDateTimeInstance().format(new Date(meta.timestamp)); } public Map getMap(){ return meta.map; } public void cautiousLoad(Runnable run){ Array mods = Array.with(getMods()); mods.removeAll(Vars.mods.getModStrings()); if(!mods.isEmpty()){ ui.showConfirm("$warning", Core.bundle.format("mod.missing", mods.toString("\n")), run); }else{ run.run(); } } public String getName(){ return Core.settings.getString("save-" + index() + "-name", "untitled"); } public void setName(String name){ Core.settings.put("save-" + index() + "-name", name); Core.settings.save(); } public String[] getMods(){ return meta.mods; } public Zone getZone(){ return meta == null || meta.rules == null ? null : meta.rules.zone; } public Gamemode mode(){ return Gamemode.bestFit(meta.rules); } public int getBuild(){ return meta.build; } public int getWave(){ return meta.wave; } public boolean isAutosave(){ return Core.settings.getBool("save-" + index() + "-autosave", true); } public void setAutosave(boolean save){ Core.settings.put("save-" + index() + "-autosave", save); Core.settings.save(); } public void importFile(Fi from) throws IOException{ try{ from.copyTo(file); }catch(Exception e){ throw new IOException(e); } } public void exportFile(Fi to) throws IOException{ try{ file.copyTo(to); }catch(Exception e){ throw new IOException(e); } } public void delete(){ file.delete(); saves.removeValue(this, true); if(this == current){ current = null; } if(Core.assets.isLoaded(loadPreviewFile().path())){ Core.assets.unload(loadPreviewFile().path()); } } } }