/*******************************************************************************
* Copyright 2014 Tobias Welther
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.tobiyas.racesandclasses.datacontainer.traitholdercontainer;
import static de.tobiyas.racesandclasses.util.traitutil.TraitConfigParser.configureTraitFromYAML;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import de.tobiyas.racesandclasses.RacesAndClasses;
import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.exceptions.HolderConfigParseException;
import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.exceptions.HolderParsingException;
import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.exceptions.HolderTraitParseException;
import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.permissionsettings.HolderPermissions;
import de.tobiyas.racesandclasses.playermanagement.leveling.values.LevelValueModifyReader;
import de.tobiyas.racesandclasses.playermanagement.player.RaCPlayer;
import de.tobiyas.racesandclasses.traitcontainer.TraitStore;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.Trait;
import de.tobiyas.racesandclasses.util.items.ItemUtils.ItemQuality;
import de.tobiyas.racesandclasses.util.items.WandItem;
import de.tobiyas.racesandclasses.util.traitutil.TraitConfigurationFailedException;
import de.tobiyas.util.collections.ListCreateUtils;
import de.tobiyas.util.config.YAMLConfigExtended;
import de.tobiyas.util.items.ItemUtils;
import de.tobiyas.util.vollotile.VollotileCode.MCVersion;
import de.tobiyas.util.vollotile.VollotileCodeManager;
public abstract class AbstractTraitHolder {
/**
* The config of the holders to store / load stuff
*/
protected final YAMLConfigExtended config;
/**
* The name of the holders
*/
protected final String configNodeName;
/**
* The Display Name to use.
*/
protected String displayName;
/**
* The pretty tag of the holders
*/
protected String holderTag;
/**
* The armor permissions of the Holder
*/
protected final Set<ItemQuality> armorUsage;
/**
* The Material of the Holder to select
*/
protected ItemStack holderSelectionItem;
/**
* A set of Traits that the holders contains
*/
protected Set<Trait> traits;
/**
* The permission container holding all Permissions for the holders
*/
protected final HolderPermissions holderPermissions;
/**
* The magic bonus to the mana pool.
*/
protected LevelValueModifyReader.LevelModifier manaBonus;
/**
* The magic bonus to the Max-Health pool.
*/
protected LevelValueModifyReader.LevelModifier healthBonus;
/**
* Logs all parsing exceptions happening during startup.
*/
protected final List<HolderTraitParseException> parsingExceptionsHappened;
/**
* Material additionally used for wands.
*/
protected final Set<WandItem> additionalWandItems;
/**
* The Description of the Holder.
* If null, none is present.
*/
protected String holderDescription = null;
/**
* The Pre holder needed.
* <br>If null no preHolder needed.
*/
protected HolderSelectionPreconditions preconditions = null;
/**
* The GuiSlot to use.
*/
protected int guiSlot = -1;
/**
* if the Traits should be hidden.
* <br>This should be used with Description!
*/
protected boolean hideTraitsInGui = false;
/**
* If the Config should be hidden.
* <br>This should be used with Description!
*/
protected boolean hideConfigInGui = false;
/**
* The parents of this holders.
*/
protected final Set<AbstractTraitHolder> parents = new HashSet<AbstractTraitHolder>();
/**
* Creates an {@link AbstractTraitHolder}
*
* @param config to load from
* @param name of the holders
*/
protected AbstractTraitHolder(YAMLConfigExtended config, String name) {
this.config = config;
this.configNodeName = name;
this.displayName = name;
this.parsingExceptionsHappened = new LinkedList<HolderTraitParseException>();
this.armorUsage = new HashSet<ItemQuality>();
this.traits = new HashSet<Trait>();
this.holderTag = "[" + name + "]";
this.additionalWandItems = new HashSet<>();
this.holderSelectionItem = new ItemStack(Material.BOOK_AND_QUILL);
this.preconditions = HolderSelectionPreconditions.getEmpty(getHolderManager());
this.holderPermissions = new HolderPermissions(getContainerTypeAsString() + "-" + configNodeName);
this.manaBonus = LevelValueModifyReader.LevelModifier.empty();
this.healthBonus = LevelValueModifyReader.LevelModifier.empty();
//we need to set the name to start. This is needed for inheritance.
try{
this.displayName = config.getString(configNodeName + ".config.name", configNodeName);
}catch(Throwable exp){}
}
/**
* Loads the Holder from the config file passed in constructor.
* If parsing fails, a HolderParsingException is thrown.
*
* @return the parsed Holder
*
* @throws HolderParsingException if the parsing failed.
*/
public AbstractTraitHolder load() throws HolderParsingException{
readConfigSection();
readArmor();
readTraitSection();
readPermissionSection();
readAdditionalWandMaterial();
readHolderSelectionItem();
readHolderDescription();
readHolderPreconditions();
return this;
}
/**
* Reads the parents section for the Trait.
*/
public void readParents() {
//Read parents:
List<String> parentsName = config.getStringList(configNodeName + ".config.parents");
List<String> ignoreParentTraits = config.getStringList(configNodeName + ".config.ignoreParentTraits");
ListCreateUtils.toLowerCase(ignoreParentTraits);
this.parents.clear();
if(parentsName.isEmpty()) return;
for(String name : parentsName){
AbstractTraitHolder parent = getHolderManager().getHolderByName(name);
if(parent == null) continue;
if(hasParentCyle(parent, 0, 50)){
RacesAndClasses.getPlugin().logError("The " + getContainerTypeAsString() + " has a cyclic Parent!"
+ " Please remove " + name + " in some sort from the cycle!");
continue;
}
this.parents.add(parent);
addHolderToAddParents(this, parent, 0, 50, ignoreParentTraits);
}
}
/**
* Reads the Preconditions for the Holder.
*/
protected void readHolderPreconditions(){
preconditions = HolderSelectionPreconditions.parse(config, getHolderManager());
}
/**
* Checks if this traitHolder has a cyclic depend.
*
* @param holders to check.
* @param step to do
*
* @return true if it has.
*/
protected boolean hasParentCyle(AbstractTraitHolder holder, int step, int border){
if(step > border) return false;
if(holder.getParents().contains(this)) return true;
for(AbstractTraitHolder parent : holder.getParents()) {
if(hasParentCyle(parent, step + 1, border)) return true;
}
return false;
}
/**
* Checks if this traitHolder has a cyclic depend.
*
* @param origin to add
* @param holder to check.
* @param step to do
* @param border to abort
*/
protected void addHolderToAddParents(AbstractTraitHolder origin, AbstractTraitHolder holder, int step, int border, List<String> toIgnore){
if(step > border) return;
for(Trait trait : holder.getTraits()) {
//if on ignoreList -> skip.
if(toIgnore.contains(holder.getDisplayName().toLowerCase() + "." + trait.getDisplayName().toLowerCase())) continue;
origin.traits.add(trait);
trait.addTraitHolder(origin);
}
for(AbstractTraitHolder parent : holder.getParents()) {
parent.addHolderToAddParents(origin, parent, step + 1, border, toIgnore);
}
}
/**
* Reads the Description from the config.
*/
protected void readHolderDescription() {
String toSet = config.getString(configNodeName + ".description", null);
if(toSet != null) holderDescription = toSet;
}
/**
* Reads the Holder selection Item from the config.
*/
@SuppressWarnings("deprecation")
protected void readHolderSelectionItem() {
Material mat = Material.BOOK_AND_QUILL;
try{
mat = Material.getMaterial(config.getInt(configNodeName + ".gui.item.id", Material.BOOK_AND_QUILL.getId()));
}catch(IllegalArgumentException exp){}
short damageValue = (short) config.getInt(configNodeName + ".gui.item.damage", 0);
this.holderSelectionItem = new ItemStack(mat, 1, damageValue);
ItemMeta meta = this.holderSelectionItem.getItemMeta();
//Add ItemFlags:
if(VollotileCodeManager.getVollotileCode().getVersion().isVersionGreaterOrEqual(MCVersion.v1_8_R3)){
meta.addItemFlags(org.bukkit.inventory.ItemFlag.HIDE_ATTRIBUTES);
meta.addItemFlags(org.bukkit.inventory.ItemFlag.HIDE_UNBREAKABLE);
}
this.holderSelectionItem.setItemMeta(meta);
//Try setting unbreakable if possible:
ItemUtils.setUnbreakable(this.holderSelectionItem, true);
}
/**
* Reads the WandMaterial Section.
*/
protected void readAdditionalWandMaterial() {
additionalWandItems.clear();
if(!config.contains(configNodeName + ".config.wandMaterial")) return;
List<String> wandMatList = config.getStringList(configNodeName + ".config.wandMaterial");
for(String line : wandMatList) {
WandItem item = WandItem.generateFrom(line);
if(item != null) additionalWandItems.add(item);
}
}
/**
* Reads the configuration section of the holders.
* Expect this to be called in the load process.
*
* @throws HolderConfigParseException if the parsing failed.
*/
protected void readConfigSection() throws HolderConfigParseException{
try{
this.displayName = config.getString(configNodeName + ".config.name", configNodeName);
this.manaBonus = new LevelValueModifyReader(config, configNodeName + ".config.manabonus").parse(0);
this.healthBonus = new LevelValueModifyReader(config, configNodeName + ".config.healthbonus").parse(0);
this.holderTag = ChatColor.translateAlternateColorCodes('&', config.getString(configNodeName + ".config.tag", "[" + configNodeName + "]"));
this.guiSlot = config.getInt(configNodeName + ".config.guislot", -1);
this.hideConfigInGui= config.getBoolean(configNodeName + ".config.hideConfigInGui", false);
this.hideTraitsInGui = config.getBoolean(configNodeName + ".config.hideTraitsInGui", false);
}catch(Exception exp){
throw new HolderConfigParseException();
}
}
/**
* Reads the Armor permissions from the Holder and parses it.
*/
protected void readArmor(){
armorUsage.clear();
String armorString = config.getString(configNodeName + ".config.armor", "").toLowerCase();
armorUsage.addAll(ItemQuality.parse(armorString));
}
/**
* Parses the Trait section of the Holder.
* When parsing fails, an {@link HolderTraitParseException} is thrown.
*
* @throws HolderTraitParseException if parsing fails.
*/
protected void readTraitSection(){
traits = new HashSet<Trait>();
addSTDTraits();
if(!config.isConfigurationSection(configNodeName + ".traits")){
return; //trait section is not necessary
}
Set<String> traitNames = config.getConfigurationSection(configNodeName + ".traits").getKeys(false);
if(traitNames == null || traitNames.size() == 0) return;
List<HolderTraitParseException> exceptionList = new LinkedList<HolderTraitParseException>();
for(String traitName : traitNames){
String realTraitName = traitName;
if(traitName.contains("#")){
//always select the first part.
realTraitName = traitName.split("#")[0];
}
if(config.isString(configNodeName + ".traits." + traitName + ".trait")){
realTraitName = config.getString(configNodeName + ".traits." + traitName + ".trait");
}
try{
Trait trait = TraitStore.buildTraitByName(realTraitName, this);
if(trait != null){
String configPath = configNodeName + ".traits." + traitName;
configureTraitFromYAML(config, configPath, trait);
trait.generalInit();
//Register as listener!
if(trait instanceof Listener) {
RacesAndClasses.getPlugin().registerEvents((Listener)trait);
}
traits.add(trait);
}
}catch(TraitConfigurationFailedException exp){
exceptionList.add(new HolderTraitParseException(exp.getMessage(), this));
RacesAndClasses.getPlugin().log("Error on parsing: '" + getDisplayName() + "' Problem was: '" + exp.getMessage()
+ "' On Trait: '" + realTraitName + "'.");
}
}
this.parsingExceptionsHappened.addAll(exceptionList);
}
/**
* Reads the Permission section of the Holder.
*/
protected void readPermissionSection() {
holderPermissions.clear();
if(!config.isList(configNodeName + ".permissions")){
return;
}
List<String> permissionList = config.getStringList(configNodeName + ".permissions");
holderPermissions.add(permissionList);
}
/**
* Adds STD Traits that every holders has to the Trait list.
*/
protected abstract void addSTDTraits();
/**
* Returns if a Player is member of this holders
*
* @param player to check
* @return true if is member, false otherwise
*/
public abstract boolean containsPlayer(RaCPlayer player);
/**
* Returns the Permissions this holders has additional
*
* @return as {@link List} of {@link String}
*/
public HolderPermissions getPermissions(){
return holderPermissions;
}
/**
* The name of the Holder
*
* @return
*/
public String getDisplayName(){
return displayName;
}
/**
* The name of the Holder
*
* @return
*/
public String getConfigNodeName(){
return configNodeName;
}
public String getTag(){
return holderTag;
}
public Set<Trait> getTraits(){
return traits;
}
/**
* The GuiSlot to show in.
*
* @return the Slot to show in.
*/
public int getGuiSlot() {
return guiSlot;
}
/**
* If the traits should be hidden in the Gui.
*/
public boolean isHideTraitsInGui() {
return hideTraitsInGui;
}
/**
* If the config is hidden in the Gui.
*/
public boolean isHideConfigInGui() {
return hideConfigInGui;
}
/**
* Returns the Holder permissions.
*
* @return holder permissions.
*/
public HolderPermissions getHolderPermissions() {
return holderPermissions;
}
/**
* Returns the Preconditions.
*
* @return preconditions.
*/
public HolderSelectionPreconditions getPreconditions() {
return preconditions;
}
/**
* Returns all Traits that are visible to the viewer
*
* @return
*/
public Set<Trait> getVisibleTraits(){
Set<Trait> traitSet = new HashSet<Trait>();
for(Trait trait : getTraits()){
if(trait.isVisible()) traitSet.add(trait);
}
return traitSet;
}
/**
* Returns a readable String for the Armor Permissions
*
* @return
*/
public String getArmorString(){
Set<ItemQuality> qualities = getArmorPerms();
String armorString = "";
for(ItemQuality quality : qualities)
armorString += quality.getName() + " ";
return armorString;
}
/**
* Returns a List of {@link ItemQuality} what this Holder can wear
*/
public Set<ItemQuality> getArmorPerms(){
return new HashSet<ItemQuality>(armorUsage);
}
@Override
public String toString(){
return displayName;
}
/**
* Returns the type name of the container.
* WARNING: This method is already called in the Constructor!!! So make sure it returns an constant String!
*
* @return
*/
protected abstract String getContainerTypeAsString();
/**
* @return the parsingExceptionsHappened
*/
public List<HolderTraitParseException> getParsingExceptionsHappened() {
return parsingExceptionsHappened;
}
/**
* @return the manaBonus
*/
public double getManaBonus(int level) {
return manaBonus.getForLevel(level);
}
/**
* @return the Health Bonus for the Level
*/
public double getMaxHealthMod(int level) {
return healthBonus.getForLevel(level);
}
/**
* Returns the HolderManager of the TraitHolder.
*
* @return the holderManager of the TraitHolder
*/
public abstract AbstractHolderManager getHolderManager();
/**
* Returns all Wand materials for the Holder
* <br>List can be empty.
* <br>This is only a clone set.
*
* @return set of all Wand Materials.
*/
public Set<WandItem> getAdditionalWandMaterials() {
return new HashSet<WandItem>(additionalWandItems);
}
/**
* Returns the Holder Selection item representing the holders in any GUI.
*
*/
public ItemStack getHolderSelectionItem() {
return holderSelectionItem;
}
/**
* @return the holderDescription
*/
public String getHolderDescription() {
return holderDescription;
}
/**
* @return if has a Holder Description
*/
public boolean hasHolderDescription() {
return holderDescription != null;
}
/**
* Returns the parents of this Holder.
*
* @return parents.
*/
public Set<AbstractTraitHolder> getParents(){
return new HashSet<AbstractTraitHolder>(parents);
}
}