fr.zcraft.MultipleInventories.snaphots.PlayerSnapshot.java Source code

Java tutorial

Introduction

Here is the source code for fr.zcraft.MultipleInventories.snaphots.PlayerSnapshot.java

Source

/*
 * Copyright or  or Copr. AmauryCarrade (2015)
 * 
 * http://amaury.carrade.eu
 * 
 * This software is governed by the CeCILL-B license under French law and
 * abiding by the rules of distribution of free software.  You can  use, 
 * modify and/ or redistribute the software under the terms of the CeCILL-B
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info". 
 * 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability. 
 * 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or 
 * data to be ensured and,  more generally, to use and operate it in the 
 * same conditions as regards security. 
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-B license and that you accept its terms.
 */
package fr.zcraft.MultipleInventories.snaphots;

import com.google.common.collect.Streams;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import fr.zcraft.MultipleInventories.MultipleInventories;
import fr.zcraft.zlib.tools.PluginLogger;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * A snapshot of a player state (inventories, experience, health, hunger,
 * saturation, potion effects). The snapshot is frozen in time, and cannot be
 * modified.
 */
public class PlayerSnapshot {
    private final int level;
    private final float exp;
    private final int expTotal;

    private final int foodLevel;
    private final float exhaustion;
    private final float saturation;

    private final double health;
    private final double maxHealth;

    private final Map<Integer, ItemStackSnapshot> inventory;
    private final Map<Integer, ItemStackSnapshot> enderChest;

    private final ItemStackSnapshot[] armor;

    private final Collection<PotionEffect> effects;

    /**
     * Creates a snapshot of a player using the given data. You should use
     * {@link #snap(Player)} instead.
     *
     * @param level      The XP level.
     * @param exp        The XP.
     * @param expTotal   The total XP. -1 if unknown (e.g. from import).
     * @param foodLevel  The food level.
     * @param exhaustion The player's exhaustion.
     * @param saturation The player's saturation.
     * @param health     The player's health.
     * @param maxHealth  The player's maximal health.
     * @param inventory  The player's inventory snapshot.
     * @param enderChest The player's ender chest snapshot.
     * @param armor      The player's armor snapshot.
     * @param effects    The player's potion effects.
     *
     * @see #snap(Player) Easier method to create a snapshot that you should
     * use.
     */
    public PlayerSnapshot(int level, float exp, int expTotal, int foodLevel, float exhaustion, float saturation,
            double health, double maxHealth, Map<Integer, ItemStackSnapshot> inventory,
            Map<Integer, ItemStackSnapshot> enderChest, ItemStackSnapshot[] armor,
            Collection<PotionEffect> effects) {
        this.level = level;
        this.exp = exp;
        this.expTotal = expTotal;
        this.foodLevel = foodLevel;
        this.exhaustion = exhaustion;
        this.saturation = saturation;
        this.health = health;
        this.maxHealth = maxHealth;
        this.inventory = inventory;
        this.enderChest = enderChest;
        this.armor = armor;
        this.effects = effects;
    }

    /**
     * Creates a snapshot of a player.
     *
     * @param player The player to snap.
     *
     * @return The snapshot, or {@code null} if the player was {@code null}.
     * @see #snap(Player, boolean) Snapshot while respawning.
     */
    public static PlayerSnapshot snap(final Player player) {
        return snap(player, false);
    }

    /**
     * Creates a snapshot of a player.
     *
     * @param player      The player to snap.
     * @param fromRespawn {@code true} if the snapshot was taken after a
     *                    respawn. It prevents storing empty health & hunger.
     *
     * @return The snapshot, or {@code null} if the player was {@code null}.
     */
    public static PlayerSnapshot snap(final Player player, boolean fromRespawn) {
        if (player == null)
            return null;

        final ItemStackSnapshot[] snapArmor = Arrays.stream(player.getInventory().getArmorContents())
                .map(ItemStackSnapshot::snap).toArray(ItemStackSnapshot[]::new);

        return new PlayerSnapshot(player.getLevel(), player.getExp(), player.getTotalExperience(),
                !fromRespawn ? player.getFoodLevel() : 20, !fromRespawn ? player.getExhaustion() : 0,
                !fromRespawn ? player.getSaturation() : 5,
                !fromRespawn ? player.getHealth() : player.getMaxHealth(), player.getMaxHealth(),
                snapInventory(player.getInventory()), snapInventory(player.getEnderChest()), snapArmor,
                player.getActivePotionEffects());
    }

