/**
* BetonQuest - advanced quests for Bukkit
* Copyright (C) 2016 Jakub "Co0sh" Sapalski
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package pl.betoncraft.betonquest;
import java.util.HashMap;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import pl.betoncraft.betonquest.config.Config;
import pl.betoncraft.betonquest.config.ConfigPackage;
import pl.betoncraft.betonquest.config.QuestCanceler;
import pl.betoncraft.betonquest.database.PlayerData;
import pl.betoncraft.betonquest.item.QuestItem;
import pl.betoncraft.betonquest.utils.Debug;
import pl.betoncraft.betonquest.utils.PlayerConverter;
import pl.betoncraft.betonquest.utils.Utils;
/**
* Represents a chest GUI for the backpack displayed to the player.
*
* @author Jakub Sapalski
*/
public class Backpack implements Listener {
/**
* ID of the player
*/
private final String playerID;
/**
* The player object
*/
private final Player player;
/**
* Instance of the BetonQuest plugin
*/
private final BetonQuest instance;
/**
* Database handler for the player
*/
private final PlayerData playerData;
/**
* The inventory created by this object
*/
private Inventory inv;
/**
* Currently displayed page
*/
private Display display;
/**
* Language of the player
*/
private String lang;
private Backpack backpack;
public enum DisplayType {
DEFAULT, CANCEL, COMPASS
}
/**
* Creates new backpack GUI opened at given page type.
*
* @param playerID
* ID of the player
* @param type
* type of the display
*/
public Backpack(String playerID, DisplayType type) {
// fill required fields
this.playerID = playerID;
lang = BetonQuest.getInstance().getPlayerData(playerID).getLanguage();
player = PlayerConverter.getPlayer(playerID);
instance = BetonQuest.getInstance();
playerData = instance.getPlayerData(playerID);
backpack = this;
// create display
switch (type) {
case DEFAULT:
display = new Page(0);
break;
case CANCEL:
display = new Cancelers();
break;
case COMPASS:
display = new Compass();
break;
}
}
/**
* Creates new backpack GUI.
*
* @param playerID
* ID of the player
*/
public Backpack(String playerID) {
this(playerID, DisplayType.DEFAULT);
}
@EventHandler
public void onClick(InventoryClickEvent event) {
if (event.getWhoClicked().equals(player)) {
// if the player clicked, then cancel this event
event.setCancelled(true);
// if the click was outside of the inventory, do nothing
if (event.getRawSlot() < 0) {
return;
}
// pass the click to the Display
display.click(event.getRawSlot(), event.getSlot(), event.getClick());
}
}
@EventHandler
public void onInventoryClosing(InventoryCloseEvent event) {
if (event.getPlayer().equals(player)) {
HandlerList.unregisterAll(this);
}
}
/**
* Represents a display that can be shown as the backpack.
*
* @author Jakub Sapalski
*/
private abstract class Display {
abstract void click(int slot, int playerSlot, ClickType click);
}
/**
* Standard page with quest items.
*
* @author Jakub Sapalski
*/
private class Page extends Display {
private final int page;
/**
* Creates and displays to the player a given page.
*
* @param page
* number of the page to display, starting from 0
*/
public Page(int page) {
this.page = page;
List<ItemStack> backpackItems = playerData.getBackpack();
// amount of pages, considering that the first contains 44
// items and all others 45
int pages = (backpackItems.size() < 45 ? 1
: (backpackItems.size() + 1 % 45 == 0 ? (int) (backpackItems.size() + 1) / 45
: (int) Math.floor((backpackItems.size() + 1) / 45) + 1));
// prepare the inventory
inv = Bukkit.createInventory(null, 54, Config.getMessage(lang, "backpack_title")
+ (pages == 1 ? "" : " (" + (page + 1) + "/" + pages + ")"));
ItemStack[] content = new ItemStack[54];
int i = 0;
// insert the journal if the player doesn't have it in his inventory
if (page == 0) {
if (!Journal.hasJournal(playerID)) {
content[0] = playerData.getJournal().getAsItem();
} else {
content[0] = null;
}
i++;
} else {
}
// set all the items
while (i < 45 && i + (page * 45) <= backpackItems.size()) {
ItemStack item = backpackItems.get(i + (page * 45) - 1);
content[i] = item;
i++;
}
// if there are other pages, place the buttons
if (page > 0) {
ItemStack previous = null;
try {
previous = new QuestItem(new ItemID(Config.getPackages().get("default"), "previous_button")).generate(1);
} catch (ObjectNotFoundException e) {
previous = new ItemStack(Material.GLOWSTONE_DUST);
} catch (InstructionParseException e) {
Debug.error("Could not load previous button: " + e.getMessage());
player.closeInventory();
return;
}
ItemMeta meta = previous.getItemMeta();
meta.setDisplayName(Config.getMessage(lang, "previous").replaceAll("&", "§"));
previous.setItemMeta(meta);
content[48] = previous;
}
if (backpackItems.size() > (page + 1) * 45 - 1) {
ItemStack next;
try {
next = new QuestItem(new ItemID(Config.getPackages().get("default"), "next_button")).generate(1);
} catch (ObjectNotFoundException e) {
next = new ItemStack(Material.REDSTONE);
} catch (InstructionParseException e) {
Debug.error("Could not load next button: " + e.getMessage());
player.closeInventory();
return;
}
ItemMeta meta = next.getItemMeta();
meta.setDisplayName(Config.getMessage(lang, "next").replaceAll("&", "§"));
next.setItemMeta(meta);
content[50] = next;
}
// set "cancel quest" button
ItemStack cancel;
try {
cancel = new QuestItem(new ItemID(Config.getPackages().get("default"), "cancel_button")).generate(1);
} catch (ObjectNotFoundException e) {
cancel = new ItemStack(Material.BONE);
} catch (InstructionParseException e) {
Debug.error("Could not load cancel button: " + e.getMessage());
player.closeInventory();
return;
}
ItemMeta meta = cancel.getItemMeta();
meta.setDisplayName(Config.getMessage(lang, "cancel").replaceAll("&", "§"));
cancel.setItemMeta(meta);
content[45] = cancel;
// set "compass targets" button
ItemStack compassItem;
try {
compassItem = new QuestItem(new ItemID(Config.getPackages().get("default"), "compass_button")).generate(1);
} catch (ObjectNotFoundException e) {
compassItem = new ItemStack(Material.COMPASS);
} catch (InstructionParseException e) {
Debug.error("Could not load compass button: " + e.getMessage());
player.closeInventory();
return;
}
ItemMeta compassMeta = compassItem.getItemMeta();
compassMeta.setDisplayName(Config.getMessage(lang, "compass").replace('&', '&'));
compassItem.setItemMeta(compassMeta);
content[46] = compassItem;
// set the inventory and display it
inv.setContents(content);
player.openInventory(inv);
Bukkit.getPluginManager().registerEvents(backpack, instance);
}
@Override
void click(int slot, int playerSlot, ClickType click) {
if (page == 0 && slot == 0) {
// first page on first slot should contain the journal
playerData.getJournal().addToInv(Integer.parseInt(Config.getString("config.default_journal_slot")));
display = new Page(page);
} else if (slot < 45) {
// raw slot lower than 45 is a quest item
// read the id of the item from clicked slot
int id = page * 45 + slot - 1;
ItemStack item = null;
// get the item if it exists
if (playerData.getBackpack().size() > id) {
item = playerData.getBackpack().get(id);
}
if (item != null) {
// if the item exists, put it in player's inventory
int backpackAmount = item.getAmount();
int getAmount = 0;
// left click is one item, right is the whole stack
switch (click) {
case LEFT:
getAmount = 1;
break;
case RIGHT:
getAmount = backpackAmount;
break;
default:
break;
}
if (getAmount != 0) {
// add desired amount of items to player's inventory
ItemStack newItem = item.clone();
newItem.setAmount(getAmount);
ItemStack leftItems = player.getInventory().addItem(newItem).get(0);
// remove from backpack only those items that were
// actually added to player's inventory
int leftAmount = 0;
if (leftItems != null) {
leftAmount = leftItems.getAmount();
}
item.setAmount(backpackAmount - getAmount + leftAmount);
if (backpackAmount - getAmount + leftAmount == 0) {
List<ItemStack> backpackItems = playerData.getBackpack();
backpackItems.remove(id);
playerData.setBackpack(backpackItems);
}
}
display = new Page(page);
}
} else if (slot > 53) {
// slot above 53 is player's inventory, so handle item storing
ItemStack item = player.getInventory().getItem(playerSlot);
if (item != null) {
// if the item exists continue
if (Utils.isQuestItem(item)) {
// if it is a quest item, add it to the backpack
int amount = 0;
// left click is one item, right is all items
switch (click) {
case LEFT:
amount = 1;
break;
case RIGHT:
amount = item.getAmount();
break;
default:
break;
}
// add item to backpack and remove it from player's
// inventory
playerData.addItem(item.clone(), amount);
if (item.getAmount() - amount == 0) {
player.getInventory().setItem(playerSlot, null);
} else {
item.setAmount(item.getAmount() - amount);
player.getInventory().setItem(playerSlot, item);
}
} else if (Journal.isJournal(playerID, item)) {
// if it's a journal, remove it so it appears in
// backpack again
playerData.getJournal().removeFromInv();
}
display = new Page(page);
}
} else if (slot == 48 && page > 0) {
// if it was a previous/next button turn the pages
display = new Page(page - 1);
} else if (slot == 50 && playerData.getBackpack().size() > (page + 1) * 45 - 1) {
display = new Page(page + 1);
} else if (slot == 45) {
// slot 45 is a slot with quest cancelers
display = new Cancelers();
} else if (slot == 46) {
// slot 46 is a slot with compass pointers
display = new Compass();
}
}
}
/**
* The page with quest cancelers.
*
* @author Jakub Sapalski
*/
private class Cancelers extends Display {
private HashMap<Integer, QuestCanceler> map = new HashMap<>();
/**
* Creates a page with quest cancelers and displays it to the player.
*/
public Cancelers() {
HashMap<String, QuestCanceler> cancelers = new HashMap<>();
// get all quest cancelers that can be shown to the player
for (String name : Config.getCancelers().keySet()) {
QuestCanceler canceler = Config.getCancelers().get(name);
if (canceler.show(playerID))
cancelers.put(name, canceler);
}
// generate the inventory view
int size = cancelers.size();
int numberOfRows = ((size - size % 9) / 9) + 1;
if (numberOfRows > 6) {
numberOfRows = 6;
Debug.error("Player " + player.getName() + " has too many active quests, please"
+ " don't allow for so many of them. It slows down your server!");
}
inv = Bukkit.createInventory(null, numberOfRows * 9, Config.getMessage(lang, "cancel_page"));
ItemStack[] content = new ItemStack[numberOfRows * 9];
int i = 0;
for (String name : cancelers.keySet()) {
QuestCanceler canceler = cancelers.get(name);
content[i] = canceler.getItem(playerID);
map.put(i, canceler);
i++;
}
inv.setContents(content);
player.openInventory(inv);
Bukkit.getPluginManager().registerEvents(backpack, instance);
}
@Override
void click(int slot, int playerSlot, ClickType click) {
QuestCanceler cancel = map.get(slot);
if (cancel == null) {
return;
}
// cancel the chosen quests
cancel.cancel(playerID);
player.closeInventory();
}
}
private class Compass extends Display {
private HashMap<Integer, Location> locations = new HashMap<>();
private HashMap<Integer, String> names = new HashMap<>();
private HashMap<Integer, String> items = new HashMap<>();
public Compass() {
Integer counter = 0;
// for every package
for (ConfigPackage pack : Config.getPackages().values()) {
String packName = pack.getName();
// loop all compass locations
ConfigurationSection s = pack.getMain().getConfig().getConfigurationSection("compass");
if (s != null) {
for (String key : s.getKeys(false)) {
String location = pack.getString("main.compass." + key + ".location");
String itemName = pack.getString("main.compass." + key + ".item");
String name = null;
if (s.isConfigurationSection(key + ".name")) {
name = pack.getString("main.compass." + key + ".name." + lang);
if (name == null)
name = pack.getString("main.compass." + key + ".name." + Config.getLanguage());
if (name == null)
name = pack.getString("main.compass." + key + ".name.en");
} else {
name = pack.getString("main.compass." + key + ".name");
}
if (name == null) {
Debug.error("Name not defined in a compass pointer in " + packName + " package: " + key);
continue;
}
if (location == null) {
Debug.error(
"Location not defined in a compass pointer in " + packName + " package: " + key);
continue;
}
// check if the player has special compass tag
if (!playerData.hasTag(packName + ".compass-" + key)) {
continue;
}
// if the tag is present, continue
String[] parts = location.split(";");
if (parts.length != 4) {
Debug.error("Could not parse location in a compass pointer in " + packName + " package: "
+ key);
continue;
}
World world = Bukkit.getWorld(parts[3]);
if (world == null) {
Debug.error(
"World does not exist in a compass pointer in " + packName + " package: " + key);
}
int x, y, z;
try {
x = Integer.parseInt(parts[0]);
y = Integer.parseInt(parts[1]);
z = Integer.parseInt(parts[2]);
} catch (NumberFormatException e) {
Debug.error("Could not parse location coordinates in a compass pointer in " + packName
+ " package: " + key);
player.closeInventory();
return;
}
Location loc = new Location(world, x, y, z);
// put location with next number
locations.put(counter, loc);
names.put(counter, name);
if (itemName != null)
items.put(counter, packName + ".items." + itemName);
counter++;
}
}
}
// solve number of needed rows
int size = locations.size();
int numberOfRows = ((size - size % 9) / 9) + 1;
if (numberOfRows > 6) {
numberOfRows = 6;
Debug.error("Player " + player.getName() + " has too many compass pointers, please"
+ " don't allow for so many of them. It slows down your server!");
player.closeInventory();
return;
}
inv = Bukkit.createInventory(null, numberOfRows * 9, Config.getMessage(lang, "compass_page"));
ItemStack[] content = new ItemStack[numberOfRows * 9];
int i = 0;
for (Integer slot : locations.keySet()) {
String name = names.get(slot);
String item = items.get(slot);
ItemStack compass = null;
try {
compass = new QuestItem(new ItemID(Config.getPackages().get("default"), item)).generate(1);
} catch (InstructionParseException e) {
Debug.error("Could not load compass button: " + e.getMessage());
player.closeInventory();
return;
} catch (NullPointerException e) {
if (e.getMessage().equals("Item instruction is null")) {
player.closeInventory();
return;
} else {
e.printStackTrace();
return;
}
} catch (ObjectNotFoundException e) {
compass = new ItemStack(Material.COMPASS);
}
ItemMeta meta = compass.getItemMeta();
meta.setDisplayName(name.replace("_", " ").replace("&", "§"));
compass.setItemMeta(meta);
content[i] = compass;
i++;
}
inv.setContents(content);
player.openInventory(inv);
Bukkit.getPluginManager().registerEvents(backpack, instance);
}
@Override
void click(int slot, int layerSlot, ClickType click) {
Location loc = locations.get(slot);
if (loc == null) {
return;
}
// set the location of the compass
player.setCompassTarget(loc);
player.closeInventory();
}
}
}