package de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.gui; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Pattern; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.DyeColor; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import de.tobiyas.racesandclasses.RacesAndClasses; import de.tobiyas.racesandclasses.APIs.LevelAPI; import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.TraitHolderCombinder; import de.tobiyas.racesandclasses.playermanagement.player.RaCPlayer; import de.tobiyas.racesandclasses.playermanagement.player.RaCPlayerManager; import de.tobiyas.racesandclasses.playermanagement.skilltree.PlayerSkillTreeManager; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.Trait; import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.TraitWithRestrictions; import de.tobiyas.util.formating.StringFormatUtils; import de.tobiyas.util.inventorymenu.BasicSelectionInterface; import de.tobiyas.util.items.ItemUtils; import de.tobiyas.util.math.Math2; import de.tobiyas.util.vollotile.VollotileCode.MCVersion; import de.tobiyas.util.vollotile.VollotileCodeManager; public class SkillTreeGui extends BasicSelectionInterface { /** * The player to use. */ private final RaCPlayer racPlayer; /** * The map of Trait -> Buttons. */ private final Map<Trait,ItemStack> buttons = new HashMap<>(); /** * The traits that should be applied! */ private final Map<Trait,Integer> toApply = new HashMap<>(); /** * The Apply button. */ private final ItemStack applyItem; /** * The Apply button. */ private final ItemStack discardItem; /** * the temp free points to remove. */ private int tempFreePointsToRemove = 0; /** * If something changed. */ private boolean somethingChanged = false; @SuppressWarnings("deprecation") public SkillTreeGui(Player player) { super(player, null, Bukkit.createInventory(player, 9*6, ChatColor.RED + "Skill Tree"), player.getInventory(), RacesAndClasses.getPlugin()); this.racPlayer = RaCPlayerManager.get().getPlayer(player); this.applyItem = generateItem(Material.WOOL, DyeColor.GREEN.getWoolData(), ChatColor.GREEN + "Apply", ChatColor.AQUA + "Applies the changes"); this.discardItem = generateItem(Material.WOOL, DyeColor.RED.getWoolData(), ChatColor.RED + "Discard", ChatColor.AQUA + "Discards the changes"); //Copy traits + levels! this.toApply.putAll(racPlayer.getSkillTreeManager().getTraitsWithLevels()); redraw(); } /** * Redraws the GUI. */ private void redraw(){ getTopInventory().clear(); buttons.clear(); int freePoints = racPlayer.getSkillTreeManager().getFreeSkillpoints() - tempFreePointsToRemove; int level = LevelAPI.getCurrentLevel(racPlayer); Collection<Trait> allTraits = TraitHolderCombinder.getAllTraitsOfPlayer(racPlayer); Collection<Trait> permanent = getPermanent(allTraits); allTraits.removeAll(permanent); //Get the filtered results: Collection<Trait> filtered = filterForExclusions(allTraits); //Now generate items and populate! for(Trait trait : filtered){ int slot = trait.getSkillTreePlace(); //Do not display items that are permanent or below 0. //Filter items that are outside of the Box. if(slot < 0 || slot >= getTopInventory().getSize()) continue; ItemStack item = generateItemForTrait(trait, toApply, level, freePoints); //If already present -> Skip! ItemStack oldItem = getTopInventory().getItem(slot); if(oldItem != null && oldItem.getType() != Material.AIR) { RacesAndClasses.getPlugin().logWarning("Warning: in the Skilltree we have multiple entries with Index " + slot); continue; } getTopInventory().setItem(slot, item); buttons.put(trait, item); } getTopInventory().setItem(8, generateFreeItem(freePoints, level)); if(somethingChanged) getTopInventory().setItem((9*5)+8, applyItem); getTopInventory().setItem((9*5)+7, discardItem); } /** * Gets all present skill-trees. * @return all skill tree names. */ @SuppressWarnings("unused") private List<String> skillTrees(){ Set<String> trees = new HashSet<>(); Set<Trait> traits = TraitHolderCombinder.getAllTraitsOfPlayer(racPlayer); for(Trait trait : traits) trees.add(trait.getSkillTreeName()); return new ArrayList<>(trees); } /** * Generates an Item for the Trait. * @param trait to use. * @param hasPrequisits * @param hasLevel * @return */ private ItemStack generateItemForTrait(Trait trait, Map<Trait,Integer> traitsHasWithLevels, int playerLevel, int freePoints){ //First generate flags: int maxSkillLevel = trait.getSkillMaxLevel(); int playerSkillLevel = traitsHasWithLevels.containsKey(trait) ? traitsHasWithLevels.get(trait) : 0; boolean playerHasMaxLevel = playerSkillLevel >= maxSkillLevel; int traitLevelNeeded = trait.getSkillMinLevel(playerSkillLevel+1); boolean playerHasLevel = playerLevel >= traitLevelNeeded; String excluded = getForExcusions(trait, traitsHasWithLevels); boolean isExcluded = !excluded.isEmpty(); boolean hasPoints = freePoints >= trait.getSkillPointCost(playerSkillLevel+1); boolean hasPrequisits = hasPrequisits(trait, traitsHasWithLevels); boolean canUse = hasPoints && hasPrequisits && playerHasLevel && !isExcluded; ChatColor titleColor = playerHasMaxLevel ? ChatColor.BLUE : (canUse ? ChatColor.GREEN : ChatColor.DARK_RED ); String title = titleColor + trait.getDisplayName() + " (" + playerSkillLevel + "/" + maxSkillLevel + ")"; String levelString = (playerHasLevel ? ChatColor.GREEN : ChatColor.RED) + "Needs level: " + traitLevelNeeded; String pointsString = (hasPoints ? ChatColor.GREEN : ChatColor.RED) + "Needs: " + trait.getSkillPointCost(playerSkillLevel+1) + " Points"; String prequisitString = trait.getSkillTreePrequisits(playerSkillLevel+1).isEmpty() ? "" : (ChatColor.YELLOW + "Prequisits: " + generatePrequisitString(trait, traitsHasWithLevels)); List<String> lore = new LinkedList<>(); lore.addAll(Arrays.asList(trait.getPrettyConfiguration().split(Pattern.quote("#n")))); lore.add(""); if(playerHasMaxLevel) lore.add(ChatColor.AQUA + "Already learned"); else{ lore.add(pointsString); lore.add(levelString); if(!prequisitString.isEmpty()) lore.add(prequisitString); if(isExcluded) lore.add(ChatColor.DARK_RED + "Excluded by: " + ChatColor.RED + excluded); } ItemStack item = trait.getSkillTreeSymbol(); ItemMeta meta = item.getItemMeta(); meta.setDisplayName(title); meta.setLore(lore); //Add enchantment if has: if(playerHasMaxLevel) { meta.addEnchant(Enchantment.DURABILITY, 1, true); if(VollotileCodeManager.getVollotileCode().getVersion().isVersionGreaterOrEqual(MCVersion.v1_8_R3)){ meta.addItemFlags(org.bukkit.inventory.ItemFlag.HIDE_ENCHANTS); } } //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); } item.setItemMeta(meta); //Try setting unbreakable if possible: ItemUtils.setUnbreakable(item, true); return item; } /** * Gets the Trait/s for Exclusions. * @param trait to use * @param traitsHasWithLevels to use * @return the trait for exclusions or empty if none.. */ private String getForExcusions(Trait trait, Map<Trait, Integer> traitsHasWithLevels) { StringBuilder builder = new StringBuilder(); for(Map.Entry<Trait,Integer> entry : traitsHasWithLevels.entrySet()){ if(entry.getValue() <= 0) continue; //Found an exclusion! if(entry.getKey().getExcludesOtherTraits().contains(trait)){ if(builder.length() != 0) builder.append(", "); builder.append(entry.getKey().getDisplayName()); } } return builder.toString(); } private String generatePrequisitString(Trait trait, Map<Trait,Integer> traitsHas){ int playerSkillLevel = traitsHas.containsKey(trait) ? traitsHas.get(trait) : 0; StringBuilder builder = new StringBuilder(); for (Iterator<String> iterator = trait.getSkillTreePrequisits(playerSkillLevel+1).iterator(); iterator.hasNext();) { String pre = iterator.next(); String[] split = pre.split(Pattern.quote("@")); String preName = split[0]; int level = split.length == 1 ? 1 : StringFormatUtils.parseInt(split[1], 1); Trait found = null; for(Trait has : traitsHas.keySet()){ if(has.getDisplayName().equalsIgnoreCase(preName)){ found = has; break; } } boolean has = found != null && traitsHas.get(found) >= level; builder.append(has?ChatColor.GREEN:ChatColor.RED); builder.append(has?found.getDisplayName():preName).append(" ").append(level); if(iterator.hasNext()) builder.append(ChatColor.WHITE).append(", "); } return builder.toString(); } private boolean hasPrequisits(Trait trait, Map<Trait,Integer> traitsHas){ int playerSkillLevel = traitsHas.containsKey(trait) ? traitsHas.get(trait) : 0; for(String pre : trait.getSkillTreePrequisits(playerSkillLevel+1)){ String[] preSplit = pre.split(Pattern.quote("@")); String preName = preSplit[0]; int level = preSplit.length == 1 ? 1 : StringFormatUtils.parseInt(preSplit[1], 1); boolean found = false; for(Map.Entry<Trait,Integer> has : traitsHas.entrySet()){ if(has.getKey().getDisplayName().equalsIgnoreCase(preName) && has.getValue() >= level){ found = true; break; } } if(!found) return false; } return true; } private ItemStack generateFreeItem(int freePoints, int level) { ItemStack item = new ItemStack(Material.CAKE, Math2.clamp(1, freePoints, 64)); ItemMeta meta = item.getItemMeta(); meta.setDisplayName(ChatColor.AQUA + "Info - Read lore"); meta.setLore(Arrays.asList(ChatColor.YELLOW + "Free Skillpoints: " + ChatColor.AQUA + freePoints, ChatColor.YELLOW + "Level: " + ChatColor.AQUA + level)); item.setItemMeta(meta); return item; } /** * Filters for the Exclusions * @param toFilter to filter. * @return the rest of the Traits. */ private Collection<Trait> filterForExclusions(Collection<Trait> toFilter) { Collection<Trait> filtered = new HashSet<>(toFilter); for(Trait contained : toFilter){ boolean hasSkilled = racPlayer.getSkillTreeManager().getLevel(contained) > 0; if(hasSkilled) filtered.removeAll(contained.getExcludesOtherTraits()); } return filtered; } /** * Returns all Permanent Traits: * @param traits to use for checking * @return the permanent Traits from the ones passed. */ private Collection<Trait> getPermanent(Collection<Trait> traits){ Collection<Trait> permanent = new HashSet<>(); if(traits == null || traits.isEmpty()) return permanent; for(Trait trait : traits){ if(trait.isPermanentSkill()){ permanent.add(trait); } } return permanent; } @Override protected boolean onBackPressed() { return true; } @Override protected void onAcceptPressed() {} @Override protected void onSelectionItemPressed(int slot, ItemStack item) { if(item == null || item.getType() == Material.AIR) return; if(applyItem.isSimilar(item)){ applyCurrentSettings(); return; } if(discardItem.isSimilar(item)){ discardCurrentSettings(); return; } //Check which trait was clicked. If none -> Not our problem! Trait trait = getTraitForItem(item); if(trait == null) return; int playerTraitLevel = this.toApply.containsKey(trait) ? this.toApply.get(trait) : 0; int freePoints = racPlayer.getSkillTreeManager().getFreeSkillpoints() - tempFreePointsToRemove; int level = LevelAPI.getCurrentLevel(racPlayer); //If already has: if(playerTraitLevel >= trait.getSkillMaxLevel()){ player.sendMessage(ChatColor.RED + "You alreadsy have this Trait at max level."); return; } //If SkillPoints too low: if(freePoints < trait.getSkillPointCost(playerTraitLevel+1)){ player.sendMessage(ChatColor.RED + "You do not have enough free Skill-Points."); return; } //If not has prequisits: if(!hasPrequisits(trait, toApply)){ player.sendMessage(ChatColor.RED + "You do not have all prequisits!"); return; } //If Level to low: int neededLevel = (trait instanceof TraitWithRestrictions) ? ((TraitWithRestrictions)trait).getMinLevel() : 0; if(level < neededLevel){ player.sendMessage(ChatColor.RED + "Your level is too low!"); return; } //Seems like the player wants to learn the Trait: this.toApply.put(trait, playerTraitLevel+1); this.tempFreePointsToRemove += trait.getSkillPointCost(playerTraitLevel+1); this.somethingChanged = true; redraw(); } /** * Discards the Current Changes. */ private void discardCurrentSettings() { this.toApply.clear(); this.toApply.putAll(racPlayer.getSkillTreeManager().getTraitsWithLevels()); this.tempFreePointsToRemove = 0; this.somethingChanged = false; redraw(); } /** * Applies + closes. */ private void applyCurrentSettings() { this.close(); PlayerSkillTreeManager manager = racPlayer.getSkillTreeManager(); manager.replaceWithNew(toApply); //Rescan after: racPlayer.getArrowManager().rescanPlayer(); racPlayer.getSpellManager().rescan(); this.somethingChanged = false; player.sendMessage(ChatColor.GREEN + "Changes applied."); } /** * Returns the Trait associated to the Item. * @param item to use. * @return the trait associated. */ private Trait getTraitForItem(ItemStack item){ for(Entry<Trait,ItemStack> entry : buttons.entrySet()){ if(item.isSimilar(entry.getValue())) return entry.getKey(); } return null; } }