    /**
     * Creates a snapshot of an inventory.
     *
     * @param inventory The inventory.
     *
     * @return The snapshot.
     */
    private static Map<Integer, ItemStackSnapshot> snapInventory(final Inventory inventory) {
        final Map<Integer, ItemStackSnapshot> snap = new HashMap<>();
        final ListIterator<ItemStack> iterator = inventory.iterator();

        while (iterator.hasNext()) {
            final int index = iterator.nextIndex();
            final ItemStack item = iterator.next();

            if (item != null && item.getType() != Material.AIR)
                snap.put(index, ItemStackSnapshot.snap(item));
        }

        return snap;
    }

    /**
     * Applies the snapshot on the given player.
     *
     * This will restore all the player properties, and clear then reload his
     * inventory as it was when the snapshot was taken.
     *
     * @param player The player.
     */
    public void reconstruct(final Player player) {
        player.setLevel(level);
        player.setExp(exp);
        if (expTotal >= 0)
            player.setTotalExperience(expTotal);
        player.setFoodLevel(foodLevel);
        player.setExhaustion(exhaustion);
        player.setSaturation(saturation);
        player.setHealth(health);
        player.setMaxHealth(maxHealth);

        reconstructInventory(player.getInventory(), inventory);
        reconstructInventory(player.getEnderChest(), enderChest);

        final ItemStack[] newArmor = new ItemStack[player.getInventory().getArmorContents().length];
        for (int i = 0; i < armor.length; i++) {
            newArmor[i] = armor[i] != null ? armor[i].reconstruct() : null;
        }

        player.getInventory().setArmorContents(newArmor);

        player.getActivePotionEffects().stream().map(PotionEffect::getType).forEach(player::removePotionEffect);

        effects.forEach(effect -> effect.apply(player));
    }

    /**
     * Clears then reconstructs from the snapshot the given inventory.
     *
     * @param inventory The inventory to reconstruct.
     * @param snapshot  The snapshot to apply.
     */
    private void reconstructInventory(final Inventory inventory, final Map<Integer, ItemStackSnapshot> snapshot) {
        inventory.clear();
        snapshot.forEach((index, itemSnapshot) -> inventory.setItem(index, itemSnapshot.reconstruct()));
    }

    /**
     * @return A JSON export of this snapshot (including inventories and {@link
     * ItemStackSnapshot item snapshots}.
     */
    @Override
    public String toString() {
        return toJSONString();
    }

    /**
     * @return A JSON export of this snapshot (including inventories and {@link
     * ItemStackSnapshot item snapshots}.
     */
    public JsonElement toJSON() {
        final JsonObject dump = new JsonObject();
        final JsonArray armorDump = new JsonArray();
        final JsonArray effectsDump = new JsonArray();

        Arrays.stream(armor).map(item -> item != null ? item.toJSON() : JsonNull.INSTANCE)
                .forEachOrdered(armorDump::add);

        effects.stream().map(this::toJSON).forEach(effectsDump::add);

        dump.addProperty("level", level);
        dump.addProperty("exp", exp);
        dump.addProperty("expTotal", expTotal);
        dump.addProperty("foodLevel", foodLevel);
        dump.addProperty("exhaustion", exhaustion);
        dump.addProperty("saturation", saturation);
        dump.addProperty("health", health);
        dump.addProperty("maxHealth", maxHealth);

        dump.add("armor", armorDump);
        dump.add("inventory", toJSON(inventory));
        dump.add("enderChest", toJSON(enderChest));
        dump.add("effects", effectsDump);

        return dump;
    }

    /**
     * @return A JSON export of an inventory.
     */
    private JsonElement toJSON(final Map<Integer, ItemStackSnapshot> inventory) {
        final JsonObject dump = new JsonObject();

        inventory.forEach((index, itemSnapshot) -> dump.add(index.toString(),
                itemSnapshot != null ? itemSnapshot.toJSON() : JsonNull.INSTANCE));

        return dump;
    }

    /**
     * @return A JSON export of the given potion effect.
     */
    private JsonElement toJSON(final PotionEffect effect) {
        final JsonObject dump = new JsonObject();

        dump.addProperty("type", effect.getType().getName());
        dump.addProperty("duration", effect.getDuration());
        dump.addProperty("amplifier", effect.getAmplifier());
        dump.addProperty("ambient", effect.isAmbient());
        dump.addProperty("has-particles", effect.hasParticles());
        dump.addProperty("color", effect.getColor() != null ? effect.getColor().asRGB() : null);

        return dump;
    }

    /**
     * @return A JSON export of this snapshot (including inventories and {@link
     * ItemStackSnapshot item snapshots}.
     */
    public String toJSONString() {
        return MultipleInventories.GSON.toJson(toJSON());
    }

