/**
* 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.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import com.google.common.collect.Lists;
import pl.betoncraft.betonquest.config.Config;
import pl.betoncraft.betonquest.config.ConfigPackage;
import pl.betoncraft.betonquest.database.Connector.UpdateType;
import pl.betoncraft.betonquest.database.Saver.Record;
import pl.betoncraft.betonquest.utils.Debug;
import pl.betoncraft.betonquest.utils.PlayerConverter;
import pl.betoncraft.betonquest.utils.Utils;
/**
* Represents player's journal.
*
* @author Jakub Sapalski
*/
public class Journal {
private String playerID;
private List<Pointer> pointers;
private List<String> texts = new ArrayList<>();
private String lang;
private String mainPage;
/**
* Creates new Journal instance from List of Pointers.
*
* @param playerID
* ID of the player whose journal is created
* @param list
* list of pointers to journal entries
* @param lang
* default language to use when generating the journal
*/
public Journal(String playerID, String lang, List<Pointer> list) {
// generate texts from list of pointers
this.playerID = playerID;
this.lang = lang;
pointers = list;
}
/**
* Retrieves the list of pointers in this journal.
*
* @return this Journal's list of pointers to journal entries
*/
public List<Pointer> getPointers() {
return pointers;
}
/**
* Adds pointer to the journal. It needs to be updated now.
*
* @param pointer
* the pointer to be added
*/
public void addPointer(Pointer pointer) {
pointers.add(pointer);
// SQLite doesn't accept formatted date and MySQL doesn't accept numeric
// timestamp
String date = (BetonQuest.getInstance().isMySQLUsed())
? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(pointer.getTimestamp()))
: Long.toString(pointer.getTimestamp());
BetonQuest.getInstance().getSaver()
.add(new Record(UpdateType.ADD_JOURNAL, new String[] { playerID, pointer.getPointer(), date }));
}
/**
* Removes the pointer from journal. It needs to be updated now.
*
* @param pointerName
* the name of the pointer to remove
*/
public void removePointer(String pointerName) {
for (Iterator<Pointer> iterator = pointers.iterator(); iterator.hasNext();) {
Pointer pointer = (Pointer) iterator.next();
if (pointer.getPointer().equalsIgnoreCase(pointerName)) {
String date = (BetonQuest.getInstance().isMySQLUsed())
? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(pointer.getTimestamp()))
: Long.toString(pointer.getTimestamp());
BetonQuest.getInstance().getSaver()
.add(new Record(UpdateType.REMOVE_JOURNAL, new String[] { playerID, pointer.getPointer(), date }));
iterator.remove();
break;
}
}
}
/**
* Retrieves the list of generated texts.
*
* @return list of Strings - texts for every journal entry
*/
public List<String> getText() {
List<String> list;
if (Config.getString("config.journal.reversed_order").equalsIgnoreCase("true")) {
list = Lists.reverse(texts);
} else {
list = new ArrayList<>(texts);
}
return list;
}
/**
* Generates texts for every pointer and places them inside a List
*
* @param lang the language to use while generating text
*/
public void generateTexts(String lang) {
// remove previous texts
texts.clear();
this.lang = lang;
// generate the first page
mainPage = generateMainPage();
for (Pointer pointer : pointers) {
// if date should not be hidden, generate the date prefix
String datePrefix = "";
if (Config.getString("config.journal.hide_date").equalsIgnoreCase("false")) {
String date = new SimpleDateFormat(Config.getString("config.date_format"))
.format(pointer.getTimestamp());
String[] dateParts = date.split(" ");
String day = "§" + Config.getString("config.journal_colors.date.day") + dateParts[0];
String hour = "";
if (dateParts.length > 1) {
hour = "§" + Config.getString("config.journal_colors.date.hour") + dateParts[1];
}
datePrefix = day + " " + hour;
}
// get package and name of the pointer
String[] parts = pointer.getPointer().split("\\.");
String packName = parts[0];
ConfigPackage pack = Config.getPackages().get(packName);
if (pack == null) {
continue;
}
String pointerName = parts[1];
// resolve the text in player's language
String text;
if (pack.getJournal().getConfig().isConfigurationSection(pointerName)) {
text = pack.getString("journal." + pointerName + "." + lang);
if (text == null) {
text = pack.getString("journal." + pointerName + "." + Config.getLanguage());
}
} else {
text = pack.getString("journal." + pointerName);
}
// handle case when the text isn't defined
if (text == null) {
Debug.error("No text defined for journal entry " + pointerName + " in language " + lang);
text = "error";
}
// add the entry to the list
texts.add(datePrefix + "§" + Config.getString("config.journal_colors.text") + "\n"
+ text.replaceAll("&", "§"));
}
}
/**
* Generates the main page for this journal.
*
* @return the main page string or null, if there is no main page
*/
private String generateMainPage() {
HashMap<Integer, String> lines = new HashMap<>(); // holds text lines with their priority
ArrayList<Integer> numbers = new ArrayList<>(); // stores numbers that are used, so there's no need to search them
for (ConfigPackage pack : Config.getPackages().values()) {
String packName = pack.getName();
ConfigurationSection s = pack.getMain().getConfig().getConfigurationSection("journal_main_page");
if (s == null)
continue;
// handle every entry
keys: for (String key : s.getKeys(false)) {
int i = s.getInt(key + ".priority", -1);
// only add entry if the priority is set and not doubled
if (i >= 0 && !numbers.contains(i)) {
// check conditions and continue loop if not met
String rawConditions = s.getString(key + ".conditions");
if (rawConditions != null && rawConditions.length() > 0) {
for (String condition : rawConditions.split(",")) {
try {
ConditionID conditionID = new ConditionID(pack, condition);
if (!BetonQuest.condition(playerID, conditionID)) {
continue keys;
}
} catch (ObjectNotFoundException e) {
Debug.error("Error while generatin main page in " + PlayerConverter.getPlayer(playerID)
+ "'s journal - condition '" + condition + "' not found: " + e.getMessage());
continue keys;
}
}
}
// here conditions are met, get the text in player's language
String text;
if (s.isConfigurationSection(key + ".text")) {
text = s.getString(key + ".text." + lang);
if (text == null)
text = s.getString(key + ".text." + Config.getLanguage());
if (text == null)
text = s.getString(key + ".text.en");
} else {
text = s.getString(key + ".text");
}
if (text == null || text.length() <= 0) {
continue;
}
// resolve variables
for (String variable : BetonQuest.resolveVariables(text)) {
try {
BetonQuest.createVariable(pack, variable);
} catch (InstructionParseException e) {
Debug.error("Error while creating variable '" + variable + "' on main page in "
+ PlayerConverter.getName(playerID) + "'s journal: " + e.getMessage());
}
text = text.replace(variable,
BetonQuest.getInstance().getVariableValue(packName, variable, playerID));
}
// add the text to HashMap
numbers.add(i);
lines.put(i, text + "§r"); // reset the formatting
} else {
Debug.error("Priority of " + packName + "." + key
+ " journal main page line is not defined or doubled");
continue;
}
}
}
if (numbers.isEmpty())
return null;
// now all lines from all packages are extracted, sort numbers
Integer[] sorted = new Integer[numbers.size()];
sorted = numbers.toArray(sorted);
Arrays.sort(sorted);
// build the string and return it
ArrayList<String> sortedLines = new ArrayList<>();
for (int i : sorted) {
sortedLines.add(lines.get(i));
}
String finalLine = StringUtils.join(sortedLines, '\n').replace('&', '§');
return finalLine;
}
/**
* Clears the Journal completely but doesn't touch the database.
*/
public void clear() {
texts.clear();
pointers.clear();
}
/**
* Adds journal to player inventory.
*
* @param slot
* slot number for adding the journal
*/
public void addToInv(int slot) {
// remove the old journal if it exists
if (hasJournal(playerID)) {
slot = removeFromInv();
}
// update the texts
generateTexts(lang);
Inventory inventory = PlayerConverter.getPlayer(playerID).getInventory();
// if the slot is less than 0 then use default slot
if (slot < 0) {
slot = 8;
}
// generate journal and place it in the slot
ItemStack item = getAsItem();
if (inventory.firstEmpty() >= 0) {
ItemStack oldItem = inventory.getItem(slot);
inventory.setItem(slot, item);
// move the item that was previously there
if (oldItem != null) {
inventory.addItem(oldItem);
}
} else {
// if there is no place for the item then print a message about it
Config.sendMessage(playerID, "inventory_full", null, "full");
}
}
/**
* Generates the journal as ItemStack
*
* @return the journal ItemStack
*/
public ItemStack getAsItem() {
// create the book with default title/author
ItemStack item = new ItemStack(Material.WRITTEN_BOOK);
BookMeta meta = (BookMeta) item.getItemMeta();
meta.setTitle(Config.getMessage(lang, "journal_title").replaceAll("&", "§"));
meta.setAuthor(PlayerConverter.getPlayer(playerID).getName());
List<String> lore = new ArrayList<String>();
lore.add(Config.getMessage(lang, "journal_lore").replaceAll("&", "§"));
meta.setLore(lore);
// add main page and generate pages from texts
List<String> finalList = new ArrayList<>();
if (Config.getString("config.journal.one_entry_per_page").equalsIgnoreCase("false")) {
String color = Config.getString("config.journal_colors.line");
StringBuilder stringBuilder = new StringBuilder();
for (String entry : getText()) {
stringBuilder.append(entry + "\n§" + color + "---------------\n");
}
if (mainPage != null && mainPage.length() > 0) {
if (Config.getString("config.journal.full_main_page").equalsIgnoreCase("true")) {
finalList.addAll(Utils.pagesFromString(mainPage));
} else {
stringBuilder.insert(0, mainPage + "\n§" + color + "---------------\n");
}
}
String wholeString = stringBuilder.toString().trim();
finalList.addAll(Utils.pagesFromString(wholeString));
} else {
if (mainPage != null && mainPage.length() > 0) {
finalList.addAll(Utils.pagesFromString(mainPage));
}
finalList.addAll(getText());
}
if (finalList.size() > 0) {
meta.setPages(finalList);
} else {
meta.addPage("");
}
item.setItemMeta(meta);
return item;
}
/**
* Updates journal by removing it and adding it again
*/
public void update() {
if (hasJournal(playerID)) {
lang = BetonQuest.getInstance().getPlayerData(playerID).getLanguage();
int slot = removeFromInv();
addToInv(slot);
}
}
/**
* Removes journal from player's inventory.
*
* @return the slot from which the journal was removed
*/
public int removeFromInv() {
// loop all items and check if any of them is a journal
Inventory inventory = PlayerConverter.getPlayer(playerID).getInventory();
for (int i = 0; i < inventory.getSize(); i++) {
if (isJournal(playerID, inventory.getItem(i))) {
inventory.setItem(i, new ItemStack(Material.AIR));
return i;
}
}
return -1;
}
/**
* Checks if the item is journal
*
* @param playerID
* ID of the player
* @param item
* ItemStack to check against being the journal
* @return true if the ItemStack is the journal, false otherwise
*/
public static boolean isJournal(String playerID, ItemStack item) {
// if there is no item then it's not a journal
if (item == null) {
return false;
}
// get language
String playerLang = BetonQuest.getInstance().getPlayerData(playerID).getLanguage();
// check all properties of the item and return the result
return (item.getType().equals(Material.WRITTEN_BOOK) && ((BookMeta) item.getItemMeta()).hasTitle()
&& ((BookMeta) item.getItemMeta()).getTitle().equals(Config.getMessage(playerLang, "journal_title"))
&& item.getItemMeta().hasLore()
&& item.getItemMeta().getLore().contains(Config.getMessage(playerLang, "journal_lore")));
}
/**
* Checks if the player has his journal in the inventory. Returns false if
* the player is not online.
*
* @param playerID
* ID of the player
* @return true if the player has his journal, false otherwise
*/
public static boolean hasJournal(String playerID) {
Player player = PlayerConverter.getPlayer(playerID);
if (player == null)
return false;
for (ItemStack item : player.getInventory().getContents()) {
if (isJournal(playerID, item)) {
return true;
}
}
return false;
}
}