Fixes skill trees: resetting, 2-nodes incompatibility. Added proximity skill trees.

This commit is contained in:
Jules 2024-09-21 19:59:33 +02:00
parent bafd3e81b1
commit f24a1b0a6a
46 changed files with 1513 additions and 2763 deletions

View File

@ -30,6 +30,7 @@ import net.Indyuce.mmocore.experience.PlayerProfessions;
import net.Indyuce.mmocore.experience.Profession;
import net.Indyuce.mmocore.experience.droptable.ExperienceItem;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import net.Indyuce.mmocore.gui.skilltree.NodeIncrementResult;
import net.Indyuce.mmocore.guild.provided.Guild;
import net.Indyuce.mmocore.loot.chest.particle.SmallParticleEffect;
import net.Indyuce.mmocore.manager.data.OfflinePlayerData;
@ -45,9 +46,8 @@ import net.Indyuce.mmocore.skill.binding.BoundSkillInfo;
import net.Indyuce.mmocore.skill.binding.SkillSlot;
import net.Indyuce.mmocore.skill.cast.SkillCastingInstance;
import net.Indyuce.mmocore.skill.cast.SkillCastingMode;
import net.Indyuce.mmocore.skilltree.NodeIncrementResult;
import net.Indyuce.mmocore.skilltree.NodeState;
import net.Indyuce.mmocore.skilltree.SkillTreeNode;
import net.Indyuce.mmocore.skilltree.SkillTreeStatus;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import net.Indyuce.mmocore.waypoint.Waypoint;
import net.Indyuce.mmocore.waypoint.WaypointOption;
@ -110,15 +110,26 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
private final CombatHandler combat = new CombatHandler(this);
/**
* Cached for easier access. Current status of each skill tree node.
* Cached data
* <p>
* Current state of each node. This does not get saved in the player database
* as it can be inferred from the skill tree node levels map.
*/
private final Map<SkillTreeNode, SkillTreeStatus> nodeStates = new HashMap<>();
private final Map<SkillTreeNode, NodeState> nodeStates = new HashMap<>();
private final Map<SkillTreeNode, Integer> nodeLevels = new HashMap<>();
private final Map<String, Integer> skillTreePoints = new HashMap<>();
/**
* Cached data
* <p>
* Amount of points spent in each tree. This does not get saved in the
* player database as it can be inferred from the skill tree node levels map.
*/
private final Map<SkillTree, Integer> skillTreePointsSpent = new HashMap<>();
/**
* Saves the namespacedkeys of the items that have been unlocked in the form "namespace:key".
* Saves the NSK's of the items that have been unlocked in the format "namespace:key".
* This is used for:
* - waypoints
* - skills
@ -126,9 +137,8 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
private final Set<String> unlockedItems = new HashSet<>();
/**
* Saves the amount of times the player has claimed some
* item in any experience table. The key used in the map
* is the identifier of the exp table item.
* Saves the amount of times the player has claimed some item in any exp
* table. The key used in the map is the identifier of the exp table item.
*/
private final Map<String, Integer> tableItemClaims = new HashMap<>();
@ -177,7 +187,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
for (SkillTreeNode node : skillTree.getNodes())
if (!nodeLevels.containsKey(node)) nodeLevels.put(node, 0);
setupSkillTree();
setupSkillTrees();
applyTemporaryTriggers();
getStats().updateStats();
}
@ -216,7 +226,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
node.getExperienceTable().applyTemporaryTriggers(this, node);
}
public void setupSkillTree() {
public void setupSkillTrees() {
// Node states setup
for (SkillTree skillTree : getProfess().getSkillTrees())
@ -242,7 +252,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
}
public int countSkillTreePoints(@NotNull SkillTree skillTree) {
return nodeLevels.keySet().stream().filter(node -> node.getTree().equals(skillTree)).mapToInt(node -> nodeLevels.get(node) * node.getSkillTreePointsConsumed()).sum();
return nodeLevels.keySet().stream().filter(node -> node.getTree().equals(skillTree)).mapToInt(node -> nodeLevels.get(node) * node.getPointConsumption()).sum();
}
/**
@ -293,24 +303,30 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
for (SkillTreeNode node : tree.getNodes())
node.resetAdvancement(this, false);
// Node levels, states and points spent
// Skill trees progress
nodeLevels.clear();
nodeStates.clear();
nodeStates.clear(); // Cache data
skillTreePointsSpent.clear();
tableItemClaims.keySet().removeIf(s -> s.startsWith(SkillTreeNode.KEY_PREFIX)); // Clear node claim count
// Skill tree points
// Skill tree (realloc) points
skillTreePoints.clear();
skillTreeReallocationPoints = 0;
// Clear node claim count
tableItemClaims.keySet().removeIf(s -> s.startsWith(SkillTreeNode.KEY_PREFIX));
// Setup skill trees again
setupSkillTrees();
}
public void clearNodeStates(@NotNull SkillTree skillTree) {
for (SkillTreeNode node : skillTree.getNodes()) nodeStates.remove(node);
}
@NotNull
public NodeIncrementResult canIncrementNodeLevel(@NotNull SkillTreeNode node) {
final SkillTreeStatus skillTreeStatus = nodeStates.get(node);
final NodeState nodeState = nodeStates.get(node);
// Check node state
if (skillTreeStatus != SkillTreeStatus.UNLOCKED && skillTreeStatus != SkillTreeStatus.UNLOCKABLE)
if (nodeState != NodeState.UNLOCKED && nodeState != NodeState.UNLOCKABLE)
return NodeIncrementResult.LOCKED_NODE;
// Check permission
@ -320,7 +336,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
if (getNodeLevel(node) >= node.getMaxLevel()) return NodeIncrementResult.MAX_LEVEL_REACHED;
final int skillTreePoints = this.skillTreePoints.getOrDefault(node.getTree().getId(), 0) + this.skillTreePoints.getOrDefault("global", 0);
if (skillTreePoints < node.getSkillTreePointsConsumed()) return NodeIncrementResult.NOT_ENOUGH_POINTS;
if (skillTreePoints < node.getPointConsumption()) return NodeIncrementResult.NOT_ENOUGH_POINTS;
return NodeIncrementResult.SUCCESS;
}
@ -335,10 +351,10 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
node.updateAdvancement(this, newLevel); // Claim the node exp table
// Update node state
nodeStates.compute(node, (key, status) -> status == SkillTreeStatus.UNLOCKABLE ? SkillTreeStatus.UNLOCKED : status);
nodeStates.compute(node, (key, status) -> status == NodeState.UNLOCKABLE ? NodeState.UNLOCKED : status);
// Consume skill tree points
final AtomicInteger cost = new AtomicInteger(node.getSkillTreePointsConsumed());
final AtomicInteger cost = new AtomicInteger(node.getPointConsumption());
skillTreePoints.computeIfPresent(node.getTree().getId(), (key, points) -> {
final int withdrawn = Math.min(points, cost.get());
cost.set(cost.get() - withdrawn);
@ -347,7 +363,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
if (cost.get() > 0) withdrawSkillTreePoints("global", cost.get());
// Reload node states from full skill tree
for (SkillTreeNode node1 : node.getTree().getNodes()) nodeStates.remove(node1);
clearNodeStates(node.getTree());
node.getTree().setupNodeStates(this);
}
@ -365,38 +381,43 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
skillTreePoints.computeIfPresent(treeId, (ignored, points) -> cost >= points ? null : points - cost);
}
public void setNodeState(SkillTreeNode node, SkillTreeStatus skillTreeStatus) {
nodeStates.put(node, skillTreeStatus);
public void setNodeState(SkillTreeNode node, NodeState nodeState) {
nodeStates.put(node, nodeState);
}
public SkillTreeStatus getNodeStatus(SkillTreeNode node) {
public NodeState getNodeState(SkillTreeNode node) {
return nodeStates.get(node);
}
public boolean hasNodeState(SkillTreeNode node) {
@Deprecated
public NodeState getNodeStatus(SkillTreeNode node) {
return getNodeState(node);
}
public boolean hasNodeState(@NotNull SkillTreeNode node) {
return nodeStates.containsKey(node);
}
public int getNodeLevel(SkillTreeNode node) {
public int getNodeLevel(@NotNull SkillTreeNode node) {
return nodeLevels.getOrDefault(node, 0);
}
public void setNodeLevel(@NotNull SkillTreeNode node, int nodeLevel) {
nodeLevels.compute(node, (ignored, currentLevelInteger) -> {
final int currentLevel = currentLevelInteger == null ? 0 : currentLevelInteger;
final int delta = (nodeLevel - currentLevel) * node.getSkillTreePointsConsumed();
final int delta = (nodeLevel - currentLevel) * node.getPointConsumption();
skillTreePointsSpent.merge(node.getTree(), delta, (level, ignored2) -> level + delta);
return nodeLevel;
});
}
public int addNodeLevels(@NotNull SkillTreeNode node, int increment) {
final int delta = increment * node.getSkillTreePointsConsumed();
final int delta = increment * node.getPointConsumption();
skillTreePointsSpent.merge(node.getTree(), delta, (points, ignored) -> points + delta);
return nodeLevels.merge(node, increment, (level, ignored) -> level + increment);
}
public void resetSkillTree(SkillTree skillTree) {
public void resetSkillTree(@NotNull SkillTree skillTree) {
for (SkillTreeNode node : skillTree.getNodes()) {
node.resetAdvancement(this, true);
setNodeLevel(node, 0);
@ -405,10 +426,15 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
skillTree.setupNodeStates(this);
}
public Map<SkillTreeNode, SkillTreeStatus> getNodeStates() {
@NotNull
public Map<SkillTreeNode, NodeState> getNodeStates() {
return new HashMap<>(nodeStates);
}
public boolean hasNodeStates() {
return !nodeStates.isEmpty();
}
@Override
public Map<String, Integer> getNodeTimesClaimed() {
Map<String, Integer> result = new HashMap<>();
@ -420,7 +446,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
/**
* @return If the item is unlocked by the player
* This is used for skills that can be locked & unlocked.
* This is used for skills that can be locked & unlocked.
*/
public boolean hasUnlocked(Unlockable unlockable) {
return unlockable.isUnlockedByDefault() || unlockedItems.contains(unlockable.getUnlockNamespacedKey());
@ -599,7 +625,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
/**
* @param key The identifier of an exp table item.
* @return Amount of times an item has been claimed
* inside an experience table.
* inside an experience table.
*/
public int getClaims(@NotNull String key) {
return tableItemClaims.getOrDefault(key, 0);
@ -1102,7 +1128,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
/**
* @return If the PlayerEnterCastingModeEvent successfully put the player
* into casting mode, otherwise if the event is cancelled, returns false.
* into casting mode, otherwise if the event is cancelled, returns false.
*/
public boolean setSkillCasting() {
Validate.isTrue(!isCasting(), "Player already in casting mode");
@ -1121,7 +1147,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
/**
* @return If player successfully left skill casting i.e the Bukkit
* event has not been cancelled
* event has not been cancelled
*/
public boolean leaveSkillCasting() {
return leaveSkillCasting(false);
@ -1130,7 +1156,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
/**
* @param skipEvent Skip firing the exit event
* @return If player successfully left skill casting i.e the Bukkit
* event has not been cancelled
* event has not been cancelled
*/
public boolean leaveSkillCasting(boolean skipEvent) {
Validate.isTrue(isCasting(), "Player not in casting mode");
@ -1332,7 +1358,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
* checks if they could potentially upgrade to one of these
*
* @return If the player can change its current class to
* a subclass
* a subclass
*/
@Deprecated
public boolean canChooseSubclass() {

View File

@ -52,7 +52,7 @@ public class PlayerAttribute implements ExperienceObject {
try {
expTable = MMOCore.plugin.experience.loadExperienceTable(config.get("exp-table"));
} catch (RuntimeException exception) {
MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load exp table from class '" + id + "': " + exception.getMessage());
MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load exp table from attribute '" + id + "': " + exception.getMessage());
}
this.expTable = expTable;
}

View File

@ -33,6 +33,7 @@ import net.Indyuce.mmocore.skill.ClassSkill;
import net.Indyuce.mmocore.skill.RegisteredSkill;
import net.Indyuce.mmocore.skill.binding.SkillSlot;
import net.Indyuce.mmocore.skill.cast.ComboMap;
import net.Indyuce.mmocore.util.Icon;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Location;
@ -100,7 +101,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
this.id = id.toUpperCase().replace("-", "_").replace(" ", "_");
name = MythicLib.plugin.parseColors(config.getString("display.name", "INVALID DISPLAY NAME"));
icon = MMOCoreUtils.readIcon(config.getString("display.item", "BARRIER"));
icon = Icon.from(config.get("display.item", "BARRIER")).toItem();
if (config.contains("display.texture") && icon.getType() == Material.PLAYER_HEAD) {
ItemMeta meta = icon.getItemMeta();

View File

@ -8,6 +8,7 @@ import io.lumine.mythic.lib.gson.JsonObject;
import io.lumine.mythic.lib.hologram.Hologram;
import io.lumine.mythic.lib.version.VEnchantment;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.util.Icon;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Entity;
@ -130,18 +131,9 @@ public class MMOCoreUtils {
}
}
@NotNull
@Deprecated
public static ItemStack readIcon(String string) {
final String[] split = string.split(":");
final ItemStack item = new ItemStack(Material.valueOf(split[0].toUpperCase().replace("-", "_").replace(" ", "_")));
if (split.length > 1) {
final ItemMeta meta = item.getItemMeta();
meta.setCustomModelData(Integer.parseInt(split[1]));
item.setItemMeta(meta);
}
return item;
return Icon.from(string).toItem();
}
public static int getWorth(ItemStack[] items) {

View File

@ -73,7 +73,7 @@ public class ExperienceItem {
public ExperienceItem(ConfigurationSection config) {
Validate.notNull(config, "Config cannot be null");
Validate.isTrue(config.contains("triggers"));
Validate.isTrue(config.contains("triggers"), "Could not find config section 'triggers'");
id = config.getName();
period = config.getInt("period", 1);
@ -96,7 +96,7 @@ public class ExperienceItem {
* @param professionLevel The profession level the player just reached
* @param timesCollected Amount of times the exp item has already been claimed by the player
* @return If the item should be claimed right now taking into
* account the randomness factor from the 'chance' parameter
* account the randomness factor from the 'chance' parameter
*/
public boolean roll(int professionLevel, int timesCollected) {

View File

@ -1,6 +1,5 @@
package net.Indyuce.mmocore.experience.droptable;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.experience.ExperienceObject;
import org.apache.commons.lang.Validate;
@ -9,7 +8,6 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
public class ExperienceTable {
private final String id;
@ -24,7 +22,7 @@ public class ExperienceTable {
Validate.isTrue(config.isConfigurationSection(str), "Key '" + str + "' is not a configuration section");
items.add(new ExperienceItem(config.getConfigurationSection(str)));
} catch (RuntimeException exception) {
MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load item '" + str + "' from experience table '" + id + "': " + exception.getMessage());
throw new RuntimeException("Could not load item '" + str + "': " + exception.getMessage());
}
}

View File

@ -1,4 +1,4 @@
package net.Indyuce.mmocore.skilltree;
package net.Indyuce.mmocore.gui.skilltree;
public enum NodeIncrementResult {

View File

@ -1,6 +1,5 @@
package net.Indyuce.mmocore.gui.skilltree;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.ConfigMessage;
@ -13,8 +12,11 @@ import net.Indyuce.mmocore.gui.api.InventoryClickContext;
import net.Indyuce.mmocore.gui.api.item.InventoryItem;
import net.Indyuce.mmocore.gui.api.item.Placeholders;
import net.Indyuce.mmocore.gui.api.item.SimplePlaceholderItem;
import net.Indyuce.mmocore.gui.skilltree.display.*;
import net.Indyuce.mmocore.skilltree.*;
import net.Indyuce.mmocore.skilltree.display.DisplayInfo;
import net.Indyuce.mmocore.util.Icon;
import net.Indyuce.mmocore.skilltree.display.NodeDisplayInfo;
import net.Indyuce.mmocore.skilltree.display.PathDisplayInfo;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
@ -37,7 +39,7 @@ import java.util.stream.Collectors;
public class SkillTreeViewer extends EditableInventory {
protected final Map<DisplayInfo, Icon> icons = new HashMap<>();
protected final Map<SkillTreeStatus, String> statusNames = new HashMap<>();
protected final Map<NodeState, String> statusNames = new HashMap<>();
@Nullable
/**
@ -60,26 +62,29 @@ public class SkillTreeViewer extends EditableInventory {
public void reload(FileConfiguration config) {
super.reload(config);
if (config.contains("status-names"))
for (SkillTreeStatus skillTreeStatus : SkillTreeStatus.values())
statusNames.put(skillTreeStatus, config.getString("status-names." + UtilityMethods.ymlName(skillTreeStatus.name()), skillTreeStatus.name()));
for (NodeState nodeState : NodeState.values())
statusNames.put(nodeState, config.getString("status-names." + UtilityMethods.ymlName(nodeState.name()), nodeState.name()));
//Loads all the pathDisplayInfo
for (PathStatus status : PathStatus.values())
// Loads all the pathDisplayInfo
for (NodeState status : NodeState.values())
for (PathType pathType : PathType.values()) {
if (!config.contains("display.paths." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(pathType.name()))) {
final String configPath = "display.paths." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(pathType.name());
if (!config.contains(configPath)) {
MMOCore.log(Level.WARNING, "An error occurred while loading skill tree GUI: Missing path type: " + MMOCoreUtils.ymlName(pathType.name()) + " for status: " + MMOCoreUtils.ymlName(status.name()));
continue;
}
icons.put(new PathDisplayInfo(pathType, status), new Icon(config.getConfigurationSection("display.paths." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(pathType.name()))));
icons.put(new PathDisplayInfo(pathType, status), Icon.from(config.get(configPath)));
}
//Loads all the nodeDisplayInfo
for (SkillTreeStatus status : SkillTreeStatus.values())
// Loads all the nodeDisplayInfo
for (NodeState status : NodeState.values())
for (NodeType nodeType : NodeType.values()) {
if (!config.contains("display.nodes." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(nodeType.name()))) {
final String configPath = "display.nodes." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(nodeType.name());
if (!config.contains(configPath)) {
MMOCore.log(Level.WARNING, "An error occurred while loading skill tree GUI: Missing node type: " + MMOCoreUtils.ymlName(nodeType.name()) + " for status: " + MMOCoreUtils.ymlName(status.name()));
continue;
}
icons.put(new NodeDisplayInfo(nodeType, status), new Icon(config.getConfigurationSection("display.nodes." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(nodeType.name()))));
icons.put(new NodeDisplayInfo(nodeType, status), Icon.from(config.get(configPath)));
}
}
@ -242,7 +247,7 @@ public class SkillTreeViewer extends EditableInventory {
IntegerCoordinates coordinates = inv.getCoordinates(n);
if (inv.getSkillTree().isPathOrNode(coordinates)) {
Icon icon = inv.getIcon(coordinates);
ItemStack item = super.display(inv, n, icon.getMaterial(), icon.getCustomModelData());
ItemStack item = super.display(inv, n, icon.getMaterial(), icon.getModelData());
ItemMeta meta = item.getItemMeta();
Placeholders holders = getPlaceholders(inv, n);
if (inv.getSkillTree().isNode(coordinates)) {
@ -250,7 +255,7 @@ public class SkillTreeViewer extends EditableInventory {
List<String> lore = new ArrayList<>();
getLore().forEach(str -> {
if (str.contains("{node-lore}")) {
node.getLore(inv.getPlayerData()).forEach(s -> lore.add(holders.apply(inv.getPlayer(), s)));
node.getLore(inv.getPlayerData()).forEach(s -> lore.add(holders.apply(inv.getPlayer(), str.replace("{node-lore}", s))));
} else if (str.contains("{strong-parents}")) {
lore.addAll(getParentsLore(inv, node, node.getParents(ParentType.STRONG)));
} else if (str.contains("{soft-parents}")) {
@ -302,12 +307,12 @@ public class SkillTreeViewer extends EditableInventory {
if (isNode) {
SkillTreeNode node = inv.getNode(n);
holders.register("current-level", inv.getPlayerData().getNodeLevel(node));
SkillTreeStatus status = inv.getPlayerData().getNodeStatus(node);
NodeState status = inv.getPlayerData().getNodeState(node);
holders.register("current-state", statusNames.getOrDefault(status, status.name()));
holders.register("max-level", node.getMaxLevel());
holders.register("name", node.getName());
holders.register("max-children", node.getMaxChildren());
holders.register("point-consumed", node.getSkillTreePointsConsumed());
holders.register("point-consumed", node.getPointConsumption());
holders.register("display-type", node.getNodeType());
} else {
holders.register("display-type", inv.skillTree.getPath(inv.getCoordinates(n)).getPathType());
@ -381,22 +386,22 @@ public class SkillTreeViewer extends EditableInventory {
if (skillTree.isNode(coordinates)) {
SkillTreeNode node = skillTree.getNode(coordinates);
NodeType nodeType = node.getNodeType();
SkillTreeStatus skillTreeStatus = playerData.getNodeStatus(node);
NodeState nodeState = playerData.getNodeState(node);
//If the node has its own display, it will be shown.
if (node.hasIcon(skillTreeStatus))
return node.getIcon(skillTreeStatus);
DisplayInfo displayInfo = new NodeDisplayInfo(nodeType, skillTreeStatus);
if (node.hasIcon(nodeState))
return node.getIcon(nodeState);
DisplayInfo displayInfo = new NodeDisplayInfo(nodeType, nodeState);
//Takes the display defined in the skill tree config if it exists.
if (skillTree.hasIcon(displayInfo))
return skillTree.getIcon(displayInfo);
Icon icon = icons.get(displayInfo);
Validate.notNull(icon, "The node " + node.getFullId() + " has no icon for the type " + nodeType + " and the status " + skillTreeStatus);
Validate.notNull(icon, "The node " + node.getFullId() + " has no icon for the type " + nodeType + " and the status " + nodeState);
return icon;
} else {
SkillTreePath path = skillTree.getPath(coordinates);
PathType pathType = path.getPathType();
PathStatus pathStatus = path.getStatus(playerData);
NodeState pathStatus = path.getStatus(playerData);
DisplayInfo displayInfo = new PathDisplayInfo(pathType, pathStatus);
//Takes the display defined in the skill tree config if it exists.
if (skillTree.hasIcon(displayInfo))
@ -543,7 +548,7 @@ public class SkillTreeViewer extends EditableInventory {
}
case NOT_ENOUGH_POINTS: {
ConfigMessage.fromKey("not-enough-skill-tree-points", "point", "" + node.getSkillTreePointsConsumed()).send(player);
ConfigMessage.fromKey("not-enough-skill-tree-points", "point", "" + node.getPointConsumption()).send(player);
MMOCore.plugin.soundManager.getSound(SoundEvent.NOT_ENOUGH_POINTS).playTo(getPlayer());
break;
}

View File

@ -1,7 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
/**
* The information needed to determine the display of a node/path depending on its context.
*/
public interface DisplayInfo {
}

View File

@ -1,4 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
public interface DisplayType {
}

View File

@ -1,54 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
import io.lumine.mythic.lib.UtilityMethods;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import java.util.Objects;
/**
* The material and custom model-data of a node
*/
public class Icon {
private final Material material;
private final int customModelData;
public Material getMaterial() {
return material;
}
public int getCustomModelData() {
return customModelData;
}
public Icon(ConfigurationSection config) {
this(Material.valueOf(UtilityMethods.enumName(
config.getString("item", "DIRT"))), config.contains("custom-model-data") ? config.getInt("custom-model-data") : 0);
}
public Icon(Material material, int customModelData) {
this.material = material;
this.customModelData = customModelData;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Icon icon = (Icon) o;
return customModelData == icon.customModelData && material == icon.material;
}
@Override
public int hashCode() {
return Objects.hash(material, customModelData);
}
@Override
public String toString() {
return "Icon{" +
"material=" + material +
", customModelData=" + customModelData +
'}';
}
}

View File

@ -1,49 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
import net.Indyuce.mmocore.skilltree.SkillTreeStatus;
import java.util.Objects;
public class NodeDisplayInfo implements DisplayInfo{
private SkillTreeStatus skillTreeStatus;
private NodeType nodeType;
public NodeDisplayInfo(NodeType nodeType, SkillTreeStatus skillTreeStatus) {
this.skillTreeStatus = skillTreeStatus;
this.nodeType = nodeType;
}
public SkillTreeStatus getNodeState() {
return skillTreeStatus;
}
public SkillTreeStatus getNodeStatus() {
return skillTreeStatus;
}
public NodeType getNodeType() {
return nodeType;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeDisplayInfo that = (NodeDisplayInfo) o;
return skillTreeStatus == that.skillTreeStatus && nodeType == that.nodeType;
}
@Override
public int hashCode() {
return Objects.hash(skillTreeStatus, nodeType);
}
@Override
public String toString() {
return "NodeDisplayInfo{" +
"nodeStatus=" + skillTreeStatus +
", nodeType=" + nodeType +
'}';
}
}

View File

@ -1,57 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
public enum NodeType implements DisplayType {
UP_RIGHT_DOWN_LEFT,
UP_RIGHT_DOWN,
UP_RIGHT_LEFT,
UP_DOWN_LEFT,
DOWN_RIGHT_LEFT,
UP_RIGHT,
UP_DOWN,
UP_LEFT,
DOWN_RIGHT,
DOWN_LEFT,
RIGHT_LEFT,
RIGHT,
LEFT,
DOWN,
UP,
NO_PATH;
public static NodeType getNodeType(boolean hasUpPathOrNode, boolean hasRightPathOrNode, boolean hasDownPathOrNode, boolean hasLeftPathOrNode) {
if (hasUpPathOrNode && hasRightPathOrNode && hasDownPathOrNode && hasLeftPathOrNode)
return UP_RIGHT_DOWN_LEFT;
else if (hasUpPathOrNode && hasRightPathOrNode && hasDownPathOrNode)
return UP_RIGHT_DOWN;
else if (hasUpPathOrNode && hasRightPathOrNode && hasLeftPathOrNode)
return UP_RIGHT_LEFT;
else if (hasUpPathOrNode && hasDownPathOrNode && hasLeftPathOrNode)
return UP_DOWN_LEFT;
else if (hasDownPathOrNode && hasRightPathOrNode && hasLeftPathOrNode)
return DOWN_RIGHT_LEFT;
else if (hasUpPathOrNode && hasRightPathOrNode)
return UP_RIGHT;
else if (hasUpPathOrNode && hasDownPathOrNode)
return UP_DOWN;
else if (hasUpPathOrNode && hasLeftPathOrNode)
return UP_LEFT;
else if (hasDownPathOrNode && hasRightPathOrNode)
return DOWN_RIGHT;
else if (hasDownPathOrNode && hasLeftPathOrNode)
return DOWN_LEFT;
else if (hasRightPathOrNode && hasLeftPathOrNode)
return RIGHT_LEFT;
else if (hasUpPathOrNode)
return UP;
else if (hasDownPathOrNode)
return DOWN;
else if (hasRightPathOrNode)
return RIGHT;
else if (hasLeftPathOrNode)
return LEFT;
return NO_PATH;
}
}

View File

@ -1,44 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
import java.util.Objects;
public class PathDisplayInfo implements DisplayInfo{
private PathType pathType;
private PathStatus pathStatus;
public PathDisplayInfo(PathType pathType, PathStatus pathStatus) {
this.pathType = pathType;
this.pathStatus = pathStatus;
}
public PathType getPathType() {
return pathType;
}
public PathStatus getPathStatus() {
return pathStatus;
}
@Override
public int hashCode() {
return Objects.hash(pathType, pathStatus);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PathDisplayInfo that = (PathDisplayInfo) o;
return pathType == that.pathType && pathStatus == that.pathStatus;
}
@Override
public String toString() {
return "PathDisplayInfo{" +
"pathType=" + pathType +
", pathStatus=" + pathStatus +
'}';
}
}

View File

@ -1,8 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
public enum PathStatus {
LOCKED,
FULLY_LOCKED,
UNLOCKABLE,
UNLOCKED;
}

View File

@ -1,21 +0,0 @@
package net.Indyuce.mmocore.gui.skilltree.display;
/**
* The direction of the path.
*/
public enum PathType implements DisplayType {
UP,
/**
* Goes to up then east/ goes to left then down.
*/
UP_RIGHT,
UP_LEFT,
DOWN_RIGHT,
DOWN_LEFT,
RIGHT,
DEFAULT;
}

View File

@ -104,6 +104,7 @@ public class ConfigManager {
copyDefaultFile("skill-trees/rogue-marksman.yml");
copyDefaultFile("skill-trees/warrior-paladin.yml");
copyDefaultFile("skill-trees/general.yml");
copyDefaultFile("skill-trees/loop.yml");
}
if (!FileUtils.getFile(MMOCore.plugin, "waypoints").exists()) {

View File

@ -3,6 +3,7 @@ package net.Indyuce.mmocore.manager;
import io.lumine.mythic.lib.UtilityMethods;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.ConfigFile;
import net.Indyuce.mmocore.api.player.profess.PlayerClass;
import net.Indyuce.mmocore.gui.*;
import net.Indyuce.mmocore.gui.api.EditableInventory;
import net.Indyuce.mmocore.gui.skilltree.SkillTreeViewer;
@ -12,13 +13,19 @@ import net.Indyuce.mmocore.gui.social.guild.EditableGuildCreation;
import net.Indyuce.mmocore.gui.social.guild.EditableGuildView;
import net.Indyuce.mmocore.gui.social.party.EditablePartyCreation;
import net.Indyuce.mmocore.gui.social.party.EditablePartyView;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import java.util.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.stream.Collectors;
public class InventoryManager {
// GUIs
public static final PlayerStats PLAYER_STATS = new PlayerStats();
public static final SkillList SKILL_LIST = new SkillList();
public static final ClassSelect CLASS_SELECT = new ClassSelect();
@ -33,11 +40,14 @@ public class InventoryManager {
public static final QuestViewer QUEST_LIST = new QuestViewer();
public static final AttributeView ATTRIBUTE_VIEW = new AttributeView();
public static final SkillTreeViewer TREE_VIEW = new SkillTreeViewer();
public static Map<String, SkillTreeViewer> SPECIFIC_TREE_VIEW = new HashMap<>();
// Specific GUIs
public static final Map<String, SkillTreeViewer> SPECIFIC_TREE_VIEW = new HashMap<>();
public static final Map<String, ClassConfirmation> CLASS_CONFIRM = new HashMap<>();
public static final List<EditableInventory> list = new ArrayList(Arrays.asList(PLAYER_STATS, ATTRIBUTE_VIEW, TREE_VIEW, SKILL_LIST, CLASS_SELECT, SUBCLASS_SELECT, QUEST_LIST, WAYPOINTS, FRIEND_LIST, FRIEND_REMOVAL, PARTY_VIEW, PARTY_CREATION, GUILD_VIEW, GUILD_CREATION));
public static final List<EditableInventory> LIST = Arrays.asList(PLAYER_STATS, ATTRIBUTE_VIEW, TREE_VIEW, SKILL_LIST, CLASS_SELECT, SUBCLASS_SELECT, QUEST_LIST, WAYPOINTS, FRIEND_LIST, FRIEND_REMOVAL, PARTY_VIEW, PARTY_CREATION, GUILD_VIEW, GUILD_CREATION);
@Deprecated
public static final List<EditableInventory> list = LIST;
public static void load() {
//Loads the specific inventories
@ -55,8 +65,8 @@ public class InventoryManager {
GUI.reload(new ConfigFile("/gui/" + loader.name, GUI.getId()).getConfig());
}
}
list.forEach(inv ->
{
LIST.forEach(inv -> {
try {
MMOCore.plugin.configManager.copyDefaultFile("gui/" + inv.getId() + ".yml");
inv.reload(new ConfigFile("/gui", inv.getId()).getConfig());
@ -71,7 +81,7 @@ public class InventoryManager {
InventoryManager.CLASS_CONFIRM,
MMOCore.plugin.classManager.getAll().
stream().
map(playerClass -> playerClass.getId()).
map(PlayerClass::getId).
collect(Collectors.toList()),
(id, isDefault) -> new ClassConfirmation(MMOCore.plugin.classManager.get(id), isDefault)
),
@ -80,7 +90,7 @@ public class InventoryManager {
InventoryManager.SPECIFIC_TREE_VIEW,
MMOCore.plugin.skillTreeManager.getAll().
stream().
map(skillTree -> skillTree.getId()).
map(SkillTree::getId).
collect(Collectors.toList()),
(id, isDefault) -> new SkillTreeViewer(MMOCore.plugin.skillTreeManager.get(id), isDefault));

View File

@ -1,66 +1,88 @@
package net.Indyuce.mmocore.manager;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.util.FileUtils;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.manager.registry.MMOCoreRegister;
import net.Indyuce.mmocore.skilltree.ParentType;
import net.Indyuce.mmocore.skilltree.SkillTreeNode;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import net.Indyuce.mmocore.skilltree.tree.SkillTreeType;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
public class SkillTreeManager extends MMOCoreRegister<SkillTree> {
private final HashMap<String, SkillTreeNode> skillTreeNodes = new HashMap<>();
private final Map<String, SkillTreeNode> skillTreeNodes = new HashMap<>();
@Override
public void register(SkillTree tree) {
public void register(@NotNull SkillTree tree) {
super.register(tree);
tree.getNodes().forEach((node) -> skillTreeNodes.put(node.getFullId(), node));
}
public boolean has(int index) {
return index >= 0 && index < registered.values().stream().collect(Collectors.toList()).size();
}
public SkillTreeNode getNode(String fullId) {
@Nullable
public SkillTreeNode getNode(@NotNull String fullId) {
return skillTreeNodes.get(fullId);
}
/**
* Useful to recursively go trough trees
*
* @return The list of all the roots (e.g the nodes without any parents
*/
public List<SkillTreeNode> getRootNodes() {
return skillTreeNodes.values().stream().filter(treeNode -> treeNode.getParents(ParentType.SOFT).size() == 0).collect(Collectors.toList());
}
@NotNull
public Collection<SkillTreeNode> getAllNodes() {
return skillTreeNodes.values();
}
public SkillTree get(int index) {
return registered.values().stream().collect(Collectors.toList()).get(index);
}
@Override
public String getRegisteredObjectName() {
return "skill tree";
}
@Override
public void initialize(boolean clearBefore) {
if (clearBefore) registered.clear();
if (clearBefore) {
registered.clear();
skillTreeNodes.clear();
}
FileUtils.loadObjectsFromFolder(MMOCore.plugin, "skill-trees", true, (key, config) -> {
SkillTree skillTree = SkillTree.loadSkillTree(config);
if (skillTree != null) register(skillTree);
}, "Could not load skill tree from file '%s': %s");
FileUtils.loadObjectsFromFolder(MMOCore.plugin, "skill-trees", true,
(key, config) -> register(loadSkillTree(config)), "Could not load skill tree from file '%s': %s");
}
@NotNull
public SkillTree loadSkillTree(@NotNull ConfigurationSection config) {
Validate.notNull(config, "Config cannot be null");
final SkillTreeType type;
try {
type = SkillTreeType.valueOf(UtilityMethods.enumName(config.getString("type", "custom")));
} catch (RuntimeException exception) {
throw new IllegalArgumentException("Not a valid skill tree type");
}
return type.construct(config);
}
@Deprecated
public SkillTree get(int index) {
return new ArrayList<>(registered.values()).get(index);
}
/**
* Useful to recursively go through skill trees
*
* @return The list of all the roots (e.g the nodes without any parents
*/
@Deprecated
public List<SkillTreeNode> getRootNodes() {
return skillTreeNodes.values().stream().filter(treeNode -> treeNode.getParents(ParentType.SOFT).isEmpty()).collect(Collectors.toList());
}
@Deprecated
public boolean has(int index) {
return index >= 0 && index < registered.size();
}
}

View File

@ -5,9 +5,9 @@ import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.skill.handler.SkillHandler;
import io.lumine.mythic.lib.skill.trigger.TriggerType;
import io.lumine.mythic.lib.util.formula.BooleanExpression;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
import net.Indyuce.mmocore.api.util.math.formula.IntegerLinearValue;
import net.Indyuce.mmocore.api.util.math.formula.LinearValue;
import net.Indyuce.mmocore.util.Icon;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@ -32,7 +32,7 @@ public class RegisteredSkill {
this.handler = handler;
name = Objects.requireNonNull(config.getString("name"), "Could not find skill name");
icon = MMOCoreUtils.readIcon(Objects.requireNonNull(config.getString("material"), "Could not find skill icon"));
icon = Icon.from(config.get("material")).toItem();
lore = Objects.requireNonNull(config.getStringList("lore"), "Could not find skill lore");
// Trigger type
@ -142,7 +142,7 @@ public class RegisteredSkill {
/**
* @return Modifier formula.
* Not null as long as the modifier is well defined
* Not null as long as the modifier is well defined
*/
@NotNull
public LinearValue getParameterInfo(String parameter) {

View File

@ -1,7 +1,11 @@
package net.Indyuce.mmocore.skilltree;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Objects;
public class IntegerCoordinates {
@ -23,10 +27,16 @@ public class IntegerCoordinates {
public int getX() {
return x;
}
public int getY() {
return y;
}
@NotNull
public IntegerCoordinates add(@NotNull IntegerCoordinates other) {
return new IntegerCoordinates(x + other.x, y + other.y);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -42,6 +52,24 @@ public class IntegerCoordinates {
@Override
public String toString() {
return x + "." + y;
return new StringBuilder("(").append(x).append(", ").append(y).append(")").toString();
}
@NotNull
public static IntegerCoordinates from(@Nullable Object object) {
Validate.notNull(object, "Could not read coordinates");
if (object instanceof ConfigurationSection) {
final ConfigurationSection config = (ConfigurationSection) object;
return new IntegerCoordinates(config.getInt("x"), config.getInt("y"));
}
if (object instanceof String) {
final String[] split = ((String) object).split("[:.,]");
Validate.isTrue(split.length > 1, "Must provide two coordinates, X and Y, got " + Arrays.asList(split));
return new IntegerCoordinates(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
}
throw new RuntimeException("Needs either a string or configuration section");
}
}

View File

@ -0,0 +1,26 @@
package net.Indyuce.mmocore.skilltree;
public enum NodeState {
/**
* The player has purchased and unlocked the skill tree node.
*/
UNLOCKED,
/**
* The player has instant access to but has not unlocked the node.
*/
UNLOCKABLE,
/**
* The player does not have access to this skill node but it
* remains a possibility to access it.
*/
LOCKED,
/**
* The player made a choice making it now impossible to
* reach this node given its skill tree exploration.
*/
FULLY_LOCKED;
}

View File

@ -0,0 +1,25 @@
package net.Indyuce.mmocore.skilltree;
/**
* TODO docs
*/
public enum NodeType {
UP_RIGHT_DOWN_LEFT,
UP_RIGHT_DOWN,
UP_RIGHT_LEFT,
UP_DOWN_LEFT,
DOWN_RIGHT_LEFT,
UP_RIGHT,
UP_DOWN,
UP_LEFT,
DOWN_RIGHT,
DOWN_LEFT,
RIGHT_LEFT,
RIGHT,
LEFT,
DOWN,
UP,
NO_PATH;
}

View File

@ -1,7 +1,5 @@
package net.Indyuce.mmocore.skilltree.tree;
package net.Indyuce.mmocore.skilltree;
import net.Indyuce.mmocore.skilltree.ParentType;
import net.Indyuce.mmocore.skilltree.SkillTreeNode;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
@ -9,10 +7,12 @@ import java.util.Objects;
public class ParentInformation {
private final SkillTreeNode node;
private final ParentType type;
private final int level;
public ParentInformation(SkillTreeNode node, ParentType type) {
public ParentInformation(SkillTreeNode node, ParentType type, int level) {
this.node = node;
this.type = type;
this.level = level;
}
@NotNull
@ -25,14 +25,8 @@ public class ParentInformation {
return type;
}
@Deprecated
public ParentType type() {
return type;
}
@Deprecated
public SkillTreeNode node() {
return node;
public int getLevel() {
return level;
}
@Override

View File

@ -6,5 +6,4 @@ public enum ParentType {
STRONG,
INCOMPATIBLE;
}

View File

@ -0,0 +1,44 @@
package net.Indyuce.mmocore.skilltree;
/**
* These are the different textures that a path between
* two nodes can have, just like a redstone wire which can take
* turns, go straight, or be a one node path on its own.
*/
public enum PathType {
/**
* up, down
*/
UP,
/**
* right, left
*/
RIGHT,
/**
* up right, left down
*/
UP_RIGHT,
/**
* up left, right down
*/
UP_LEFT,
/**
* down right, left up
*/
DOWN_RIGHT,
/**
* down left, right up
*/
DOWN_LEFT,
/**
*
*/
DEFAULT;
}

View File

@ -9,9 +9,7 @@ import net.Indyuce.mmocore.experience.ExpCurve;
import net.Indyuce.mmocore.experience.ExperienceObject;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import net.Indyuce.mmocore.gui.api.item.Placeholders;
import net.Indyuce.mmocore.gui.skilltree.display.Icon;
import net.Indyuce.mmocore.gui.skilltree.display.NodeType;
import net.Indyuce.mmocore.skilltree.tree.ParentInformation;
import net.Indyuce.mmocore.util.Icon;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import org.apache.commons.lang.Validate;
import org.bukkit.Location;
@ -26,56 +24,37 @@ import java.util.stream.Collectors;
public class SkillTreeNode implements ExperienceObject {
private final SkillTree tree;
private final String name, id;
private final String permissionRequired;
private final Map<SkillTreeStatus, Icon> icons = new HashMap<>();
private IntegerCoordinates coordinates;
/**
* The number of skill tree points this node requires.
*/
private final int skillTreePointsConsumed;
private boolean isRoot;
/**
* The lore corresponding to each level
*/
private final int pointConsumption;
private final Map<NodeState, Icon> icons = new HashMap<>();
private final IntegerCoordinates coordinates;
private final int maxLevel, maxChildren;
private final ExperienceTable experienceTable;
private final List<ParentInformation> children = new ArrayList<>();
private final List<ParentInformation> parents = new ArrayList<>();
private final Map<Integer, List<String>> lores = new HashMap<>();
private final ExperienceTable experienceTable;
// The max level the skill tree node can have and the max amount of children it can have.
private final int maxLevel, maxChildren;
private final List<SkillTreeNode> children = new ArrayList<>();
/**
* Associates the required level to each parent.
* <p>
* You only need to have the requirement for one of your softParents
* but you need to fulfill the requirements of all of your strong parents.
**/
private final Map<ParentInformation, Integer> parents = new HashMap<>();
private boolean root;
public SkillTreeNode(SkillTree tree, ConfigurationSection config) {
Validate.notNull(config, "Config cannot be null");
this.id = config.getName();
this.tree = tree;
if (config.isConfigurationSection("display")) {
for (SkillTreeStatus status : SkillTreeStatus.values()) {
String ymlStatus = MMOCoreUtils.ymlName(status.name());
if (!config.isConfigurationSection("display." + ymlStatus)) {
MMOCore.log("Could not find node display for status " + ymlStatus + " for node " + id + " in tree " + tree.getId() + ". Using default display.");
continue;
}
icons.put(status, new Icon(config.getConfigurationSection("display." + MMOCoreUtils.ymlName(status.name()))));
}
// Load icons for node states
if (config.isConfigurationSection("display")) for (NodeState state : NodeState.values()) {
final String ymlStatus = MMOCoreUtils.ymlName(state.name());
if (config.isConfigurationSection("display." + ymlStatus))
icons.put(state, Icon.from(config.get("display." + MMOCoreUtils.ymlName(state.name()))));
else
MMOCore.log("Could not find node display for state " + ymlStatus + " of node " + id + " in tree " + tree.getId() + ". Using default display.");
}
name = Objects.requireNonNull(config.getString("name"), "Could not find node name");
isRoot = config.getBoolean("is-root", false);
skillTreePointsConsumed = config.getInt("point-consumed", 1);
root = config.getBoolean("root", config.getBoolean("is-root")); // backwards compatibility
pointConsumption = config.getInt("point-consumed", 1);
permissionRequired = config.getString("permission-required");
Validate.isTrue(skillTreePointsConsumed > 0, "The skill tree points consumed by a node must be greater than 0.");
Validate.isTrue(pointConsumption > 0, "The skill tree points consumed by a node must be greater than 0.");
if (config.contains("lores"))
for (String key : config.getConfigurationSection("lores").getKeys(false))
try {
@ -84,68 +63,70 @@ public class SkillTreeNode implements ExperienceObject {
throw new RuntimeException("You shall only specify integers in the 'lores' config section");
}
Validate.isTrue(config.contains("experience-table"), "You must specify an exp table");
this.experienceTable = MMOCore.plugin.experience.loadExperienceTable(config.get("experience-table"));
try {
Validate.isTrue(config.contains("experience-table"), "You must specify an exp table");
this.experienceTable = MMOCore.plugin.experience.loadExperienceTable(config.get("experience-table"));
} catch (RuntimeException exception) {
throw new RuntimeException("Could not load experience table: " + exception.getMessage());
}
maxLevel = config.contains("max-level") ? config.getInt("max-level") : 1;
maxChildren = config.contains("max-children") ? config.getInt("max-children") : 1;
// If coordinates are precised and we are not with an automaticTree we set them up
Validate.isTrue(config.contains("coordinates.x") && config.contains("coordinates.y"), "No coordinates specified");
coordinates = new IntegerCoordinates(config.getInt("coordinates.x"), config.getInt("coordinates.y"));
maxLevel = config.getInt("max-level", 1);
Validate.isTrue(maxLevel > 0, "Max level must be positive");
maxChildren = config.getInt("max-children", 0);
Validate.isTrue(maxChildren >= 0, "Max children must positive or zero");
coordinates = IntegerCoordinates.from(config.get("coordinates"));
}
public SkillTree getTree() {
return tree;
}
public boolean hasIcon(SkillTreeStatus status) {
public boolean hasIcon(NodeState status) {
return icons.containsKey(status);
}
public Icon getIcon(SkillTreeStatus status) {
public Icon getIcon(NodeState status) {
return icons.get(status);
}
public void setIsRoot() {
isRoot = true;
}
public boolean isRoot() {
return isRoot;
return root;
}
// Used when postLoaded
public void addParent(SkillTreeNode parent, int requiredLevel, ParentType parentType) {
parents.put(new ParentInformation(parent, parentType), requiredLevel);
public void addParent(@NotNull SkillTreeNode parent, @NotNull ParentType parentType, int requiredLevel) {
parents.add(new ParentInformation(parent, parentType, requiredLevel));
}
public void addChild(SkillTreeNode child) {
children.add(child);
public void addChild(@NotNull SkillTreeNode child, @NotNull ParentType parentType, int requiredLevel) {
children.add(new ParentInformation(child, parentType, requiredLevel));
}
public int getSkillTreePointsConsumed() {
return skillTreePointsConsumed;
public void setRoot() {
root = true;
}
public void setCoordinates(IntegerCoordinates coordinates) {
this.coordinates = coordinates;
public int getPointConsumption() {
return pointConsumption;
}
public int getParentNeededLevel(SkillTreeNode parent) {
for (Map.Entry<ParentInformation, Integer> entry : parents.entrySet())
if (entry.getKey().getNode().equals(parent))
return entry.getValue();
for (ParentInformation entry : parents)
if (entry.getNode().equals(parent))
return entry.getLevel();
throw new RuntimeException("Could not find parent " + parent.getId() + " for node " + id);
}
@Deprecated
public int getParentNeededLevel(SkillTreeNode parent, ParentType parentType) {
return parents.get(new ParentInformation(parent, parentType));
for (ParentInformation entry : parents)
if (entry.getNode().equals(parent) && entry.getType() == parentType)
return entry.getLevel();
return 0;
}
public boolean hasParent(SkillTreeNode parent) {
for (Map.Entry<ParentInformation, Integer> entry : parents.entrySet())
if (entry.getKey().getNode() == parent)
return true;
for (ParentInformation entry : parents)
if (entry.getNode() == parent) return true;
return false;
}
@ -157,19 +138,23 @@ public class SkillTreeNode implements ExperienceObject {
return maxChildren;
}
public boolean hasPermissionRequirement(PlayerData playerData) {
public boolean hasPermissionRequirement(@NotNull PlayerData playerData) {
return permissionRequired == null || playerData.getPlayer().hasPermission(permissionRequired);
}
public Set<SkillTreeNode> getParents() {
return parents.keySet().stream().map(ParentInformation::getNode).collect(Collectors.toSet());
@NotNull
public List<ParentInformation> getParents() {
return parents;
}
public Set<SkillTreeNode> getParents(ParentType parentType) {
return parents.entrySet().stream().filter(entry -> entry.getKey().type() == parentType).map((entry) -> entry.getKey().getNode()).collect(Collectors.toSet());
@NotNull
@Deprecated
public List<SkillTreeNode> getParents(ParentType parentType) {
return parents.stream().filter(integer -> integer.getType() == parentType).map(ParentInformation::getNode).collect(Collectors.toList());
}
public List<SkillTreeNode> getChildren() {
@NotNull
public List<ParentInformation> getChildren() {
return children;
}
@ -182,16 +167,19 @@ public class SkillTreeNode implements ExperienceObject {
/**
* @return Full node identifier, containing both the node identifier AND
* the skill tree identifier, like "combat_extra_strength"
* the skill tree identifier, like "combat_extra_strength"
*/
@NotNull
public String getFullId() {
return tree.getId() + "_" + id;
}
@NotNull
public String getName() {
return MythicLib.plugin.parseColors(name);
}
@NotNull
public IntegerCoordinates getCoordinates() {
return coordinates;
}
@ -203,12 +191,6 @@ public class SkillTreeNode implements ExperienceObject {
return KEY_PREFIX + ":" + getFullId().replace("-", "_");
}
@Nullable
@Override
public ExpCurve getExpCurve() {
throw new RuntimeException("Attributes don't have experience");
}
@Override
@NotNull
public ExperienceTable getExperienceTable() {
@ -221,11 +203,27 @@ public class SkillTreeNode implements ExperienceObject {
}
public NodeType getNodeType() {
boolean hasUpPathOrNode = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX(), coordinates.getY() - 1));
boolean hasDownPathOrNode = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX(), coordinates.getY() + 1));
boolean hasRightPathOrNode = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX() + 1, coordinates.getY()));
boolean hasLeftPathOrNode = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX() - 1, coordinates.getY()));
return NodeType.getNodeType(hasUpPathOrNode, hasRightPathOrNode, hasDownPathOrNode, hasLeftPathOrNode);
boolean up = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX(), coordinates.getY() - 1));
boolean down = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX(), coordinates.getY() + 1));
boolean right = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX() + 1, coordinates.getY()));
boolean left = tree.isPathOrNode(new IntegerCoordinates(coordinates.getX() - 1, coordinates.getY()));
if (up && right && down && left) return NodeType.UP_RIGHT_DOWN_LEFT;
else if (up && right && down) return NodeType.UP_RIGHT_DOWN;
else if (up && right && left) return NodeType.UP_RIGHT_LEFT;
else if (up && down && left) return NodeType.UP_DOWN_LEFT;
else if (down && right && left) return NodeType.DOWN_RIGHT_LEFT;
else if (up && right) return NodeType.UP_RIGHT;
else if (up && down) return NodeType.UP_DOWN;
else if (up && left) return NodeType.UP_LEFT;
else if (down && right) return NodeType.DOWN_RIGHT;
else if (down && left) return NodeType.DOWN_LEFT;
else if (right && left) return NodeType.RIGHT_LEFT;
else if (up) return NodeType.UP;
else if (down) return NodeType.DOWN;
else if (right) return NodeType.RIGHT;
else if (left) return NodeType.LEFT;
return NodeType.NO_PATH;
}
@Override
@ -241,16 +239,6 @@ public class SkillTreeNode implements ExperienceObject {
return Objects.hash(tree, id);
}
public Placeholders getPlaceholders(PlayerData playerData) {
Placeholders holders = new Placeholders();
holders.register("name", getName());
holders.register("node-state", playerData.getNodeStatus(this));
holders.register("level", playerData.getNodeLevel(this));
holders.register("max-level", getMaxLevel());
holders.register("max-children", getMaxChildren());
return holders;
}
public List<String> getLore(PlayerData playerData) {
final int nodeLevel = playerData.getNodeLevel(this);
final List<String> parsedLore = new ArrayList<>();
@ -267,14 +255,30 @@ public class SkillTreeNode implements ExperienceObject {
return parsedLore;
}
private Placeholders getPlaceholders(@NotNull PlayerData playerData) {
Placeholders holders = new Placeholders();
holders.register("name", getName());
holders.register("node-state", playerData.getNodeState(this));
holders.register("level", playerData.getNodeLevel(this));
holders.register("max-level", getMaxLevel());
holders.register("max-children", getMaxChildren());
return holders;
}
@Override
public void giveExperience(PlayerData playerData, double experience, @Nullable Location hologramLocation,
@NotNull EXPSource source) {
throw new RuntimeException("Attributes don't have experience");
throw new RuntimeException("Skill trees don't have experience");
}
@Override
public boolean shouldHandle(PlayerData playerData) {
throw new RuntimeException("Attributes don't have experience");
throw new RuntimeException("Skill trees don't have experience");
}
@Nullable
@Override
public ExpCurve getExpCurve() {
throw new RuntimeException("Skill trees don't have experience");
}
}

View File

@ -1,8 +1,6 @@
package net.Indyuce.mmocore.skilltree;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.gui.skilltree.display.PathStatus;
import net.Indyuce.mmocore.gui.skilltree.display.PathType;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
public class SkillTreePath {
@ -18,16 +16,16 @@ public class SkillTreePath {
to = skillTreeNode;
}
public PathStatus getStatus(PlayerData playerData) {
SkillTreeStatus fromStatus = playerData.getNodeStatus(from);
SkillTreeStatus toStatus = playerData.getNodeStatus(to);
if (fromStatus == SkillTreeStatus.UNLOCKED && toStatus == SkillTreeStatus.UNLOCKED)
return PathStatus.UNLOCKED;
if ((fromStatus == SkillTreeStatus.UNLOCKABLE && toStatus == SkillTreeStatus.UNLOCKED) || (fromStatus == SkillTreeStatus.UNLOCKED && toStatus == SkillTreeStatus.UNLOCKABLE))
return PathStatus.UNLOCKABLE;
if (fromStatus == SkillTreeStatus.FULLY_LOCKED || toStatus == SkillTreeStatus.FULLY_LOCKED)
return PathStatus.FULLY_LOCKED;
return PathStatus.LOCKED;
public NodeState getStatus(PlayerData playerData) {
NodeState fromStatus = playerData.getNodeState(from);
NodeState toStatus = playerData.getNodeState(to);
if (fromStatus == NodeState.UNLOCKED && toStatus == NodeState.UNLOCKED)
return NodeState.UNLOCKED;
if ((fromStatus == NodeState.UNLOCKABLE && toStatus == NodeState.UNLOCKED) || (fromStatus == NodeState.UNLOCKED && toStatus == NodeState.UNLOCKABLE))
return NodeState.UNLOCKABLE;
if (fromStatus == NodeState.FULLY_LOCKED || toStatus == NodeState.FULLY_LOCKED)
return NodeState.FULLY_LOCKED;
return NodeState.LOCKED;
}
public PathType getPathType() {

View File

@ -1,25 +0,0 @@
package net.Indyuce.mmocore.skilltree;
public enum SkillTreeStatus {
/**
* The player does not have access to this skill tree node just yet.
*/
LOCKED,
/**
* The player has bought and unlocked the skill tree node.
*/
UNLOCKED,
/**
* The player has access to but has not unlocked the node yet.
*/
UNLOCKABLE,
/**
* The player had access to this node, but unlocked another
* node which now prevents him from unlocking this one.
*/
FULLY_LOCKED;
}

View File

@ -0,0 +1,18 @@
package net.Indyuce.mmocore.skilltree.display;
import net.Indyuce.mmocore.skilltree.NodeState;
/**
* The information needed to determine the display of a node/path depending on its context.
*/
public abstract class DisplayInfo {
protected final NodeState state;
protected DisplayInfo(NodeState state) {
this.state = state;
}
public NodeState getState() {
return state;
}
}

View File

@ -0,0 +1,39 @@
package net.Indyuce.mmocore.skilltree.display;
import net.Indyuce.mmocore.skilltree.NodeType;
import net.Indyuce.mmocore.skilltree.NodeState;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class NodeDisplayInfo extends DisplayInfo {
private final NodeType type;
public NodeDisplayInfo(@NotNull NodeType type, @NotNull NodeState status) {
super(status);
this.type = type;
}
public NodeType getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeDisplayInfo that = (NodeDisplayInfo) o;
return state == that.state && type == that.type;
}
@Override
public int hashCode() {
return Objects.hash(state, type);
}
@Override
public String toString() {
return "NodeDisplayInfo{" + "status=" + state + ", type=" + type + '}';
}
}

View File

@ -0,0 +1,42 @@
package net.Indyuce.mmocore.skilltree.display;
import net.Indyuce.mmocore.skilltree.PathType;
import net.Indyuce.mmocore.skilltree.NodeState;
import java.util.Objects;
public class PathDisplayInfo extends DisplayInfo {
private final PathType type;
public PathDisplayInfo(PathType type, NodeState status) {
super(status);
this.type = type;
}
public PathType getType() {
return type;
}
public NodeState getStatus() {
return state;
}
@Override
public int hashCode() {
return Objects.hash(type, state);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PathDisplayInfo that = (PathDisplayInfo) o;
return type == that.type && state == that.state;
}
@Override
public String toString() {
return "PathDisplayInfo{" + "type=" + type + ", status=" + state + '}';
}
}

View File

@ -1,55 +1,18 @@
package net.Indyuce.mmocore.skilltree.tree;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.util.PostLoadAction;
import net.Indyuce.mmocore.skilltree.ParentType;
import net.Indyuce.mmocore.skilltree.SkillTreeNode;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
public class CustomSkillTree extends SkillTree {
private final PostLoadAction postLoadAction = new PostLoadAction(config -> {
// Setup the children and parents for each node.
for (SkillTreeNode node : nodes.values()) {
if (config.isConfigurationSection("nodes." + node.getId() + ".parents"))
for (String key : config.getConfigurationSection("nodes." + node.getId() + ".parents").getKeys(false)) {
ConfigurationSection section = config.getConfigurationSection("nodes." + node.getId() + ".parents." + key);
if (section != null) {
for (String parent : section.getKeys(false)) {
node.addParent(getNode(parent), section.getInt(parent), ParentType.valueOf(UtilityMethods.enumName(key)));
getNode(parent).addChild(node);
}
}
}
}
setupRoots();
});
public CustomSkillTree(ConfigurationSection config) {
public CustomSkillTree(@NotNull ConfigurationSection config) {
super(config);
postLoadAction.cacheConfig(config);
// Setup the coordinate map because coordinates are given in the yml for linked skill tree
super.coordinatesSetup();
}
private void setupRoots() {
// Find the tree roots which don't have any parents
for (SkillTreeNode node : nodes.values()) {
if (node.getParents().size() == 0) {
// We mark the node as a root also
roots.add(node);
node.setIsRoot();
// Nodes with no parents are roots
for (SkillTreeNode node : nodes.values())
if (node.getParents().isEmpty()) {
node.setRoot();
addRoot(node);
}
}
}
@NotNull
@Override
public PostLoadAction getPostLoadAction() {
return postLoadAction;
}
}

View File

@ -0,0 +1,29 @@
package net.Indyuce.mmocore.skilltree.tree;
import net.Indyuce.mmocore.skilltree.IntegerCoordinates;
import net.Indyuce.mmocore.skilltree.ParentType;
import net.Indyuce.mmocore.skilltree.SkillTreeNode;
import org.bukkit.configuration.ConfigurationSection;
public class ProximitySkillTree extends SkillTree {
public ProximitySkillTree(ConfigurationSection config) {
super(config);
// Neighbors are marked as soft parents
for (SkillTreeNode node : nodes.values())
for (IntegerCoordinates relative : RELATIVES) {
final SkillTreeNode neighbor = this.getNodeOrNull(node.getCoordinates().add(relative));
if (neighbor != null) {
node.addParent(neighbor, ParentType.SOFT, 1);
neighbor.addChild(node, ParentType.SOFT, 1);
}
}
}
private static final IntegerCoordinates[] RELATIVES = {
new IntegerCoordinates(1, 0),
new IntegerCoordinates(-1, 0),
new IntegerCoordinates(0, 1),
new IntegerCoordinates(0, -1)
};
}

View File

@ -2,13 +2,15 @@ package net.Indyuce.mmocore.skilltree.tree;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
import net.Indyuce.mmocore.gui.skilltree.display.*;
import net.Indyuce.mmocore.manager.registry.RegisteredObject;
import net.Indyuce.mmocore.skilltree.*;
import net.Indyuce.mmocore.skilltree.display.DisplayInfo;
import net.Indyuce.mmocore.skilltree.display.NodeDisplayInfo;
import net.Indyuce.mmocore.skilltree.display.PathDisplayInfo;
import net.Indyuce.mmocore.util.Icon;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
@ -16,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.logging.Level;
/**
* A passive skill tree that features nodes, or passive skills.
@ -30,34 +33,22 @@ import java.util.*;
* - particle or potion effects
*
* @author Ka0rX
* @see {@link SkillTreeNode}
* @see SkillTreeNode
*/
public abstract class SkillTree implements RegisteredObject, PreloadedObject {
public abstract class SkillTree implements RegisteredObject {
private final String id, name;
private final List<String> lore = new ArrayList<>();
private final Material item;
private final int customModelData;
//2 different maps to get the nodes
/**
* Represents all the nodes
* Key: the coordinates of the node
* Value: the node
*/
protected final Map<IntegerCoordinates, SkillTreeNode> coordinatesNodes = new HashMap<>();
/**
* Represents all the paths between nodes.
*/
protected final Map<IntegerCoordinates, SkillTreePath> coordinatesPaths = new HashMap<>();
protected final Map<String, SkillTreeNode> nodes = new HashMap<>();
protected final int maxPointSpent;
//Caches the height of the skill tree
protected final List<SkillTreeNode> roots = new ArrayList<>();
protected final Map<DisplayInfo, Icon> icons = new HashMap<>();
public SkillTree(ConfigurationSection config) {
protected final Map<IntegerCoordinates, SkillTreeNode> coordNodes = new HashMap<>();
protected final Map<IntegerCoordinates, SkillTreePath> coordPaths = new HashMap<>();
public SkillTree(@NotNull ConfigurationSection config) {
this.id = Objects.requireNonNull(config.getString("id"), "Could not find skill tree id");
this.name = MythicLib.plugin.parseColors(Objects.requireNonNull(config.getString("name"), "Could not find skill tree name"));
Objects.requireNonNull(config.getStringList("lore"), "Could not find skill tree lore").forEach(str -> lore.add(MythicLib.plugin.parseColors(str)));
@ -65,15 +56,21 @@ public abstract class SkillTree implements RegisteredObject, PreloadedObject {
this.customModelData = config.getInt("custom-model-data", 0);
Validate.isTrue(config.isConfigurationSection("nodes"), "Could not find any nodes in the tree");
this.maxPointSpent = config.getInt("max-point-spent", Integer.MAX_VALUE);
for (String key : config.getConfigurationSection("nodes").getKeys(false)) {
// Load nodes
for (String key : config.getConfigurationSection("nodes").getKeys(false))
try {
ConfigurationSection section = config.getConfigurationSection("nodes." + key);
SkillTreeNode node = new SkillTreeNode(this, section);
nodes.put(node.getId(), node);
coordNodes.put(node.getCoordinates(), node);
if (node.isRoot()) roots.add(node);
} catch (Exception e) {
MMOCore.log("Couldn't load skill tree node " + id + "." + key + ": " + e.getMessage());
}
}
// Load paths
for (String from : config.getConfigurationSection("nodes").getKeys(false)) {
ConfigurationSection section = config.getConfigurationSection("nodes." + from);
if (section.contains("paths")) {
@ -84,40 +81,53 @@ public abstract class SkillTree implements RegisteredObject, PreloadedObject {
continue;
}
for (String pathKey : section.getConfigurationSection("paths." + to).getKeys(false)) {
IntegerCoordinates coordinates = new IntegerCoordinates(section.getInt("paths." + to + "." + pathKey + ".x"), section.getInt("paths." + to + "." + pathKey + ".y"));
coordinatesPaths.put(coordinates, new SkillTreePath(this, coordinates, nodes.get(from), node1));
IntegerCoordinates coordinates = IntegerCoordinates.from(section.get("paths." + to + "." + pathKey));
coordPaths.put(coordinates, new SkillTreePath(this, coordinates, nodes.get(from), node1));
}
}
}
}
//Loads all the pathDisplayInfo
for (PathStatus status : PathStatus.values())
for (PathType pathType : PathType.values()) {
ConfigurationSection section = config.getConfigurationSection("display.paths." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(pathType.name()));
if (section != null)
icons.put(new PathDisplayInfo(pathType, status), new Icon(section));
// Loads all the pathDisplayInfo
for (NodeState status : NodeState.values())
for (PathType pathType : PathType.values())
try {
final String configPath = "display.paths." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(pathType.name());
icons.put(new PathDisplayInfo(pathType, status), Icon.from(config.get(configPath)));
} catch (Exception exception) {
// Ignore
}
// Loads all the nodeDisplayInfo
for (NodeState status : NodeState.values())
for (NodeType nodeType : NodeType.values())
try {
final String configPath = "display.nodes." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(nodeType.name());
icons.put(new NodeDisplayInfo(nodeType, status), Icon.from(config.get(configPath)));
} catch (Exception exception) {
// Ignore
}
// Setup children and parents for each node
for (SkillTreeNode node : nodes.values())
try {
if (config.isConfigurationSection("nodes." + node.getId() + ".parents"))
for (String key : config.getConfigurationSection("nodes." + node.getId() + ".parents").getKeys(false)) {
final ConfigurationSection section = config.getConfigurationSection("nodes." + node.getId() + ".parents." + key);
if (section != null) {
final ParentType parentType = ParentType.valueOf(UtilityMethods.enumName(key));
for (String parentId : section.getKeys(false)) {
final SkillTreeNode parent = getNode(parentId);
final int level = section.getInt(parentId);
node.addParent(parent, parentType, level);
parent.addChild(node, parentType, level);
}
}
}
} catch (RuntimeException exception) {
MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load parents of skill tree node '" + node.getId() + "': " + exception.getMessage());
}
//Loads all the nodeDisplayInfo
for (SkillTreeStatus status : SkillTreeStatus.values())
for (NodeType nodeType : NodeType.values()) {
ConfigurationSection section = config.getConfigurationSection("display.nodes." + MMOCoreUtils.ymlName(status.name()) + "." + MMOCoreUtils.ymlName(nodeType.name()));
if (section != null)
icons.put(new NodeDisplayInfo(nodeType, status), new Icon(section));
}
}
/**
* Used to setup everything related to coordinates when each node has its coordinates loaded.
*/
public void coordinatesSetup() {
for (SkillTreeNode node : nodes.values()) {
coordinatesNodes.put(node.getCoordinates(), node);
if (node.isRoot())
roots.add(node);
}
}
public List<String> getLore() {
@ -132,119 +142,170 @@ public abstract class SkillTree implements RegisteredObject, PreloadedObject {
return customModelData;
}
@Deprecated
public static SkillTree loadSkillTree(ConfigurationSection config) {
SkillTree skillTree = null;
try {
skillTree = new CustomSkillTree(config);
skillTree.getPostLoadAction().performAction();
} catch (Exception e) {
MMOCore.log("Couldn't load skill tree " + config.getString("id") + ": " + e.getMessage());
}
return skillTree;
return MMOCore.plugin.skillTreeManager.loadSkillTree(config);
}
public void addRoot(SkillTreeNode node) {
public void addRoot(@NotNull SkillTreeNode node) {
roots.add(node);
}
/**
* Recursively go through the skill trees to update the the node states
*/
public void setupNodeStates(PlayerData playerData) {
for (SkillTreeNode root : roots)
setupNodeStateFrom(root, playerData);
}
@NotNull
public List<SkillTreeNode> getRoots() {
return roots;
}
/**
* Update recursively the state of all the nodes that are
* children of this node (Used when we change the state of a node)
* TODO Write documentation
* TODO Use some collection and progressively filter out the nodes to avoid useless iterations
* <p>
* Let:
* - V denote the number of nodes in the skill tree
* - P the number of parents any node, has at most
* - C the number of children any node has, at most
* <p>
* This algorithm runs in O(V * P * C)
*/
private void setupNodeStateFrom(SkillTreeNode node, PlayerData playerData) {
if (playerData.getNodeLevel(node) > 0) {
playerData.setNodeState(node, SkillTreeStatus.UNLOCKED);
} else if (playerData.getNodeLevel(node) == 0 && node.isRoot()) {
playerData.setNodeState(node, SkillTreeStatus.UNLOCKABLE);
} else {
Set<SkillTreeNode> strongParents = node.getParents(ParentType.STRONG);
Set<SkillTreeNode> softParents = node.getParents(ParentType.SOFT);
Set<SkillTreeNode> incompatibleParents = node.getParents(ParentType.INCOMPATIBLE);
public void setupNodeStates(@NotNull PlayerData playerData) {
// Reinitialization
playerData.clearNodeStates(this);
boolean isUnlockableFromStrongParent = true;
boolean isUnlockableFromSoftParent = softParents.size() == 0;
boolean isFullyLockedFromStrongParent = false;
boolean isFullyLockedFromSoftParent = softParents.size() != 0;
boolean isFullyLockedFromIncompatibleParent = false;
// If the player has already spent the maximum amount of points in this skill tree.
final boolean skillTreeLocked = playerData.getPointsSpent(this) >= this.maxPointSpent;
final NodeState lockState = skillTreeLocked ? NodeState.FULLY_LOCKED : NodeState.LOCKED;
for (SkillTreeNode strongParent : strongParents) {
if (playerData.getNodeLevel(strongParent) < node.getParentNeededLevel(strongParent, ParentType.STRONG)) {
isUnlockableFromStrongParent = false;
}
//We count the number of children the parent
int numberChildren = 0;
for (SkillTreeNode child : strongParent.getChildren())
if (playerData.getNodeLevel(child) > 0)
numberChildren++;
// PASS 1
//
// Initialization. Mark all nodes either locked or unlocked
for (SkillTreeNode node : nodes.values())
playerData.setNodeState(node, playerData.getNodeLevel(node) > 0 ? NodeState.UNLOCKED : lockState);
//We must check if the parent is Fully Locked or not and if it can unlock a new node(with its max children constraint)
if (numberChildren >= strongParent.getMaxChildren() || playerData.getNodeStatus(strongParent) == SkillTreeStatus.FULLY_LOCKED)
isFullyLockedFromStrongParent = true;
}
if (skillTreeLocked) return;
// PASS 2
//
// Apply basic unreachability rules in O(V * [C + P])
// It has to differ from pass 1 because it uses results from pass 1.
final Stack<SkillTreeNode> unreachable = new Stack<>();
final Set<SkillTreeNode> updated = new HashSet<>();
for (SkillTreeNode softParent : softParents) {
if (playerData.getNodeLevel(softParent) >= node.getParentNeededLevel(softParent, ParentType.SOFT)) {
isUnlockableFromSoftParent = true;
}
//We count the number of children the parent has
int numberChildren = 0;
for (SkillTreeNode child : softParent.getChildren())
if (playerData.getNodeLevel(child) > 0)
numberChildren++;
if (numberChildren < softParent.getMaxChildren() && playerData.getNodeStatus(softParent) != SkillTreeStatus.FULLY_LOCKED)
isFullyLockedFromSoftParent = false;
}
for (SkillTreeNode incompatibleParent : incompatibleParents) {
if (playerData.getNodeLevel(incompatibleParent) >= node.getParentNeededLevel(incompatibleParent, ParentType.INCOMPATIBLE)) {
isFullyLockedFromIncompatibleParent = true;
for (SkillTreeNode node : nodes.values()) {
// INCOMPATIBILITY RULES
//
// Any node with an unlocked incompatible parent is made unreachable.
for (ParentInformation parent : node.getParents())
if (parent.getType() == ParentType.INCOMPATIBLE && playerData.getNodeState(parent.getNode()) == NodeState.UNLOCKED) {
unreachable.add(node);
break;
}
// MAX CHILDREN RULE
//
// If a node has N total children and M <= N are already unlocked,
// the remaining N - M are made unreachable.
final int maxChildren = node.getMaxChildren();
if (maxChildren > 0) {
int unlocked = 0;
final List<SkillTreeNode> locked = new ArrayList<>();
for (ParentInformation child : node.getChildren())
switch (playerData.getNodeState(child.getNode())) {
case LOCKED:
locked.add(child.getNode());
break;
case UNLOCKED:
unlocked++;
break;
}
if (unlocked >= maxChildren) unreachable.addAll(locked);
}
}
// PASS 3
//
// Propagate unreachability in O(V * C * P)
while (!unreachable.empty()) {
final SkillTreeNode node = unreachable.pop();
updated.add(node);
playerData.setNodeState(node, NodeState.FULLY_LOCKED);
for (ParentInformation child : node.getChildren()) // Propagate
if (!updated.contains(child.getNode()) && isUnreachable(child.getNode(), playerData))
unreachable.push(child.getNode());
}
// PASS 4
//
// Mark unlockable nodes, in O(V * P). This rule does not need propagation
// because the distance between the set of all unlocked nodes and the set
// of all unlockable nodes is at most 1 (unlockability is not "transitive")
pass4:
for (SkillTreeNode node : nodes.values()) {
if (playerData.getNodeState(node) != NodeState.LOCKED) continue;
// ROOT NODES
//
// Roots are either unlockable or unlocked.
if (node.isRoot()) {
playerData.setNodeState(node, NodeState.UNLOCKABLE);
continue;
}
boolean isFullyLocked = isFullyLockedFromSoftParent || isFullyLockedFromStrongParent || isFullyLockedFromIncompatibleParent;
boolean isUnlockable = isUnlockableFromSoftParent && isUnlockableFromStrongParent;
if (isFullyLocked)
playerData.setNodeState(node, SkillTreeStatus.FULLY_LOCKED);
else if (isUnlockable)
playerData.setNodeState(node, SkillTreeStatus.UNLOCKABLE);
else
playerData.setNodeState(node, SkillTreeStatus.LOCKED);
}
//We recursively call the algorithm for all the children of the current node
for (SkillTreeNode child : node.getChildren())
setupNodeStateFrom(child, playerData);
// STRONG & SOFT PARENTS
//
// For nodes with no strong/soft parents, the rule is nulled.
// All strong parents of any node must be unlocked for the node to be unlockable.
// One soft parent of any node must be unlocked for the node to be unlockable.
boolean soft = false, hasSoft = false;
for (ParentInformation parent : node.getParents()) {
if (parent.getType() == ParentType.STRONG && playerData.getNodeState(parent.getNode()) != NodeState.UNLOCKED)
continue pass4; // Keep the node locked
else if (!soft && parent.getType() == ParentType.SOFT) {
hasSoft = true;
if (playerData.getNodeState(parent.getNode()) == NodeState.UNLOCKED)
soft = true; // Cannot continue, must check for other strong parents
}
}
// At least one soft parent!
if (!hasSoft || soft) playerData.setNodeState(node, NodeState.UNLOCKABLE);
}
}
/**
* Returns null if it is not a node and returns the node type if it a node
*/
@Nullable
public boolean isNode(IntegerCoordinates coordinates) {
for (SkillTreeNode node : nodes.values()) {
if (node.getCoordinates().equals(coordinates))
private boolean isUnreachable(@NotNull SkillTreeNode node, @NotNull PlayerData playerData) {
// UNREACHABILITY RULES
//
// If at least one strong parent is unreachable, the node is unreachable too.
// If all soft parents are unreachable, the node is unreachable.
// This rule is the logical opposite of the reachability rule.
boolean soft = false, hasSoft = false;
for (ParentInformation parent : node.getParents()) {
if (parent.getType() == ParentType.STRONG && playerData.getNodeState(parent.getNode()) == NodeState.FULLY_LOCKED)
return true;
else if (!soft && parent.getType() == ParentType.SOFT) {
hasSoft = true;
if (playerData.getNodeState(parent.getNode()) != NodeState.FULLY_LOCKED)
soft = true; // Cannot continue, must check for other strong parents
}
}
return false;
return hasSoft && !soft;
}
public boolean isPath(IntegerCoordinates coordinates) {
return coordinatesPaths.keySet().contains(coordinates);
public boolean isNode(@NotNull IntegerCoordinates coordinates) {
return coordNodes.containsKey(coordinates);
}
public boolean isPath(@NotNull IntegerCoordinates coordinates) {
return coordPaths.containsKey(coordinates);
}
public boolean isPathOrNode(IntegerCoordinates coordinates) {
@ -269,17 +330,22 @@ public abstract class SkillTree implements RegisteredObject, PreloadedObject {
}
@NotNull
public SkillTreeNode getNode(IntegerCoordinates coords) {
return Objects.requireNonNull(coordinatesNodes.get(coords), "Could not find node in tree '" + id + "' with coordinates '" + coords.toString() + "'");
public SkillTreeNode getNode(@NotNull IntegerCoordinates coords) {
return Objects.requireNonNull(coordNodes.get(coords), "Could not find node in tree '" + id + "' with coordinates '" + coords + "'");
}
@Nullable
public SkillTreeNode getNodeOrNull(@NotNull IntegerCoordinates coords) {
return coordNodes.get(coords);
}
@NotNull
public SkillTreePath getPath(IntegerCoordinates coords) {
return Objects.requireNonNull(coordinatesPaths.get(coords), "Could not find path in tree '" + id + "' with coordinates '" + coords.toString() + "'");
public SkillTreePath getPath(@NotNull IntegerCoordinates coords) {
return Objects.requireNonNull(coordPaths.get(coords), "Could not find path in tree '" + id + "' with coordinates '" + coords + "'");
}
@NotNull
public SkillTreeNode getNode(String name) {
public SkillTreeNode getNode(@NotNull String name) {
return Objects.requireNonNull(nodes.get(name), "Could not find node in tree '" + id + "' with name '" + name + "'");
}
@ -291,7 +357,6 @@ public abstract class SkillTree implements RegisteredObject, PreloadedObject {
return icons.get(displayInfo);
}
public boolean isNode(String name) {
return nodes.containsKey(name);
}

View File

@ -0,0 +1,32 @@
package net.Indyuce.mmocore.skilltree.tree;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
public enum SkillTreeType {
/**
* Fully custom skill tree with manual inputs on path placement
*/
CUSTOM(CustomSkillTree::new),
/**
* A simpler skill tree pattern where neighbor nodes are instantly
* soft parents of one another.
*/
PROXIMITY(ProximitySkillTree::new),
;
private final Function<ConfigurationSection, SkillTree> constructor;
SkillTreeType(Function<ConfigurationSection, SkillTree> constructor) {
this.constructor = constructor;
}
@NotNull
public SkillTree construct(@NotNull ConfigurationSection config) {
return constructor.apply(config);
}
}

View File

@ -0,0 +1,82 @@
package net.Indyuce.mmocore.util;
import io.lumine.mythic.lib.UtilityMethods;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* The material and custom model-data of a node
*/
public class Icon {
private final Material material;
private final int modelData;
public Material getMaterial() {
return material;
}
public int getModelData() {
return modelData;
}
public Icon(@NotNull Material material, int modelData) {
this.material = material;
this.modelData = modelData;
}
public ItemStack toItem() {
final ItemStack stack = new ItemStack(material);
if (modelData > 0) {
final ItemMeta meta = stack.getItemMeta();
meta.setCustomModelData(modelData);
stack.setItemMeta(meta);
}
return stack;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Icon icon = (Icon) o;
return modelData == icon.modelData && material == icon.material;
}
@Override
public int hashCode() {
return Objects.hash(material, modelData);
}
@Override
public String toString() {
return "Icon{" + "material=" + material + ", customModelData=" + modelData + '}';
}
@NotNull
public static Icon from(@Nullable Object object) {
Validate.notNull(object, "Could not read icon");
if (object instanceof ConfigurationSection) {
final ConfigurationSection config = (ConfigurationSection) object;
final Material material = Material.valueOf(UtilityMethods.enumName(((ConfigurationSection) object).getString("item")));
final int modelData = config.getInt("model-data", config.getInt("custom-model-data")); // Backwards compatibility
return new Icon(material, modelData);
}
if (object instanceof String) {
final String[] split = ((String) object).split("[:.,]");
final Material mat = Material.valueOf(UtilityMethods.enumName(split[0]));
final int modelData = split.length == 1 ? 0 : Integer.parseInt(split[1]);
return new Icon(mat, modelData);
}
throw new RuntimeException("Needs either a string or configuration section");
}
}

View File

@ -35,7 +35,7 @@ public class PlayerListener implements Listener {
final PlayerData playerData = PlayerData.get(event.getHolder().getUniqueId());
final Player player = playerData.getPlayer();
playerData.setupSkillTree();
playerData.setupSkillTrees();
playerData.applyTemporaryTriggers();
playerData.getStats().updateStats(true); // TODO maybe duplicate?

View File

@ -91,7 +91,7 @@ items:
#- '&fSoft Parents: '
#- '{soft-parents}'
- ''
- '{node-lore}'
- '&7{node-lore}'
- ''
- '&e◆ Points Spent:&6 {point-spent}&e/&6{max-point-spent}'
- '&e◆ Current {skill-tree} &ePoints: &6{skill-tree-points} &e+ &6{global-points} &eGlobal'

View File

@ -68,7 +68,7 @@ items:
#- '&fSoft Parents: '
#- '{soft-parents}'
- ''
- '{node-lore}'
- '&7{node-lore}'
- ''
- '&e◆ Points Spent:&6 {point-spent}&e/&6{max-point-spent}'
- '&e◆ Current {skill-tree} &ePoints: &6{skill-tree-points} &e(&6{global-points} &eglobal)'

View File

@ -1,21 +1,18 @@
id: 'combat'
name: '&4Combat'
lore:
lore:
- '&6This skill tree is used for combat abilities!'
type: 'linked'
item: 'DIAMOND_AXE'
type: PROXIMITY
item: DIAMOND_AXE
max-point-spent: 20
nodes:
a1:
name: 'Cooldown Reduction'
name: 'Cooldown Reduction I'
coordinates:
x: -3
y: -2
max-level: 1
is-root: true
size: 1
point-consumed: 1
root: true
experience-table:
first_table_item:
triggers:
@ -25,14 +22,9 @@ nodes:
- "&eReduces item/class skill cooldowns (5%)"
1:
- "&eReduces item/class skill cooldowns (5%)"
a2:
name: 'Cooldown Reduction'
size: 1
point-consumed: 1
max-children: 1
name: 'Cooldown Reduction II'
experience-table:
first_table_item:
triggers:
@ -47,10 +39,7 @@ nodes:
- "&eReduces item/class skill cooldowns (20%)"
a3:
name: 'Cooldown Reduction'
size: 1
point-consumed: 1
max-children: 1
name: 'Cooldown Reduction III'
experience-table:
first_table_item:
triggers:
@ -64,13 +53,10 @@ nodes:
1:
- "&eReduces item/class skill cooldowns (30%)"
### B
### B
b1:
name: 'Critical Strike Chance'
size: 1
point-consumed: 1
max-children: 1
name: 'Crit Chance I'
experience-table:
first_table_item:
triggers:
@ -85,10 +71,7 @@ nodes:
- "&eCritical Strikes deal more damage in +%1 chance"
b2:
name: 'Critical Strike Chance'
size: 1
point-consumed: 1
max-children: 1
name: 'Crit Chance II'
experience-table:
first_table_item:
triggers:
@ -103,10 +86,7 @@ nodes:
- "&eCritical Strikes deal more damage in +%3 chance"
b3:
name: 'Critical Strike Chance'
size: 1
point-consumed: 1
max-children: 2
name: 'Crit Chance III'
experience-table:
first_table_item:
triggers:
@ -120,13 +100,10 @@ nodes:
1:
- "&eCritical Strikes deal more damage in +%5 chance"
## C
## C
c1:
name: 'Life Steal'
size: 1
point-consumed: 1
max-children: 1
name: 'Life Steal I'
experience-table:
first_table_item:
triggers:
@ -141,10 +118,7 @@ nodes:
- "&ePercentage of damage you gain back as health when inflicting weapon damage +%1"
c2:
name: 'Life Steal'
size: 1
point-consumed: 1
max-children: 1
name: 'Life Steal II'
experience-table:
first_table_item:
triggers:
@ -159,10 +133,7 @@ nodes:
- "&ePercentage of damage you gain back as health when inflicting weapon damage +%2"
c3:
name: 'Life Steal'
size: 1
point-consumed: 1
max-children: 1
name: 'Life Steal III'
experience-table:
first_table_item:
triggers:
@ -176,13 +147,10 @@ nodes:
1:
- "&ePercentage of damage you gain back as health when inflicting weapon damage +%3"
## D
## D
d1:
name: 'Damage Reduction'
size: 1
point-consumed: 1
max-children: 1
name: 'Resistance I'
experience-table:
first_table_item:
triggers:
@ -197,9 +165,7 @@ nodes:
- "&eReduces damage from any source in +%1"
d2:
name: 'Damage Reduction'
size: 1
point-consumed: 1
name: 'Resistance II'
max-children: 2
experience-table:
first_table_item:
@ -215,10 +181,7 @@ nodes:
- "&eReduces damage from any source in +%2"
d3:
name: 'Damage Reduction'
size: 1
point-consumed: 1
max-children: 1
name: 'Resistance III'
experience-table:
first_table_item:
triggers:
@ -232,13 +195,10 @@ nodes:
1:
- "&eReduces damage from any source in +%2"
## E
## E
e1:
name: 'Health Regeneration'
size: 1
point-consumed: 1
max-children: 1
name: 'Health Regen I'
experience-table:
first_table_item:
triggers:
@ -253,10 +213,7 @@ nodes:
- "&eHealth regen in pts/sec +1"
e2:
name: 'Health Regeneration'
size: 1
point-consumed: 1
max-children: 1
name: 'Health Regen II'
experience-table:
first_table_item:
triggers:
@ -271,10 +228,7 @@ nodes:
- "&eHealth regen in pts/sec +2"
e3:
name: 'Health Regeneration'
size: 1
point-consumed: 1
max-children: 1
name: 'Health Regen III'
experience-table:
first_table_item:
triggers:
@ -288,13 +242,10 @@ nodes:
1:
- "&eHealth regen in pts/sec +2"
## F
## F
f1:
name: 'Mana Regeneration'
size: 1
point-consumed: 1
max-children: 1
name: 'Mana Regen I'
experience-table:
first_table_item:
triggers:
@ -309,10 +260,7 @@ nodes:
- "&eMana regen in pts/sec +1"
f2:
name: 'Mana Regeneration'
size: 1
point-consumed: 1
max-children: 1
name: 'Mana Regen II'
experience-table:
first_table_item:
triggers:
@ -327,10 +275,7 @@ nodes:
- "&eMana regen in pts/sec +2"
f3:
name: 'Mana Regeneration'
size: 1
point-consumed: 1
max-children: 1
name: 'Mana Regen III'
experience-table:
first_table_item:
triggers:
@ -344,13 +289,10 @@ nodes:
1:
- "&eMana regen in pts/sec +2"
## G
## G
g1:
name: 'Magic Damage'
size: 1
point-consumed: 1
max-children: 1
name: 'Magic Damage I'
experience-table:
first_table_item:
triggers:
@ -365,10 +307,7 @@ nodes:
- "&eAdditional magic skill damage in +%1"
g2:
name: 'Magic Damage'
size: 1
point-consumed: 1
max-children: 1
name: 'Magic Damage II'
experience-table:
first_table_item:
triggers:
@ -383,10 +322,7 @@ nodes:
- "&eAdditional magic skill damage in +%2"
g3:
name: 'Magic Damage'
size: 1
point-consumed: 1
max-children: 1
name: 'Magic Damage III'
experience-table:
first_table_item:
triggers:
@ -400,13 +336,10 @@ nodes:
1:
- "&eAdditional magic skill damage in +%2"
## H
## H
h1:
name: 'Max Health'
size: 1
point-consumed: 1
max-children: 1
name: 'Max Health I'
experience-table:
first_table_item:
triggers:
@ -421,10 +354,7 @@ nodes:
- "&eAdditional amount of health in +%2"
h2:
name: 'Max Health'
size: 1
point-consumed: 1
max-children: 1
name: 'Max Health II'
experience-table:
first_table_item:
triggers:
@ -439,10 +369,7 @@ nodes:
- "&eAdditional amount of health in +%4"
h3:
name: 'Max Health'
size: 1
point-consumed: 1
max-children: 1
name: 'Max Health III'
experience-table:
first_table_item:
triggers:
@ -456,13 +383,10 @@ nodes:
1:
- "&eAdditional amount of health in +%4"
## J
## J
j1:
name: 'Weapon Damage'
size: 1
point-consumed: 1
max-children: 1
name: 'Weapon Damage I'
experience-table:
first_table_item:
triggers:
@ -477,10 +401,7 @@ nodes:
- "&eAdditional on-hit weapon damage in +%2."
j2:
name: 'Weapon Damage'
size: 1
point-consumed: 1
max-children: 1
name: 'Weapon Damage II'
experience-table:
first_table_item:
triggers:
@ -495,10 +416,7 @@ nodes:
- "&eAdditional on-hit weapon damage in +%4"
j3:
name: 'Weapon Damage'
size: 1
point-consumed: 1
max-children: 1
name: 'Weapon Damage III'
experience-table:
first_table_item:
triggers:
@ -512,16 +430,6 @@ nodes:
1:
- "&eAdditional on-hit weapon damage in +%4"
paths:
path1:
x: 2
y: 2
path2:
x: 2
y: 3
# This section is to have a specific display for the skill tree in the GUI.
# It is optional as if you don't fill it the config in gui/skill-tree.yml will be used.
# For each node it will first look if the node has a specific display, if not it will look if
@ -530,287 +438,103 @@ paths:
#display:
# paths:
# unlocked:
# up:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# right:
# item: "WHITE_DYE"
# custom-model-data: 0
# default:
# item: "WHITE_DYE"
# custom-model-data: 0
# up: 'WHITE_DYE:0'
# up-right: 'WHITE_DYE:0'
# up-left: 'WHITE_DYE:0'
# down-right: 'WHITE_DYE:0'
# down-left: 'WHITE_DYE:0'
# right: 'WHITE_DYE:0'
# default: 'WHITE_DYE:0'
# unlockable:
# up:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# right:
# item: "BLUE_DYE"
# custom-model-data: 0
# default:
# item: "BLUE_DYE"
# custom-model-data: 0
# up: 'BLUE_DYE:0'
# up-right: 'BLUE_DYE:0'
# up-left: 'BLUE_DYE:0'
# down-right: 'BLUE_DYE:0'
# down-left: 'BLUE_DYE:0'
# right: 'BLUE_DYE:0'
# default: 'BLUE_DYE:0'
# locked:
# up:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# right:
# item: "GRAY_DYE"
# custom-model-data: 0
# default:
# item: "GRAY_DYE"
# custom-model-data: 0
# up: 'GRAY_DYE:0'
# up-right: 'GRAY_DYE:0'
# up-left: 'GRAY_DYE:0'
# down-right: 'GRAY_DYE:0'
# down-left: 'GRAY_DYE:0'
# right: 'GRAY_DYE:0'
# default: 'GRAY_DYE:0'
# fully-locked:
# up:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# right:
# item: "BLACK_DYE"
# custom-model-data: 0
# default:
# item: "BLACK_DYE"
# custom-model-data: 0
# up: 'BLACK_DYE:0'
# up-right: 'BLACK_DYE:0'
# up-left: 'BLACK_DYE:0'
# down-right: 'BLACK_DYE:0'
# down-left: 'BLACK_DYE:0'
# right: 'BLACK_DYE:0'
# default: 'BLACK_DYE:0'
# nodes:
# unlocked:
# up-right-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'WHITE_CONCRETE:0'
# up-right-down: 'WHITE_CONCRETE:0'
# up-right-left: 'WHITE_CONCRETE:0'
# up-down-left: 'WHITE_CONCRETE:0'
# down-right-left: 'WHITE_CONCRETE:0'
# up-right: 'WHITE_CONCRETE:0'
# up-down: 'WHITE_CONCRETE:0'
# up-left: 'WHITE_CONCRETE:0'
# down-right: 'WHITE_CONCRETE:0'
# down-left: 'WHITE_CONCRETE:0'
# right-left: 'WHITE_CONCRETE:0'
# right: 'WHITE_CONCRETE:0'
# left: 'WHITE_CONCRETE:0'
# up: 'WHITE_CONCRETE:0'
# down: 'WHITE_CONCRETE:0'
# no-path: 'WHITE_CONCRETE:0'
# locked:
# up-right-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'GRAY_CONCRETE:0'
# up-right-down: 'GRAY_CONCRETE:0'
# up-right-left: 'GRAY_CONCRETE:0'
# up-down-left: 'GRAY_CONCRETE:0'
# down-right-left: 'GRAY_CONCRETE:0'
# up-right: 'GRAY_CONCRETE:0'
# up-down: 'GRAY_CONCRETE:0'
# up-left: 'GRAY_CONCRETE:0'
# down-right: 'GRAY_CONCRETE:0'
# down-left: 'GRAY_CONCRETE:0'
# right-left: 'GRAY_CONCRETE:0'
# right: 'GRAY_CONCRETE:0'
# left: 'GRAY_CONCRETE:0'
# up: 'GRAY_CONCRETE:0'
# down: 'GRAY_CONCRETE:0'
# no-path: 'GRAY_CONCRETE:0'
# unlockable:
# up-right-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'BLUE_CONCRETE:0'
# up-right-down: 'BLUE_CONCRETE:0'
# up-right-left: 'BLUE_CONCRETE:0'
# up-down-left: 'BLUE_CONCRETE:0'
# down-right-left: 'BLUE_CONCRETE:0'
# up-right: 'BLUE_CONCRETE:0'
# up-down: 'BLUE_CONCRETE:0'
# up-left: 'BLUE_CONCRETE:0'
# down-right: 'BLUE_CONCRETE:0'
# down-left: 'BLUE_CONCRETE:0'
# right-left: 'BLUE_CONCRETE:0'
# right: 'BLUE_CONCRETE:0'
# left: 'BLUE_CONCRETE:0'
# up: 'BLUE_CONCRETE:0'
# down: 'BLUE_CONCRETE:0'
# no-path: 'BLUE_CONCRETE:0'
# fully-locked:
# up-right-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'BLACK_CONCRETE:0'
# up-right-down: 'BLACK_CONCRETE:0'
# up-right-left: 'BLACK_CONCRETE:0'
# up-down-left: 'BLACK_CONCRETE:0'
# down-right-left: 'BLACK_CONCRETE:0'
# up-right: 'BLACK_CONCRETE:0'
# up-down: 'BLACK_CONCRETE:0'
# up-left: 'BLACK_CONCRETE:0'
# down-right: 'BLACK_CONCRETE:0'
# down-left: 'BLACK_CONCRETE:0'
# right-left: 'BLACK_CONCRETE:0'
# right: 'BLACK_CONCRETE:0'
# left: 'BLACK_CONCRETE:0'
# up: 'BLACK_CONCRETE:0'
# down: 'BLACK_CONCRETE:0'
# no-path: 'BLACK_CONCRETE:0'

View File

@ -1,35 +1,31 @@
id: 'general'
name: '&4General'
type: 'custom'
type: CUSTOM
item: 'DIAMOND_AXE'
lore:
- '&6This skill tree is used for combat abilities!'
- '&6General skill tree'
max-point-spent: 11
nodes:
#### A
a1:
name: 'Mana Regeneration'
name: 'Mana Regen I'
coordinates:
x: -3
y: -2
max-level: 1
is-root: true
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="MANA_REGENERATION";amount=1;type="FLAT"}'
lores:
0:
- "&eMana regen in pts/sec +1"
- "Mana regen in pts/sec +1"
1:
- "&eMana regen in pts/sec +1"
- "Mana regen in pts/sec +1"
a2:
name: 'Mana Regeneration'
name: 'Mana Regen II'
coordinates:
x: -2
y: -2
@ -38,20 +34,17 @@ nodes:
strong:
a1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="MANA_REGENERATION";amount=2;type="FLAT"}'
lores:
0:
- "&eMana regen in pts/sec +2"
- "Mana regen in pts/sec +2"
1:
- "&eMana regen in pts/sec +2"
- "Mana regen in pts/sec +2"
a3:
name: 'Mana Regeneration'
name: 'Mana Regen III'
coordinates:
x: -1
y: -1
@ -59,43 +52,36 @@ nodes:
parents:
strong:
a2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="MANA_REGENERATION";amount=2;type="FLAT"}'
lores:
0:
- "&eMana regen in pts/sec +2"
- "Mana regen in pts/sec +2"
1:
- "&eMana regen in pts/sec +2"
- "Mana regen in pts/sec +2"
#### B
b1:
name: 'Health Regeneration'
name: 'Health Regen I'
coordinates:
x: -3
y: -0
max-level: 1
is-root: true
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="HEALTH_REGENERATION";amount=1;type="FLAT"}'
lores:
0:
- "&eHealth regen in pts/sec +1"
- "Health regen in pts/sec +1"
1:
- "&eHealth regen in pts/sec +1"
- "Health regen in pts/sec +1"
b2:
name: 'Health Regeneration'
name: 'Health Regen II'
coordinates:
x: -2
y: -0
@ -104,21 +90,18 @@ nodes:
strong:
b1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="HEALTH_REGENERATION";amount=2;type="FLAT"}'
lores:
0:
- "&eHealth regen in pts/sec +2"
- "Health regen in pts/sec +2"
1:
- "&eHealth regen in pts/sec +2"
- "Health regen in pts/sec +2"
b3:
name: 'Health Regeneration'
name: 'Health Regen III'
coordinates:
x: -1
y: -0
@ -126,43 +109,36 @@ nodes:
parents:
strong:
b2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="HEALTH_REGENERATION";amount=2;type="FLAT"}'
lores:
0:
- "&eHealth regen in pts/sec +2"
- "Health regen in pts/sec +2"
1:
- "&eHealth regen in pts/sec +2"
- "Health regen in pts/sec +2"
#### C
c1:
name: 'Cooldown Reduction'
name: 'Cooldown Reduction I'
coordinates:
x: -3
y: 2
max-level: 1
is-root: true
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="COOLDOWN_REDUCTION";amount=5;type="FLAT"}'
lores:
0:
- "&eReduces cooldowns of item and player skills (5%)"
- "Reduces cooldowns of item and player skills (5%)"
1:
- "&eReduces cooldowns of item and player skills (5%)"
- "Reduces cooldowns of item and player skills (5%)"
c2:
name: 'Cooldown Reduction'
name: 'Cooldown Reduction II'
coordinates:
x: -2
y: 2
@ -171,21 +147,18 @@ nodes:
strong:
c1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="COOLDOWN_REDUCTION";amount=10;type="FLAT"}'
lores:
0:
- "&eReduces cooldowns of item and player skills (10%)"
- "Reduces cooldowns of item and player skills (10%)"
1:
- "&eReduces cooldowns of item and player skills (10%)"
- "Reduces cooldowns of item and player skills (10%)"
c3:
name: 'Cooldown Reduction'
name: 'Cooldown Reduction III'
coordinates:
x: -1
y: 1
@ -194,22 +167,19 @@ nodes:
strong:
c2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="COOLDOWN_REDUCTION";amount=15;type="FLAT"}'
lores:
0:
- "&eReduces cooldowns of item and player skills (15%)"
- "Reduces cooldowns of item and player skills (15%)"
1:
- "&eReduces cooldowns of item and player skills (15%)"
- "Reduces cooldowns of item and player skills (15%)"
#### D
d1:
name: 'Critical Strike Chance'
name: 'Crit Chance I'
coordinates:
x: 0
y: 0
@ -219,22 +189,19 @@ nodes:
a3: 1
b3: 1
c3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="CRITICAL_STRIKE_CHANCE";amount=2;type="FLAT"}'
lores:
0:
- "&eCritical Strikes deal more damage in +%2 chance"
- "Critical Strikes deal more damage in +%2 chance"
1:
- "&eCritical Strikes deal more damage in +%2 chance"
- "Critical Strikes deal more damage in +%2 chance"
d2:
name: 'Critical Strike Chance'
name: 'Crit Chance II'
coordinates:
x: 1
y: 0
@ -243,21 +210,18 @@ nodes:
strong:
d1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="CRITICAL_STRIKE_CHANCE";amount=3;type="FLAT"}'
lores:
0:
- "&eCritical Strikes deal more damage in % chance"
- "Critical Strikes deal more damage in % chance"
1:
- "&eCritical Strikes deal more damage in +%3 chance"
- "Critical Strikes deal more damage in +%3 chance"
d3:
name: 'Critical Strike Chance'
name: 'Crit Chance III'
coordinates:
x: 2
y: 0
@ -266,23 +230,20 @@ nodes:
strong:
d2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="CRITICAL_STRIKE_CHANCE";amount=5;type="FLAT"}'
lores:
0:
- "&eCritical Strikes deal more damage in % chance"
- "Critical Strikes deal more damage in % chance"
1:
- "&eCritical Strikes deal more damage in +%5 chance"
- "Critical Strikes deal more damage in +%5 chance"
#### E
e1:
name: 'Damage Reduction'
name: 'Damage Reduction I'
coordinates:
x: 2
y: 1
@ -291,21 +252,18 @@ nodes:
strong:
d3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="DAMAGE_REDUCTION";amount=1;type="FLAT"}'
lores:
0:
- "&eReduces damage from any source in %."
- "Reduces damage from any source in %."
1:
- "&eReduces damage from any source in +%1."
- "Reduces damage from any source in +%1."
e2:
name: 'Damage Reduction'
name: 'Damage Reduction II'
coordinates:
x: 2
y: 2
@ -314,21 +272,18 @@ nodes:
strong:
e1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="DAMAGE_REDUCTION";amount=2;type="FLAT"}'
lores:
0:
- "&eReduces damage from any source in %."
- "Reduces damage from any source in %."
1:
- "&eReduces damage from any source in +%2."
- "Reduces damage from any source in +%2."
e3:
name: 'Damage Reduction'
name: 'Damage Reduction III'
coordinates:
x: 3
y: 2
@ -337,22 +292,19 @@ nodes:
strong:
e2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="DAMAGE_REDUCTION";amount=2;type="FLAT"}'
lores:
0:
- "&eReduces damage from any source in %."
- "Reduces damage from any source in %."
1:
- "&eReduces damage from any source in +%2."
- "Reduces damage from any source in +%2."
#### F
f1:
name: 'Weapon Damage'
name: 'Force I'
coordinates:
x: 2
y: -1
@ -361,21 +313,18 @@ nodes:
strong:
d3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="WEAPON_DAMAGE";amount=1;type="FLAT"}'
lores:
0:
- "&eAdditional on-hit weapon damage in +%1."
- "Additional on-hit weapon damage in +%1."
1:
- "&eAdditional on-hit weapon damage in +%1."
- "Additional on-hit weapon damage in +%1."
f2:
name: 'Weapon Damage'
name: 'Force II'
coordinates:
x: 2
y: -2
@ -384,21 +333,18 @@ nodes:
strong:
f1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="WEAPON_DAMAGE";amount=2;type="FLAT"}'
lores:
0:
- "&eAdditional on-hit weapon damage in +%2."
- "Additional on-hit weapon damage in +%2."
1:
- "&eAdditional on-hit weapon damage in +%2."
- "Additional on-hit weapon damage in +%2."
f3:
name: 'Weapon Damage'
name: 'Force III'
coordinates:
x: 3
y: -2
@ -406,19 +352,16 @@ nodes:
parents:
strong:
f2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="WEAPON_DAMAGE";amount=2;type="FLAT"}'
lores:
0:
- "&eAdditional on-hit weapon damage in +%2."
- "Additional on-hit weapon damage in +%2."
1:
- "&eAdditional on-hit weapon damage in +%2."
- "Additional on-hit weapon damage in +%2."
##last line
@ -431,19 +374,16 @@ nodes:
parents:
strong:
f3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="LIFESTEAL";amount=3;type="FLAT"}'
lores:
0:
- "&ePercentage of damage you gain back as health when inflicting weapon damage."
- "Percentage of damage you gain back as health when inflicting weapon damage."
1:
- "&ePercentage of damage you gain back as health when inflicting weapon damage +%3"
- "Percentage of damage you gain back as health when inflicting weapon damage +%3"
j1:
name: 'Max Health'
@ -455,19 +395,16 @@ nodes:
soft:
g1: 1
h1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="LIFESTEAL";amount=5;type="FLAT"}'
lores:
0:
- "&eAdditional amount of health in +%5"
- "Additional amount of health in +%5"
1:
- "&eAdditional amount of health in +%5"
- "Additional amount of health in +%5"
h1:
name: 'Magic Damage'
@ -478,20 +415,16 @@ nodes:
parents:
strong:
e3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
- 'stat{stat="LIFESTEAL";amount=3;type="FLAT"}'
lores:
0:
- "&eAdditional magic skill damage in +%3"
- "Additional magic skill damage in +%3"
1:
- "&eAdditional magic skill damage in +%3"
- "Additional magic skill damage in +%3"
# This section is to have a specific display for the skill tree in the GUI.
# It is optional as if you don't fill it the config in gui/skill-tree.yml will be used.
@ -501,287 +434,103 @@ nodes:
#display:
# paths:
# unlocked:
# up:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# right:
# item: "WHITE_DYE"
# custom-model-data: 0
# default:
# item: "WHITE_DYE"
# custom-model-data: 0
# up: 'WHITE_DYE:0'
# up-right: 'WHITE_DYE:0'
# up-left: 'WHITE_DYE:0'
# down-right: 'WHITE_DYE:0'
# down-left: 'WHITE_DYE:0'
# right: 'WHITE_DYE:0'
# default: 'WHITE_DYE:0'
# unlockable:
# up:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# right:
# item: "BLUE_DYE"
# custom-model-data: 0
# default:
# item: "BLUE_DYE"
# custom-model-data: 0
# up: 'BLUE_DYE:0'
# up-right: 'BLUE_DYE:0'
# up-left: 'BLUE_DYE:0'
# down-right: 'BLUE_DYE:0'
# down-left: 'BLUE_DYE:0'
# right: 'BLUE_DYE:0'
# default: 'BLUE_DYE:0'
# locked:
# up:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# right:
# item: "GRAY_DYE"
# custom-model-data: 0
# default:
# item: "GRAY_DYE"
# custom-model-data: 0
# up: 'GRAY_DYE:0'
# up-right: 'GRAY_DYE:0'
# up-left: 'GRAY_DYE:0'
# down-right: 'GRAY_DYE:0'
# down-left: 'GRAY_DYE:0'
# right: 'GRAY_DYE:0'
# default: 'GRAY_DYE:0'
# fully-locked:
# up:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# right:
# item: "BLACK_DYE"
# custom-model-data: 0
# default:
# item: "BLACK_DYE"
# custom-model-data: 0
# up: 'BLACK_DYE:0'
# up-right: 'BLACK_DYE:0'
# up-left: 'BLACK_DYE:0'
# down-right: 'BLACK_DYE:0'
# down-left: 'BLACK_DYE:0'
# right: 'BLACK_DYE:0'
# default: 'BLACK_DYE:0'
# nodes:
# unlocked:
# up-right-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'WHITE_CONCRETE:0'
# up-right-down: 'WHITE_CONCRETE:0'
# up-right-left: 'WHITE_CONCRETE:0'
# up-down-left: 'WHITE_CONCRETE:0'
# down-right-left: 'WHITE_CONCRETE:0'
# up-right: 'WHITE_CONCRETE:0'
# up-down: 'WHITE_CONCRETE:0'
# up-left: 'WHITE_CONCRETE:0'
# down-right: 'WHITE_CONCRETE:0'
# down-left: 'WHITE_CONCRETE:0'
# right-left: 'WHITE_CONCRETE:0'
# right: 'WHITE_CONCRETE:0'
# left: 'WHITE_CONCRETE:0'
# up: 'WHITE_CONCRETE:0'
# down: 'WHITE_CONCRETE:0'
# no-path: 'WHITE_CONCRETE:0'
# locked:
# up-right-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'GRAY_CONCRETE:0'
# up-right-down: 'GRAY_CONCRETE:0'
# up-right-left: 'GRAY_CONCRETE:0'
# up-down-left: 'GRAY_CONCRETE:0'
# down-right-left: 'GRAY_CONCRETE:0'
# up-right: 'GRAY_CONCRETE:0'
# up-down: 'GRAY_CONCRETE:0'
# up-left: 'GRAY_CONCRETE:0'
# down-right: 'GRAY_CONCRETE:0'
# down-left: 'GRAY_CONCRETE:0'
# right-left: 'GRAY_CONCRETE:0'
# right: 'GRAY_CONCRETE:0'
# left: 'GRAY_CONCRETE:0'
# up: 'GRAY_CONCRETE:0'
# down: 'GRAY_CONCRETE:0'
# no-path: 'GRAY_CONCRETE:0'
# unlockable:
# up-right-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'BLUE_CONCRETE:0'
# up-right-down: 'BLUE_CONCRETE:0'
# up-right-left: 'BLUE_CONCRETE:0'
# up-down-left: 'BLUE_CONCRETE:0'
# down-right-left: 'BLUE_CONCRETE:0'
# up-right: 'BLUE_CONCRETE:0'
# up-down: 'BLUE_CONCRETE:0'
# up-left: 'BLUE_CONCRETE:0'
# down-right: 'BLUE_CONCRETE:0'
# down-left: 'BLUE_CONCRETE:0'
# right-left: 'BLUE_CONCRETE:0'
# right: 'BLUE_CONCRETE:0'
# left: 'BLUE_CONCRETE:0'
# up: 'BLUE_CONCRETE:0'
# down: 'BLUE_CONCRETE:0'
# no-path: 'BLUE_CONCRETE:0'
# fully-locked:
# up-right-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-down-left: 'BLACK_CONCRETE:0'
# up-right-down: 'BLACK_CONCRETE:0'
# up-right-left: 'BLACK_CONCRETE:0'
# up-down-left: 'BLACK_CONCRETE:0'
# down-right-left: 'BLACK_CONCRETE:0'
# up-right: 'BLACK_CONCRETE:0'
# up-down: 'BLACK_CONCRETE:0'
# up-left: 'BLACK_CONCRETE:0'
# down-right: 'BLACK_CONCRETE:0'
# down-left: 'BLACK_CONCRETE:0'
# right-left: 'BLACK_CONCRETE:0'
# right: 'BLACK_CONCRETE:0'
# left: 'BLACK_CONCRETE:0'
# up: 'BLACK_CONCRETE:0'
# down: 'BLACK_CONCRETE:0'
# no-path: 'BLACK_CONCRETE:0'

View File

@ -0,0 +1,185 @@
id: 'example_loop'
name: '&4Loop Skill Tree'
lore:
- '&6This skill tree demonstrates how to'
- '&6do loops with node parents'
type: CUSTOM
item: DIAMOND_AXE
max-point-spent: 20
nodes:
a1:
name: 'Loop I'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a2: 1
e1: 1
coordinates: 0,1
a2:
name: 'Loop II'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a3: 1
coordinates: -1,1
a3:
name: 'Loop III'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a4: 1
e4: 1
coordinates: -1,0
a4:
name: 'Loop IV'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a5: 1
coordinates: -1,-1
a5:
name: 'Loop V'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a6: 1
e2: 1
coordinates: 0,-1
a6:
name: 'Loop VI'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a7: 1
coordinates: 1,-1
a7:
name: 'Loop VII'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a8: 1
e3: 1
coordinates: 1,0
a8:
name: 'Loop VIII'
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
parents:
soft:
a1: 1
coordinates: 1,1
e1:
name: 'Entry I'
root: true
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
coordinates: 0,2
e2:
name: 'Entry II'
root: true
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
coordinates: 0,-2
e3:
name: 'Entry III'
root: true
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
coordinates: 2,0
e4:
name: 'Entry IV'
root: true
experience-table: {}
lores:
0:
- "&eLorem ipsum dolor sit amet"
coordinates: -2,0

View File

@ -1,24 +1,20 @@
id: 'mage-arcane-mage'
name: '&4Mage'
type: 'custom'
type: CUSTOM
item: 'DIAMOND_AXE'
lore:
- '&6This skill tree is used for Mage & Arcane Mage class only!'
lore:
- '&6Available to Mages only'
max-point-spent: 21
nodes:
#### A
#### A
a1:
name: 'Mana Regeneration'
name: 'Mana Regen I'
coordinates:
x: 1
y: -2
max-level: 1
is-root: true
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -29,18 +25,15 @@ nodes:
1:
- "&eMana regen in pts/sec +1"
a2:
name: 'Mana Regeneration'
name: 'Mana Regen II'
coordinates:
x: 0
y: -2
parents:
strong:
parents:
strong:
a1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -51,18 +44,15 @@ nodes:
1:
- "&eMana regen in pts/sec +2"
a3:
name: 'Mana Regeneration'
name: 'Mana Regen III'
coordinates:
x: -1
y: -2
parents:
strong:
parents:
strong:
a2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -73,21 +63,18 @@ nodes:
1:
- "&eMana regen in pts/sec +2"
## B
## B
b1:
name: 'Skill Damage'
name: 'Offense I'
coordinates:
x: -2
y: -1
parents:
strong:
parents:
strong:
a3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -98,18 +85,15 @@ nodes:
1:
- "&eAdditional ability damage in +%5"
b2:
name: 'Skill Damage'
name: 'Offense II'
coordinates:
x: -3
y: 0
parents:
strong:
parents:
strong:
b1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -120,18 +104,15 @@ nodes:
1:
- "&eAdditional ability damage in +%5"
b3:
name: 'Skill Damage'
name: 'Offense III'
coordinates:
x: -2
y: 1
parents:
strong:
parents:
strong:
b2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -142,21 +123,18 @@ nodes:
1:
- "&eAdditional ability damage in +%10"
## C
## C
c1:
name: 'Spell Vampirism'
name: 'Spell Vamp I'
coordinates:
x: -2
y: 2
parents:
strong:
parents:
strong:
b3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -167,18 +145,15 @@ nodes:
1:
- "&ePercentage of damage you gain back as health when inflicting skill damage +%2"
c2:
name: 'Spell Vampirism'
name: 'Spell Vamp II'
coordinates:
x: -1
y: 2
parents:
strong:
parents:
strong:
c1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -189,18 +164,15 @@ nodes:
1:
- "&ePercentage of damage you gain back as health when inflicting skill damage +%2"
c3:
name: 'Spell Vampirism'
name: 'Spell Vamp III'
coordinates:
x: 0
y: 2
parents:
strong:
parents:
strong:
c2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -211,21 +183,18 @@ nodes:
1:
- "&ePercentage of damage you gain back as health when inflicting skill damage +%6"
## D
## D
d1:
name: 'Additional Experience'
name: 'Additional Exp I'
coordinates:
x: 0
y: 1
parents:
strong:
parents:
strong:
c3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -236,18 +205,15 @@ nodes:
1:
- "&eAdditional MMOCore main class experience +%2"
d2:
name: 'Additional Experience'
name: 'Additional Exp II'
coordinates:
x: 0
y: 0
parents:
strong:
parents:
strong:
d1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -258,18 +224,15 @@ nodes:
1:
- "&eAdditional MMOCore main class experience +%3"
d3:
name: 'Additional Experience'
name: 'Additional Exp III'
coordinates:
x: 1
y: 0
parents:
strong:
parents:
strong:
d2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -280,21 +243,18 @@ nodes:
1:
- "&eAdditional MMOCore main class experience +%5"
## E
## E
e1:
name: 'Magic Damage Reduction'
name: 'Magic Resistance I'
coordinates:
x: 2
y: 0
parents:
strong:
parents:
strong:
d3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -305,18 +265,15 @@ nodes:
1:
- "&eReduce magic damage dealt by potions %5"
e2:
name: 'Magic Damage Reduction'
name: 'Magic Resistance II'
coordinates:
x: 2
y: 1
parents:
strong:
parents:
strong:
e1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -327,18 +284,15 @@ nodes:
1:
- "&eReduce magic damage dealt by potions %5"
e3:
name: 'Magic Damage Reduction'
name: 'Magic Resistance III'
coordinates:
x: 2
y: 2
parents:
strong:
parents:
strong:
e2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -349,21 +303,18 @@ nodes:
1:
- "&eReduce magic damage dealt by potions %10"
## F
## F
f1:
name: 'Skill Critical Strike Chance'
name: 'Skill Crit Chance I'
coordinates:
x: 3
y: 2
parents:
strong:
parents:
strong:
e3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -374,18 +325,15 @@ nodes:
1:
- "&eIncreases the chance of dealing skill crits +%5"
f2:
name: 'Skill Critical Strike Chance'
name: 'Skill Crit Chance II'
coordinates:
x: 4
y: 2
parents:
strong:
parents:
strong:
f1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -396,18 +344,15 @@ nodes:
1:
- "&eIncreases the chance of dealing skill crits +%10"
f3:
name: 'Skill Critical Strike Chance'
name: 'Skill Crit Chance III'
coordinates:
x: 4
y: 1
parents:
strong:
parents:
strong:
f2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -418,21 +363,18 @@ nodes:
1:
- "&eIncreases the chance of dealing skill crits +%15"
## G
## G
g1:
name: 'Magic Damage'
name: 'Skills IV'
coordinates:
x: 4
y: 0
parents:
strong:
parents:
strong:
f3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -443,18 +385,15 @@ nodes:
1:
- "&eAdditional magic skill damage in +%1"
g2:
name: 'Magic Damage'
name: 'Skills V'
coordinates:
x: 4
y: -1
parents:
strong:
parents:
strong:
g1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -465,18 +404,15 @@ nodes:
1:
- "&eAdditional magic skill damage in +%2"
g3:
name: 'Magic Damage'
name: 'Skills VI'
coordinates:
x: 4
y: -2
parents:
strong:
parents:
strong:
g2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -486,303 +422,3 @@ nodes:
- "&eAdditional magic skill damage in +%2"
1:
- "&eAdditional magic skill damage in +%2"
# This section is to have a specific display for the skill tree in the GUI.
# It is optional as if you don't fill it the config in gui/skill-tree.yml will be used.
# For each node it will first look if the node has a specific display, if not it will look if
# the display is defined in this section. If not it will use the default display in gui/skill-tree.yml.
#
#display:
# paths:
# unlocked:
# up:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# right:
# item: "WHITE_DYE"
# custom-model-data: 0
# default:
# item: "WHITE_DYE"
# custom-model-data: 0
# unlockable:
# up:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# right:
# item: "BLUE_DYE"
# custom-model-data: 0
# default:
# item: "BLUE_DYE"
# custom-model-data: 0
# locked:
# up:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# right:
# item: "GRAY_DYE"
# custom-model-data: 0
# default:
# item: "GRAY_DYE"
# custom-model-data: 0
# fully-locked:
# up:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# right:
# item: "BLACK_DYE"
# custom-model-data: 0
# default:
# item: "BLACK_DYE"
# custom-model-data: 0
# nodes:
# unlocked:
# up-right-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# locked:
# up-right-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# unlockable:
# up-right-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# fully-locked:
# up-right-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLACK_CONCRETE"
# custom-model-data: 0

View File

@ -1,24 +1,20 @@
id: 'rogue-marksman'
name: '&4Rogue&Marksman'
type: 'custom'
name: '&4Rogue & Marksman'
type: CUSTOM
item: 'DIAMOND_AXE'
lore:
- '&6This skill tree is used for Rogue & Marksman class only!'
- '&6Available to Rogues and Marksmen only'
max-point-spent: 21
nodes:
#### A
a1:
name: 'Additional Experience'
name: 'Additional Exp'
coordinates:
x: -3
y: 0
max-level: 1
is-root: true
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -29,7 +25,7 @@ nodes:
1:
- "&eAdditional MMOCore main class experience +%2"
a2:
name: 'Additional Experience'
name: 'Additional Exp II'
coordinates:
x: -2
y: 0
@ -38,9 +34,6 @@ nodes:
strong:
a1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -51,7 +44,7 @@ nodes:
1:
- "&eAdditional MMOCore main class experience +%3"
a3:
name: 'Additional Experience'
name: 'Additional Exp III'
coordinates:
x: -1
y: 0
@ -59,10 +52,7 @@ nodes:
parents:
strong:
a2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -76,7 +66,7 @@ nodes:
## B
b1:
name: 'Arrow Velocity'
name: 'Shooting Force I'
coordinates:
x: -1
y: -1
@ -85,9 +75,6 @@ nodes:
strong:
a3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -98,7 +85,7 @@ nodes:
1:
- "&eDetermines how far your weapon can shoot +%10"
b2:
name: 'Arrow Velocity'
name: 'Shooting Force II'
coordinates:
x: -1
y: -2
@ -107,9 +94,6 @@ nodes:
strong:
b1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -120,7 +104,7 @@ nodes:
1:
- "&eDetermines how far your weapon can shoot +%15"
b3:
name: 'Arrow Velocity'
name: 'Shooting Force III'
coordinates:
x: 0
y: -2
@ -128,10 +112,7 @@ nodes:
parents:
strong:
b2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -145,7 +126,7 @@ nodes:
## C
c1:
name: 'Life Steal'
name: 'Sustain I'
coordinates:
x: 1
y: -2
@ -153,10 +134,7 @@ nodes:
parents:
strong:
b3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -167,7 +145,7 @@ nodes:
1:
- "&ePercentage of damage you gain back as health when inflicting weapon damage +%1"
c2:
name: 'Life Steal'
name: 'Sustain II'
coordinates:
x: 2
y: -2
@ -175,10 +153,7 @@ nodes:
parents:
strong:
c1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -189,7 +164,7 @@ nodes:
1:
- "&ePercentage of damage you gain back as health when inflicting weapon damage +%2"
c3:
name: 'Life Steal'
name: 'Sustain III'
coordinates:
x: 3
y: -2
@ -197,10 +172,7 @@ nodes:
parents:
strong:
c2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -214,7 +186,7 @@ nodes:
## D
d1:
name: 'Knockback Resistance'
name: 'Knockback Resistance I'
coordinates:
x: 3
y: -1
@ -222,10 +194,7 @@ nodes:
parents:
strong:
c3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -236,7 +205,7 @@ nodes:
1:
- "&eThe chance of you to block the knockback +%5"
d2:
name: 'Knockback Resistance'
name: 'Knockback Resistance II'
coordinates:
x: 4
y: -1
@ -244,10 +213,7 @@ nodes:
parents:
strong:
d1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -258,7 +224,7 @@ nodes:
1:
- "&eThe chance of you to block the knockback +%5"
d3:
name: 'Knockback Resistance'
name: 'Knockback Resistance III'
coordinates:
x: 4
y: 0
@ -266,10 +232,7 @@ nodes:
parents:
strong:
d2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -283,7 +246,7 @@ nodes:
## E
e1:
name: 'Critical Strike Chance'
name: 'Crit Chance I'
coordinates:
x: 4
y: 1
@ -291,10 +254,7 @@ nodes:
parents:
strong:
d3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -305,7 +265,7 @@ nodes:
1:
- "&eCritical Strikes deal more damage in +%2 chance"
e2:
name: 'Critical Strike Chance'
name: 'Crit Chance II'
coordinates:
x: 4
y: 2
@ -313,10 +273,7 @@ nodes:
parents:
strong:
e1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -327,7 +284,7 @@ nodes:
1:
- "&eCritical Strikes deal more damage in +%3 chance"
e3:
name: 'Critical Strike Chance'
name: 'Crit Chance III'
coordinates:
x: 3
y: 2
@ -335,10 +292,7 @@ nodes:
parents:
strong:
e2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -352,7 +306,7 @@ nodes:
## F
f1:
name: 'Movement Speed'
name: 'Agility I'
coordinates:
x: 2
y: 2
@ -360,10 +314,7 @@ nodes:
parents:
strong:
e3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -374,7 +325,7 @@ nodes:
1:
- "&eMovement Speed increase walk speed. +%2"
f2:
name: 'Movement Speed'
name: 'Agility II'
coordinates:
x: 2
y: 1
@ -382,10 +333,7 @@ nodes:
parents:
strong:
f1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -396,7 +344,7 @@ nodes:
1:
- "&eMovement Speed increase walk speed. +%2"
f3:
name: 'Movement Speed'
name: 'Agility III'
coordinates:
x: 1
y: 1
@ -404,10 +352,7 @@ nodes:
parents:
strong:
f2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -421,7 +366,7 @@ nodes:
## G
g1:
name: 'Projectile Damage'
name: 'Projectile Damage I'
coordinates:
x: 0
y: 1
@ -429,10 +374,7 @@ nodes:
parents:
strong:
f3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -443,7 +385,7 @@ nodes:
1:
- "&eAdditional skill/weapon projectile damage +%2"
g2:
name: 'Projectile Damage'
name: 'Projectile Damage II'
coordinates:
x: 0
y: 2
@ -451,10 +393,7 @@ nodes:
parents:
strong:
g1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -465,7 +404,7 @@ nodes:
1:
- "&eAdditional skill/weapon projectile damage +%2"
g3:
name: 'Projectile Damage'
name: 'Projectile Damage III'
coordinates:
x: -1
y: 2
@ -473,10 +412,7 @@ nodes:
parents:
strong:
g2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -486,299 +422,3 @@ nodes:
- "&eAdditional skill/weapon projectile damage +%6"
1:
- "&eAdditional skill/weapon projectile damage +%6"
# This section is to have a specific display for the skill tree in the GUI.
# It is optional as if you don't fill it the config in gui/skill-tree.yml will be used.
# For each node it will first look if the node has a specific display, if not it will look if
# the display is defined in this section. If not it will use the default display in gui/skill-tree.yml.
#
#display:
# paths:
# unlocked:
# up:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# right:
# item: "WHITE_DYE"
# custom-model-data: 0
# default:
# item: "WHITE_DYE"
# custom-model-data: 0
# unlockable:
# up:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# right:
# item: "BLUE_DYE"
# custom-model-data: 0
# default:
# item: "BLUE_DYE"
# custom-model-data: 0
# locked:
# up:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# right:
# item: "GRAY_DYE"
# custom-model-data: 0
# default:
# item: "GRAY_DYE"
# custom-model-data: 0
# fully-locked:
# up:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# right:
# item: "BLACK_DYE"
# custom-model-data: 0
# default:
# item: "BLACK_DYE"
# custom-model-data: 0
# nodes:
# unlocked:
# up-right-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# locked:
# up-right-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# unlockable:
# up-right-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# fully-locked:
# up-right-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLACK_CONCRETE"
# custom-model-data: 0

View File

@ -1,24 +1,20 @@
id: 'warrior-paladin'
name: '&4Warrior&Paladin'
name: '&4Warrior & Paladin'
type: 'custom'
item: 'DIAMOND_AXE'
lore:
- '&6This skill tree is used for Warrior and Paladin class only!'
- '&6Available to strong bois, Warriors and Paladins only'
max-point-spent: 21
nodes:
#### A
a1:
name: 'Attack Speed'
name: 'Attack Speed I'
coordinates:
x: -1
y: 2
max-level: 1
is-root: true
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -29,7 +25,7 @@ nodes:
1:
- "&eThe speed at which your weapon strikes. +%5"
a2:
name: 'Attack Speed'
name: 'Attack Speed II'
coordinates:
x: -2
y: 1
@ -38,9 +34,6 @@ nodes:
strong:
a1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -51,7 +44,7 @@ nodes:
1:
- "&eThe speed at which your weapon strikes. +%10"
a3:
name: 'Attack Speed'
name: 'Attack Speed III'
coordinates:
x: -3
y: 0
@ -59,10 +52,7 @@ nodes:
parents:
strong:
a2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -76,7 +66,7 @@ nodes:
## B
b1:
name: 'Health Regeneration'
name: 'Health Regen I'
coordinates:
x: -2
y: -1
@ -85,9 +75,6 @@ nodes:
strong:
a3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -98,7 +85,7 @@ nodes:
1:
- "&eIncreases health regeneration by 1%"
b2:
name: 'Health Regeneration'
name: 'Health Regen II'
coordinates:
x: -1
y: -2
@ -107,9 +94,6 @@ nodes:
strong:
b1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -120,7 +104,7 @@ nodes:
1:
- "&eIncreases health regeneration by 1%"
b3:
name: 'Health Regeneration'
name: 'Health Regen III'
coordinates:
x: 0
y: -2
@ -128,10 +112,7 @@ nodes:
parents:
strong:
b2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -145,7 +126,7 @@ nodes:
## C
c1:
name: 'Physical Damage Reduction'
name: 'Resistance I'
coordinates:
x: 0
y: -1
@ -154,9 +135,6 @@ nodes:
strong:
b3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -167,7 +145,7 @@ nodes:
1:
- "&eReduces physical damage In -%5"
c2:
name: 'Physical Damage Reduction'
name: 'Resistance II'
coordinates:
x: -1
y: 0
@ -176,9 +154,6 @@ nodes:
strong:
c1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -189,7 +164,7 @@ nodes:
1:
- "&eReduces physical damage In -%10"
c3:
name: 'Physical Damage Reduction'
name: 'Resistance III'
coordinates:
x: 0
y: 1
@ -197,10 +172,7 @@ nodes:
parents:
strong:
c2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -214,7 +186,7 @@ nodes:
## D
d1:
name: 'Parry Rating'
name: 'Parrying I'
coordinates:
x: 1
y: 1
@ -223,9 +195,6 @@ nodes:
strong:
c3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -236,7 +205,7 @@ nodes:
1:
- "&eThe chance to parry an attack. Parrying negates the damage and knocks the attacker back +%5"
d2:
name: 'Parry Rating'
name: 'Parrying II'
coordinates:
x: 2
y: 1
@ -245,9 +214,6 @@ nodes:
strong:
d1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -258,7 +224,7 @@ nodes:
1:
- "&eThe chance to parry an attack. Parrying negates the damage and knocks the attacker back +%5"
d3:
name: 'Parry Rating'
name: 'Parrying III'
coordinates:
x: 2
y: 2
@ -266,10 +232,7 @@ nodes:
parents:
strong:
d2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -283,7 +246,7 @@ nodes:
## E
e1:
name: 'Knockback Resistance'
name: 'Toughness I'
coordinates:
x: 3
y: 2
@ -292,9 +255,6 @@ nodes:
strong:
d3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -305,7 +265,7 @@ nodes:
1:
- "&eThe chance of you to block the knockback +%5"
e2:
name: 'Knockback Resistance'
name: 'Toughness II'
coordinates:
x: 4
y: 2
@ -314,9 +274,6 @@ nodes:
strong:
e1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -327,7 +284,7 @@ nodes:
1:
- "&eThe chance of you to block the knockback +%5"
e3:
name: 'Knockback Resistance'
name: 'Toughness III'
coordinates:
x: 4
y: 1
@ -335,10 +292,7 @@ nodes:
parents:
strong:
e2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -352,7 +306,7 @@ nodes:
## F
f1:
name: 'Dodge Rating'
name: 'Dodging I'
coordinates:
x: 4
y: 0
@ -361,9 +315,6 @@ nodes:
strong:
e3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -374,7 +325,7 @@ nodes:
1:
- "&eThe change to dodge an attack. Dodging completely negates the attack damage. +%2"
f2:
name: 'Dodge Rating'
name: 'Dodging II'
coordinates:
x: 4
y: -1
@ -383,9 +334,6 @@ nodes:
strong:
f1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -396,7 +344,7 @@ nodes:
1:
- "&eThe change to dodge an attack. Dodging completely negates the attack damage. +%3"
f3:
name: 'Dodge Rating'
name: 'Dodging III'
coordinates:
x: 4
y: -2
@ -404,10 +352,7 @@ nodes:
parents:
strong:
f2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -421,7 +366,7 @@ nodes:
## G
g1:
name: 'Weapon Damage'
name: 'Weapon Damage I'
coordinates:
x: 3
y: -2
@ -430,9 +375,6 @@ nodes:
strong:
f3: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -443,7 +385,7 @@ nodes:
1:
- "&eAdditional on-hit weapon damage in +%1."
g2:
name: 'Weapon Damage'
name: 'Weapon Damage II'
coordinates:
x: 2
y: -2
@ -452,9 +394,6 @@ nodes:
strong:
g1: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -465,7 +404,7 @@ nodes:
1:
- "&eAdditional on-hit weapon damage in +%2."
g3:
name: 'Weapon Damage'
name: 'Weapon Damage III'
coordinates:
x: 2
y: -1
@ -473,10 +412,7 @@ nodes:
parents:
strong:
g2: 1
max-level: 1
size: 1
point-consumed: 1
experience-table:
first_table_item:
triggers:
@ -486,297 +422,3 @@ nodes:
- "&eAdditional on-hit weapon damage in +%2."
1:
- "&eAdditional on-hit weapon damage in +%2."
# This section is to have a specific display for the skill tree in the GUI.
# It is optional as if you don't fill it the config in gui/skill-tree.yml will be used.
# For each node it will first look if the node has a specific display, if not it will look if
# the display is defined in this section. If not it will use the default display in gui/skill-tree.yml.
#
#display:
# paths:
# unlocked:
# up:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# up-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-right:
# item: "WHITE_DYE"
# custom-model-data: 0
# down-left:
# item: "WHITE_DYE"
# custom-model-data: 0
# right:
# item: "WHITE_DYE"
# custom-model-data: 0
# default:
# item: "WHITE_DYE"
# custom-model-data: 0
# unlockable:
# up:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# up-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-right:
# item: "BLUE_DYE"
# custom-model-data: 0
# down-left:
# item: "BLUE_DYE"
# custom-model-data: 0
# right:
# item: "BLUE_DYE"
# custom-model-data: 0
# default:
# item: "BLUE_DYE"
# custom-model-data: 0
# locked:
# up:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# up-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-right:
# item: "GRAY_DYE"
# custom-model-data: 0
# down-left:
# item: "GRAY_DYE"
# custom-model-data: 0
# right:
# item: "GRAY_DYE"
# custom-model-data: 0
# default:
# item: "GRAY_DYE"
# custom-model-data: 0
# fully-locked:
# up:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# up-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-right:
# item: "BLACK_DYE"
# custom-model-data: 0
# down-left:
# item: "BLACK_DYE"
# custom-model-data: 0
# right:
# item: "BLACK_DYE"
# custom-model-data: 0
# default:
# item: "BLACK_DYE"
# custom-model-data: 0
# nodes:
# unlocked:
# up-right-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# right:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# left:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# up:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# down:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "WHITE_CONCRETE"
# custom-model-data: 0
# locked:
# up-right-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# right:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# left:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# up:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# down:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "GRAY_CONCRETE"
# custom-model-data: 0
# unlockable:
# up-right-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLUE_CONCRETE"
# custom-model-data: 0
# fully-locked:
# up-right-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right-left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# right:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# left:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# up:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# down:
# item: "BLACK_CONCRETE"
# custom-model-data: 0
# no-path:
# item: "BLACK_CONCRETE"
# custom-model-data: 0