    /**
     * Constructs a snapshot from a JSON export (including {@link
     * ItemStackSnapshot item snapshots} in the inventories).
     *
     * @param json The JSON export.
     *
     * @return The snapshot.
     */
    public static PlayerSnapshot fromJSONString(final String json) {
        return fromJSON((new JsonParser().parse(json)).getAsJsonObject());
    }

    /**
     * Constructs a snapshot from a JSON export (including {@link
     * ItemStackSnapshot item snapshots} in the inventories).
     *
     * @param json The JSON export.
     *
     * @return The snapshot.
     */
    public static PlayerSnapshot fromJSON(final JsonObject json) {
        final JsonArray jsonArmor = json.getAsJsonArray("armor");
        final ItemStackSnapshot[] armor = new ItemStackSnapshot[jsonArmor.size()];

        for (int i = 0; i < jsonArmor.size(); i++) {
            final JsonElement armorItem = jsonArmor.get(i);
            armor[i] = armorItem.isJsonObject() ? ItemStackSnapshot.fromJSON(armorItem.getAsJsonObject()) : null;
        }

        final JsonArray jsonEffects = json.get("effects").isJsonArray() ? json.getAsJsonArray("effects")
                : new JsonArray();
        final List<PotionEffect> effects = Streams.stream(jsonEffects)
                .filter(jsonEffect -> jsonEffect != null && jsonEffect.isJsonObject())
                .map(jsonEffect -> potionEffectFromJSON(jsonEffect.getAsJsonObject())).collect(Collectors.toList());

        return new PlayerSnapshot(isNull(json, "level") ? 0 : json.getAsJsonPrimitive("level").getAsInt(),
                isNull(json, "exp") ? 0.0f : json.getAsJsonPrimitive("exp").getAsFloat(),
                isNull(json, "expTotal") ? 0 : json.getAsJsonPrimitive("expTotal").getAsInt(),
                isNull(json, "foodLevel") ? 20 : json.getAsJsonPrimitive("foodLevel").getAsInt(),
                isNull(json, "exhaustion") ? 0f : json.getAsJsonPrimitive("exhaustion").getAsFloat(),
                isNull(json, "saturation") ? 5f : json.getAsJsonPrimitive("saturation").getAsFloat(),
                isNull(json, "health") ? 20.0 : json.getAsJsonPrimitive("health").getAsDouble(),
                isNull(json, "maxHealth") ? 20.0 : json.getAsJsonPrimitive("maxHealth").getAsDouble(),
                inventoryFromJSON(json.getAsJsonObject("inventory")),
                inventoryFromJSON(json.getAsJsonObject("enderChest")), armor, effects);
    }

    /**
     * Imports an inventory snapshot from a JSON export.
     *
     * @param json The JSON export.
     *
     * @return The inventory snapshot.
     */
    private static Map<Integer, ItemStackSnapshot> inventoryFromJSON(final JsonObject json) {
        final Map<Integer, ItemStackSnapshot> snapshot = new HashMap<>();

        json.entrySet().forEach(jsonItemEntry -> {
            if (!jsonItemEntry.getValue().isJsonObject())
                return;

            try {
                snapshot.put(Integer.parseInt(jsonItemEntry.getKey()),
                        ItemStackSnapshot.fromJSON(jsonItemEntry.getValue().getAsJsonObject()));
            } catch (NumberFormatException e) {
                PluginLogger.error("Skipping item with invalid index {0} from JSON snapshot",
                        jsonItemEntry.getKey());
            }
        });

        return snapshot;
    }

    private static PotionEffect potionEffectFromJSON(final JsonObject json) {
        final JsonPrimitive color = json.get("color").isJsonNull() ? null : json.getAsJsonPrimitive("color");

        return new PotionEffect(PotionEffectType.getByName(json.getAsJsonPrimitive("type").getAsString()),
                isNull(json, "duration") ? 1 : json.getAsJsonPrimitive("duration").getAsInt(),
                isNull(json, "amplifier") ? 1 : json.getAsJsonPrimitive("amplifier").getAsInt(),
                !isNull(json, "ambient") && json.getAsJsonPrimitive("ambient").getAsBoolean(),
                isNull(json, "has-particles") || json.getAsJsonPrimitive("has-particles").getAsBoolean(),
                color != null && !color.isJsonNull() ? Color.fromRGB(color.getAsInt()) : null);
    }

    private static boolean isNull(final JsonObject element, final String child) {
        final JsonElement childElement = element.get(child);
        return childElement == null || childElement.isJsonNull();
    }
}