com.headswilllol.mineflat.world.SaveManager.java Source code

Java tutorial

Introduction

Here is the source code for com.headswilllol.mineflat.world.SaveManager.java

Source

/*
 * MineFlat
 * Copyright (c) 2014-2015, Maxim Roncac <caseif@caseif.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.headswilllol.mineflat.world;

import com.google.gson.*;
import com.headswilllol.mineflat.Direction;
import com.headswilllol.mineflat.Main;
import com.headswilllol.mineflat.Material;
import com.headswilllol.mineflat.entity.*;
import com.headswilllol.mineflat.entity.living.Living;
import com.headswilllol.mineflat.entity.living.Mob;
import com.headswilllol.mineflat.entity.living.hostile.Ghost;
import com.headswilllol.mineflat.entity.living.passive.Snail;
import com.headswilllol.mineflat.entity.living.player.Human;
import com.headswilllol.mineflat.entity.living.player.Player;
import com.headswilllol.mineflat.util.FileUtil;
import com.headswilllol.mineflat.util.VboUtil;

import java.io.*;
import java.util.Map;

public class SaveManager {

    private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();

    /**
     * Parses all loaded chunks in a world to a JSON object.
     */
    @SuppressWarnings("unchecked")
    public static JsonObject saveWorld(World world) {

        System.out.println("Saving chunks...");

        JsonObject save = world.getJson();
        save.addProperty("name", Main.world.getName());
        save.addProperty("createTime", Main.world.creationTime);
        save.addProperty("modifyTime", System.currentTimeMillis() / 1000L);
        save.addProperty("chunkCount", Main.world.getChunkCount());
        save.addProperty("chunkLength", Main.world.getChunkLength());
        save.addProperty("chunkHeight", Main.world.getChunkHeight());
        save.addProperty("seed", Main.world.getSeed());
        save.addProperty("ticks", TickManager.getTotalTicks());
        save.addProperty("playerLevel", Main.player.getLevel().getIndex());
        save.addProperty("playerChunk", Main.player.getLocation().getChunk());

        JsonObject levels = (JsonObject) save.get("levels");
        if (levels == null)
            levels = new JsonObject();
        for (Level level : world.getLevels()) {
            levels.add(Integer.toString(level.getIndex()), saveLevel(level));
        }
        save.add("levels", levels);
        return save;
    }

    public static void saveWorldToMemory(World world) {
        world.setJson(saveWorld(world));
    }

    public static void writeWorldToDisk(World world) {

        saveWorldToMemory(world);
        System.out.println("Writing chunks...");
        File saveFolder = new File(FileUtil.getAppDataFolder() + File.separator + ".mineflat", "saves");
        saveFolder = new File(saveFolder, world.getName());
        if (!saveFolder.exists())
            saveFolder.mkdirs();
        File saveFile = new File(saveFolder, "level.json");
        File zippedFile = new File(saveFolder, "level.gz");
        if (saveFile.exists())
            saveFile.delete();
        if (zippedFile.exists())
            zippedFile.delete();
        try {
            saveFile.createNewFile();
            PrintWriter writer = new PrintWriter(saveFile);
            Gson gson = new GsonBuilder().setPrettyPrinting().create();
            JsonElement je = new JsonParser().parse(gson.toJson(world.getJson()));
            writer.write(gson.toJson(je));
            writer.close();
            FileUtil.gzip(saveFile.getAbsolutePath(), zippedFile.getAbsolutePath());
        } catch (IOException ex) {
            ex.printStackTrace();
            System.err.println("Failed to save world to disk!");
        } finally {
            saveFile.delete();
        }
    }

    //TODO: make this method more type-safe
    public static void loadWorld(String world) {
        System.out.println("Loading world \"" + world + "\"");
        File saveFolder = new File(FileUtil.getAppDataFolder() + File.separator + ".mineflat", "saves");
        saveFolder = new File(saveFolder, world);
        if (!saveFolder.exists())
            saveFolder.mkdirs();
        File zippedFile = new File(saveFolder, "level.gz");
        File saveFile = new File(saveFolder, "level.json");
        if (!zippedFile.exists())
            return;
        FileUtil.ungzip(zippedFile.getAbsolutePath(), saveFile.getAbsolutePath());
        try {
            JsonObject save = (JsonObject) new JsonParser().parse(new FileReader(saveFile));
            Main.world = new World(save.get("name").getAsString(), longToInt(save.get("chunkCount").getAsLong()),
                    longToInt(save.get("chunkLength").getAsLong()), longToInt(save.get("chunkHeight").getAsLong()));
            Main.world.setJson(save);
            Main.world.seed = save.get("seed").getAsLong();
            Main.world.creationTime = save.get("createTime").getAsLong();
            TickManager.setTicks(longToInt(save.get("ticks").getAsLong()));
            loadLevel(Main.world, longToInt(save.get("playerLevel").getAsLong()));
            Main.world.setTicking(true);
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
            System.err.println("Failed to load world from disk - save file cannot be found!");
        } catch (ClassCastException ex) {
            ex.printStackTrace();
            System.err.println("Failed to load world from disk - save file is invalid!");
        } finally {
            saveFile.delete();
        }
    }

    public static Chunk loadChunk(Level level, int chunk) {
        JsonObject jChunk = level.getWorld().getJson().getAsJsonObject("levels")
                .getAsJsonObject(Integer.toString(level.getIndex())).getAsJsonObject("chunks")
                .getAsJsonObject(Integer.toString(chunk));
        if (jChunk != null) {
            System.out.println("Loading chunk " + chunk);
            Biome biome = Biome.getById(jChunk.get("biome").toString());
            Chunk c = new Chunk(level, chunk, biome);
            for (JsonElement blockObj : jChunk.getAsJsonArray("blocks")) {
                JsonObject block = blockObj.getAsJsonObject();
                Material type = Material.valueOf(block.get("type").getAsString());
                if (type == null)
                    type = Material.AIR;
                int data = block.get("data") != null ? block.get("data").getAsInt() : 0;
                Block b = new Block(type, data,
                        new Location(level, Chunk.getWorldXFromChunkIndex(chunk, block.get("x").getAsLong()),
                                block.get("y").getAsLong()));
                JsonObject meta = (JsonObject) block.get("metadata");
                for (Map.Entry<String, JsonElement> e : meta.entrySet()) {
                    JsonPrimitive prim = meta.get(e.getKey()).getAsJsonPrimitive();
                    Object value;
                    if (prim.isBoolean())
                        value = prim.getAsBoolean();
                    else if (prim.isNumber())
                        value = prim.getAsNumber();
                    else if (prim.isString())
                        value = prim.getAsString();
                    else
                        value = prim.getAsCharacter();
                    b.setMetadata(e.getKey(), value);
                }
                b.addToWorld();
            }
            for (Object entityObj : jChunk.get("entities").getAsJsonArray()) {
                JsonObject entity = (JsonObject) entityObj;
                EntityType type = EntityType.valueOf(entity.get("type").getAsString());
                float x = Chunk.getWorldXFromChunkIndex(c.getIndex(),
                        Float.valueOf(Double.toString(entity.get("x").getAsDouble())));
                float y = Float.valueOf(Double.toString(entity.get("y").getAsDouble()));
                float w = Float.valueOf(Double.toString(entity.get("w").getAsDouble()));
                float h = Float.valueOf(Double.toString(entity.get("h").getAsDouble()));
                Entity e;
                if (entity.has("living")) {
                    if (entity.has("mob")) {
                        switch (type) {
                        case GHOST:
                            e = new Ghost(new Location(level, x, y));
                            break;
                        case SNAIL:
                            e = new Snail(new Location(level, x, y));
                            break;
                        default:
                            continue; // ignore it
                        }
                        ((Mob) e).setPlannedWalkDistance(entity.get("pwd").getAsFloat());
                        ((Mob) e).setActualWalkDistance(entity.get("awd").getAsFloat());
                        ((Mob) e).setLastX(entity.get("lx").getAsFloat());
                    } else {
                        switch (type) {
                        case PLAYER:
                            e = new Player(new Location(level, x, y));
                            break;
                        case HUMAN:
                            e = new Human(new Location(level, x, y));
                            break;
                        default:
                            continue; // ignore it
                        }
                    }
                    ((Living) e).setFacingDirection(Direction.valueOf(entity.get("fd").getAsString()));
                    ((Living) e).setJumping(entity.get("j").getAsBoolean());
                } else
                    e = new Entity(type, new Location(level, x, y), w, h);
                e.getVelocity().setX(Float.valueOf(Double.toString(entity.get("xv").getAsDouble())));
                e.getVelocity().setY(Float.valueOf(Double.toString(entity.get("yv").getAsDouble())));
                level.addEntity(e);
                if (type == EntityType.PLAYER)
                    Main.player = (Player) e;
            }
            c.updateLight();
            Chunk left = c.getLevel().getChunk(c.getIndex() == 1 ? -1 : c.getIndex() - 1);
            Chunk right = c.getLevel().getChunk(c.getIndex() == -1 ? 1 : c.getIndex() + 1);
            if (left != null) {
                for (int y = 0; y < c.getLevel().getWorld().getChunkHeight(); y++) {
                    left.getBlock(c.getLevel().getWorld().getChunkLength() - 1, y).updateLight();
                    VboUtil.updateChunkArray(c.getLevel(), left.getIndex());
                }
            }
            if (right != null) {
                for (int y = 0; y < c.getLevel().getWorld().getChunkHeight(); y++) {
                    right.getBlock(0, y).updateLight();
                    VboUtil.updateChunkArray(c.getLevel(), right.getIndex());
                }
            }
            System.gc(); //TODO: temporary fix until I have the motivation to find the memory leak
            return c;
        }
        return null;
    }

    public static Level loadLevel(World world, int level) {
        Main.world.addLevel(level);
        Level l = Main.world.getLevel(level);
        if (longToInt(world.getJson().get("playerLevel").getAsLong()) == level) {
            loadChunk(l, longToInt(world.getJson().get("playerChunk").getAsLong()));
            Chunk.handleChunkLoading(true);
        }
        for (Chunk c : l.chunks.values())
            c.updateLight();
        return l;
    }

    /**
     * Saves a chunk to a JSON object. <strong>This method does not save the JSON to memory or to disk.</strong>
     * @param chunk the chunk to save.
     * @return the created JSON object.
     */
    public static JsonObject saveChunk(Chunk chunk) {
        JsonObject c = new JsonObject();
        JsonArray blocks = new JsonArray();
        for (int x = 0; x < Main.world.getChunkLength(); x++) {
            for (int y = 0; y < Main.world.getChunkHeight(); y++) {
                Block block = chunk.getBlock(x, y);
                if (block != null) {
                    JsonObject b = new JsonObject();
                    b.addProperty("x", x);
                    b.addProperty("y", y);
                    b.addProperty("type", block.getType().toString());
                    if (block.getData() != 0)
                        b.addProperty("data", block.getData());
                    blocks.add(b);
                    JsonObject meta = new JsonObject();
                    for (String key : block.getAllMetadata()) {
                        Object data = block.getMetadata(key);
                        if (data instanceof String)
                            meta.addProperty(key, (String) block.getMetadata(key));
                        else if (data instanceof Number)
                            meta.addProperty(key, (Number) block.getMetadata(key));
                        else if (data instanceof Boolean)
                            meta.addProperty(key, (Boolean) block.getMetadata(key));
                        else if (data instanceof Character)
                            meta.addProperty(key, (Character) block.getMetadata(key));
                        else
                            System.err.println("Failed to save metadata \"" + key + "\" for block at " + "{level="
                                    + chunk.getLevel().getIndex() + ", x=" + block.getX() + ", y=" + block.getY()
                                    + "}");
                    }
                    b.add("metadata", meta);
                }
            }
        }
        c.addProperty("biome", chunk.getBiome().getId());
        c.add("blocks", blocks);
        JsonArray entities = new JsonArray();
        for (Entity entity : chunk.getEntities()) {
            JsonObject e = new JsonObject();
            e.addProperty("type", entity.getType().toString());
            e.addProperty("x", entity.getLocation().getPosInChunk());
            e.addProperty("y", entity.getY());
            e.addProperty("w", entity.width);
            e.addProperty("h", entity.height);
            e.addProperty("xv", entity.getVelocity().getX());
            e.addProperty("yv", entity.getVelocity().getY());
            if (entity instanceof Living) {
                e.addProperty("living", true);
                Living le = (Living) entity;
                e.addProperty("fd", le.getFacingDirection().toString());
                e.addProperty("j", le.isJumping());
                if (le instanceof Mob) {
                    e.addProperty("mob", true);
                    Mob m = (Mob) le;
                    e.addProperty("pwd", m.getPlannedWalkDistance());
                    e.addProperty("awd", m.getActualWalkDistance());
                    e.addProperty("lx", m.getLastX());
                }
            }
            entities.add(e);
        }
        c.add("entities", entities);
        return c;
    }

    /**
     * Saves a level to a JSON object. <strong>This method does not save the JSON to memory or to disk.</strong>
     * @param level the chunk to save.
     * @return the created JSON object.
     */
    public static JsonObject saveLevel(Level level) {
        JsonObject l = new JsonObject();
        JsonObject chunks = new JsonObject();
        JsonObject levels = (JsonObject) level.getWorld().getJson().get("levels");
        if (levels != null) {
            l = levels.get(Integer.toString(level.getIndex())).getAsJsonObject();
            chunks = l.get("chunks").getAsJsonObject();
        }
        for (Chunk chunk : level.chunks.values()) {
            chunks.add(Integer.toString(chunk.getIndex()), saveChunk(chunk));
        }
        l.add("chunks", chunks);
        return l;
    }

    /**
     * Saves a chunk to its world's {@link JsonObject JSON object}
     * @param chunk the chunk to save.
     */
    public static void saveChunkToMemory(Chunk chunk) {
        JsonObject world = chunk.getLevel().getWorld().getJson();
        JsonObject levels = world.get("levels").getAsJsonObject();
        if (levels == null)
            levels = new JsonObject();
        JsonObject level = levels.get(Integer.toString(chunk.getLevel().getIndex())).getAsJsonObject();
        if (level == null)
            level = new JsonObject();
        JsonObject chunks = level.get("chunks").getAsJsonObject();
        if (chunks == null)
            chunks = new JsonObject();
        JsonObject jChunk = saveChunk(chunk);
        chunks.add(Integer.toString(chunk.getIndex()), jChunk);
        level.add("chunks", chunks);
        levels.add(Integer.toString(chunk.getLevel().getIndex()), level);
        world.add("levels", levels);
    }

    /**
     * Saves a level to its world's {@link JsonObject JSON object}
     * @param level the chunk to save.
     */
    public static void saveLevelToMemory(Level level) {
        JsonObject world = level.getWorld().getJson();
        JsonObject levels = (JsonObject) world.get("levels");
        if (levels == null)
            levels = new JsonObject();
        JsonObject jLevel = saveLevel(level);
        levels.add(Integer.toString(level.getIndex()), jLevel);
        world.add("levels", levels);
    }

    // this saves me a bit of casting and makes the code look nicer
    private static int longToInt(long number) {
        return (int) number;
    }

    public static void prepareWorld() {
        VboUtil.updateArray();
        VboUtil.prepareBindArray();

        Thread chunkLoader = new Thread(new Runnable() {
            public void run() {
                while (true) {
                    Chunk.handleChunkLoading();
                    try {
                        Thread.sleep(Chunk.LOAD_CHECK_INTERVAL);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    if (Main.closed)
                        return;
                }
            }
        });
        chunkLoader.start();
    }

}