More display options for skill, class skill tree node/paths icons

This commit is contained in:
Jules 2025-07-20 19:11:39 +02:00
parent 2aa78c4f2b
commit d93f0d7062
38 changed files with 580 additions and 446 deletions

View File

@ -4,6 +4,7 @@ import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.api.MMOLineConfig;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.gui.util.IconOptions;
import io.lumine.mythic.lib.player.modifier.ModifierSource;
import io.lumine.mythic.lib.player.skill.PassiveSkill;
import io.lumine.mythic.lib.script.Script;
@ -33,15 +34,12 @@ 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;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -51,7 +49,7 @@ import java.util.logging.Level;
public class PlayerClass implements ExperienceObject, PreloadedObject {
private final String name, id, actionBarFormat;
private final List<String> description = new ArrayList<>(), attrDescription = new ArrayList<>();
private final ItemStack icon;
private final IconOptions icon;
private final Map<ClassOption, Boolean> options = new HashMap<>();
private final int maxLevel, displayOrder;
@ -101,13 +99,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
this.id = UtilityMethods.enumName(id);
name = MythicLib.plugin.parseColors(config.getString("display.name", "INVALID DISPLAY NAME"));
icon = Icon.from(config.get("display.item", "BARRIER")).toItem();
if (config.contains("display.texture") && icon.getType() == Material.PLAYER_HEAD) {
ItemMeta meta = icon.getItemMeta();
UtilityMethods.setTextureValue((SkullMeta) meta, config.getString("display.texture"));
icon.setItemMeta(meta);
}
icon = IconOptions.from(config.get("display.item"));
for (String string : config.getStringList("display.lore"))
description.add(ChatColor.GRAY + MythicLib.plugin.parseColors(string));
@ -141,7 +133,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
try {
skillTrees.add(MMOCore.plugin.skillTreeManager.get(str));
} catch (Exception e) {
MMOCore.log(Level.WARNING, "Could not find skill tree with ID: " + str);
MMOCore.log(Level.WARNING, "Skill tree '" + str + "' not found for player class " + getId());
}
// Class-specific scripts
@ -276,7 +268,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
comboMap = null;
castParticle = new CastingParticle(VParticle.INSTANT_EFFECT.get());
actionBarFormat = "";
this.icon = new ItemStack(material);
this.icon = new IconOptions(material);
setOption(ClassOption.DISPLAY, false);
setOption(ClassOption.DEFAULT, false);
for (PlayerResource resource : PlayerResource.values())
@ -336,8 +328,14 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
return expTable != null;
}
@NotNull
public IconOptions getRawIcon() {
return icon;
}
@Deprecated
public ItemStack getIcon() {
return icon.clone();
return icon.toItemStack();
}
@Nullable

View File

@ -61,7 +61,7 @@ public class MMOCoreUtils {
* @param current Current value of resource
* @param maxStat Maximum value of resource
* @return Clamped resource value. If the provided current value is 0,
* this function will return the maximum resource value.
* this function will return the maximum resource value.
*/
public static double fixResource(double current, double maxStat) {
return current == 0 ? maxStat : Math.max(0, Math.min(current, maxStat));
@ -82,6 +82,11 @@ public class MMOCoreUtils {
return builder.toString();
}
/**
* @see UtilityMethods#kebabCase(Enum)
* @see UtilityMethods#kebabCase(String)
*/
@Deprecated
public static String ymlName(String str) {
return str.toLowerCase().replace("_", "-").replace(" ", "-");
}

View File

@ -55,8 +55,8 @@ public abstract class AbstractClassSelect extends EditableInventory {
}
@Override
public ItemStack getDisplayedItem(T inv, int n) {
ItemOptions options = n == 0 ? ItemOptions.item(n, playerClass.getIcon()) : ItemOptions.index(n);
public ItemStack getDisplayedItem(@NotNull T inv, int n) {
ItemOptions options = n == 0 ? new ItemOptions(n, playerClass.getRawIcon()) : ItemOptions.index(n);
return super.getDisplayedItem(inv, options);
}

View File

@ -1,5 +1,6 @@
package net.Indyuce.mmocore.gui;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.gui.Navigator;
import io.lumine.mythic.lib.gui.editable.item.InventoryItem;
import net.Indyuce.mmocore.MMOCore;
@ -63,7 +64,7 @@ public class ClassSelect extends AbstractClassSelect {
inv.getNavigator().unblockClosing();
final PlayerClass playerClass = findDeepestSubclass(inv.playerData, this.playerClass);
InventoryManager.CLASS_CONFIRM.get(MMOCoreUtils.ymlName(playerClass.getId())).newInventory(inv, inv.profileCallback != null).open();
InventoryManager.CLASS_CONFIRM.get(UtilityMethods.kebabCase(playerClass.getId())).newInventory(inv, inv.profileCallback != null).open();
}
}

View File

@ -150,7 +150,7 @@ public class SkillList extends EditableInventory {
public ItemStack getDisplayedItem(SkillViewerInventory inv, int n) {
if (inv.selected == null) return new ItemStack(Material.AIR);
return getDisplayedItem(inv, inv.selected.getSkill().getRawIcon().toItemOptions(n));
return getDisplayedItem(inv, new ItemOptions(n, inv.selected.getSkill().getRawIcon()));
}
@Override
@ -269,7 +269,7 @@ public class SkillList extends EditableInventory {
if (skillSlot == null || !inv.playerData.hasUnlocked(skillSlot)) return new ItemStack(Material.AIR);
final @Nullable ClassSkill boundSkill = inv.playerData.getBoundSkill(n + 1);
final ItemOptions options = boundSkill == null ? ItemOptions.index(n) : filledItem == null ? boundSkill.getSkill().getRawIcon().toItemOptions(n) : ItemOptions.model(n, filledItem, filledCMD);
final ItemOptions options = boundSkill == null ? ItemOptions.index(n) : filledItem == null ? new ItemOptions(n, boundSkill.getSkill().getRawIcon()) : ItemOptions.model(n, filledItem, filledCMD);
return super.getDisplayedItem(inv, options);
}
@ -395,7 +395,7 @@ public class SkillList extends EditableInventory {
if (index >= inv.skills.size()) return new ItemStack(Material.AIR);
ClassSkill skill = inv.skills.get(index);
return getDisplayedItem(inv, skill.getSkill().getRawIcon().toItemOptions(n));
return getDisplayedItem(inv, new ItemOptions(n, skill.getSkill().getRawIcon()));
}
@Override

View File

@ -9,18 +9,19 @@ import io.lumine.mythic.lib.gui.editable.item.ItemOptions;
import io.lumine.mythic.lib.gui.editable.item.PhysicalItem;
import io.lumine.mythic.lib.gui.editable.item.SimpleItem;
import io.lumine.mythic.lib.gui.editable.placeholder.Placeholders;
import io.lumine.mythic.lib.gui.util.IconOptions;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.ConfigMessage;
import net.Indyuce.mmocore.api.SoundEvent;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
import net.Indyuce.mmocore.skilltree.*;
import net.Indyuce.mmocore.skilltree.display.DisplayInfo;
import net.Indyuce.mmocore.skilltree.IntegerCoordinates;
import net.Indyuce.mmocore.skilltree.NodeState;
import net.Indyuce.mmocore.skilltree.ParentType;
import net.Indyuce.mmocore.skilltree.SkillTreeNode;
import net.Indyuce.mmocore.skilltree.display.DisplayMap;
import net.Indyuce.mmocore.skilltree.display.NodeDisplayInfo;
import net.Indyuce.mmocore.skilltree.display.PathDisplayInfo;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import net.Indyuce.mmocore.util.Icon;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
@ -37,10 +38,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.logging.Level;
public class SkillTreeViewer extends EditableInventory {
protected final Map<DisplayInfo, Icon> icons = new HashMap<>();
protected DisplayMap icons;
protected final Map<NodeState, String> statusNames = new HashMap<>();
/**
@ -64,31 +64,11 @@ public class SkillTreeViewer extends EditableInventory {
public void reload(@NotNull JavaPlugin plugin, @NotNull ConfigurationSection config) {
super.reload(plugin, config);
if (config.contains("status-names"))
for (NodeState nodeState : NodeState.values())
statusNames.put(nodeState, config.getString("status-names." + UtilityMethods.ymlName(nodeState.name()), nodeState.name()));
if (config.contains("status-names")) for (NodeState nodeState : NodeState.values())
statusNames.put(nodeState, config.getString("status-names." + UtilityMethods.ymlName(nodeState.name()), nodeState.name()));
// Loads all the pathDisplayInfo
for (NodeState status : NodeState.values())
for (PathType pathType : PathType.values()) {
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), Icon.from(config.get(configPath)));
}
// Loads all the nodeDisplayInfo
for (NodeState status : NodeState.values())
for (NodeType nodeType : NodeType.values()) {
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), Icon.from(config.get(configPath)));
}
// Loads display info
icons = DisplayMap.from(config.getConfigurationSection("display"));
}
@Override
@ -268,8 +248,7 @@ public class SkillTreeViewer extends EditableInventory {
@Override
public void onClick(@NotNull SkillTreeInventory inv, @NotNull InventoryClickEvent event) {
String id = event.getCurrentItem().getItemMeta().getPersistentDataContainer().get(
new NamespacedKey(MMOCore.plugin, "skill-tree-id"), PersistentDataType.STRING);
String id = event.getCurrentItem().getItemMeta().getPersistentDataContainer().get(new NamespacedKey(MMOCore.plugin, "skill-tree-id"), PersistentDataType.STRING);
MMOCore.plugin.soundManager.getSound(SoundEvent.CHANGE_SKILL_TREE).playTo(inv.getPlayer());
inv.skillTree = MMOCore.plugin.skillTreeManager.get(id);
inv.open();
@ -317,8 +296,7 @@ public class SkillTreeViewer extends EditableInventory {
public SkillTreeNodeItem(ConfigurationSection config) {
super(config);
if (config.isList("path-lore"))
pathLore.addAll(config.getStringList("path-lore"));
if (config.isList("path-lore")) pathLore.addAll(config.getStringList("path-lore"));
}
@Override
@ -373,8 +351,8 @@ public class SkillTreeViewer extends EditableInventory {
IntegerCoordinates coordinates = inv.getCoordinates(n);
if (!inv.getSkillTree().isPathOrNode(coordinates)) return new ItemStack(Material.AIR);
Icon icon = inv.getIcon(coordinates);
ItemStack item = super.getDisplayedItem(inv, icon.toItemOptions(n));
IconOptions icon = inv.getIcon(coordinates);
ItemStack item = super.getDisplayedItem(inv, new ItemOptions(n, icon));
ItemMeta meta = item.getItemMeta();
// Make sure name is not null
@ -513,17 +491,14 @@ public class SkillTreeViewer extends EditableInventory {
this.skillTree = skillTree == null ? skillTrees.get(0) : skillTree;
if (skillTree == null)
maxTreeListPage = (skillTrees.size() - 1) / SkillTreeViewer.this.getByFunction("skill-tree").getSlots().size();
else
maxTreeListPage = 0;
else maxTreeListPage = 0;
//We get the width and height of the GUI(corresponding to the slots given)
slots = SkillTreeViewer.this.getByFunction("skill-tree-node").getSlots();
minSlot = 64;
maxSlot = 0;
for (int slot : slots) {
if (slot < minSlot)
minSlot = slot;
if (slot > maxSlot)
maxSlot = slot;
if (slot < minSlot) minSlot = slot;
if (slot > maxSlot) maxSlot = slot;
}
width = (maxSlot - minSlot) % 9;
height = (maxSlot - minSlot) / 9;
@ -551,32 +526,30 @@ public class SkillTreeViewer extends EditableInventory {
return playerData;
}
public Icon getIcon(IntegerCoordinates coordinates) {
public IconOptions getIcon(@NotNull IntegerCoordinates coordinates) {
if (skillTree.isNode(coordinates)) {
SkillTreeNode node = skillTree.getNode(coordinates);
NodeType nodeType = node.getNodeType();
NodeState nodeState = playerData.getNodeState(node);
//If the node has its own display, it will be shown.
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);
var node = skillTree.getNode(coordinates);
var nodeShape = node.getNodeType();
var nodeState = playerData.getNodeState(node);
var displayInfo = new NodeDisplayInfo(nodeShape, nodeState);
// Node > skill tree > skill tree UI
var icon = DisplayMap.getIcon(displayInfo, node.getIcons(), skillTree.getIcons(), icons);
if (icon == null) icon = DisplayMap.DEFAULT_ICON;
//Validate.notNull(icon, "Node " + node.getFullId() + " has no icon for shape " + nodeShape + " and state " + nodeState);
Icon icon = icons.get(displayInfo);
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();
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))
return skillTree.getIcon(displayInfo);
Icon icon = icons.get(displayInfo);
Validate.notNull(icon, "There is no icon for the path type " + pathType + " and the status " + pathStatus);
var path = skillTree.getPath(coordinates);
var pathShape = path.getPathType();
var pathStatus = path.getStatus(playerData);
var displayInfo = new PathDisplayInfo(pathShape, pathStatus);
// Skill tree > Skill tree UI
var icon = DisplayMap.getIcon(displayInfo, skillTree.getIcons(), icons);
if (icon == null) icon = DisplayMap.DEFAULT_ICON;
//Validate.notNull(icon, "No icon for path shape " + pathShape + " and state " + pathStatus);
return icon;
}
}

View File

@ -69,7 +69,12 @@ public class InventoryManager {
final var specificUi = invType.provider.apply(id, !configFile.exists());
((Map) invType.inventories).put(formattedId, specificUi);
specificUi.reload(MMOCore.plugin, new ConfigFile("/gui/" + invType.name, specificUi.getId()).getConfig());
// try {
specificUi.reload(MMOCore.plugin, new ConfigFile("/gui/" + invType.name, specificUi.getId()).getConfig());
/* } catch (Exception exception) {
MMOCore.log(Level.WARNING, "Could not load inventory 'gui/" + invType.name + "/" + invType.name + "-default" + "': " + exception.getMessage());
} */
}
}

View File

@ -2,12 +2,12 @@ package net.Indyuce.mmocore.skill;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.gui.util.IconOptions;
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.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;
@ -23,7 +23,7 @@ public class RegisteredSkill {
private final Map<String, DecimalFormat> parameterDecimalFormats = new HashMap<>();
private final Icon icon;
private final IconOptions icon;
private final List<String> lore;
private final List<String> categories;
private final TriggerType triggerType;
@ -32,7 +32,7 @@ public class RegisteredSkill {
this.handler = handler;
name = Objects.requireNonNull(config.getString("name"), "Could not find skill name");
icon = Icon.from(config.get("material"));
icon = IconOptions.from(config.get("material"));
lore = Objects.requireNonNull(config.getStringList("lore"), "Could not find skill lore");
// Trigger type
@ -46,7 +46,6 @@ public class RegisteredSkill {
else
categories.add("ACTIVE");
// Load default modifier formulas
for (String param : handler.getParameters()) {
if (config.contains(param + ".decimal-format"))
@ -62,15 +61,20 @@ public class RegisteredSkill {
defaultParameters.put("level", new IntegerLinearValue(0, 1));
}
public RegisteredSkill(SkillHandler<?> handler, String name, ItemStack icon, List<String> lore, @Nullable TriggerType triggerType) {
public RegisteredSkill(SkillHandler<?> handler, String name, IconOptions icon, List<String> lore, @Nullable TriggerType triggerType) {
this.handler = handler;
this.name = name;
this.icon = Icon.fromItem(icon);
this.icon = IconOptions.from(icon);
this.lore = lore;
this.triggerType = triggerType;
this.categories = new ArrayList<>();
}
@Deprecated
public RegisteredSkill(SkillHandler<?> handler, String name, ItemStack icon, List<String> lore, @Nullable TriggerType triggerType) {
this(handler, name, IconOptions.from(icon), lore, triggerType);
}
public SkillHandler<?> getHandler() {
return handler;
}
@ -89,11 +93,11 @@ public class RegisteredSkill {
@Deprecated
public ItemStack getIcon() {
return icon.toItem();
return icon.toItemStack();
}
@NotNull
public Icon getRawIcon() {
public IconOptions getRawIcon() {
return icon;
}

View File

@ -5,8 +5,7 @@ import net.Indyuce.mmocore.api.player.PlayerData;
/**
* State of one skill tree node, or path between nodes.
*
* @see PlayerData#getNodeState(SkillTreeNode)
* @see SkillTreePath#getStatus(PlayerData)
* @see PlayerData#getNodeState(SkillTreeNode)
*/
public enum NodeState {

View File

@ -0,0 +1,32 @@
package net.Indyuce.mmocore.skilltree;
import net.Indyuce.mmocore.api.player.PlayerData;
/**
* @see SkillTreePath#getStatus(PlayerData)
*/
public enum PathState {
/**
* 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. The player needs to
* re-spec to unlock that node.
*/
FULLY_LOCKED
}

View File

@ -2,14 +2,16 @@ package net.Indyuce.mmocore.skilltree;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.gui.editable.placeholder.Placeholders;
import io.lumine.mythic.lib.gui.util.IconOptions;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
import net.Indyuce.mmocore.experience.EXPSource;
import net.Indyuce.mmocore.experience.ExpCurve;
import net.Indyuce.mmocore.experience.ExperienceObject;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import net.Indyuce.mmocore.util.Icon;
import net.Indyuce.mmocore.skilltree.display.DisplayMap;
import net.Indyuce.mmocore.skilltree.display.NodeDisplayInfo;
import net.Indyuce.mmocore.skilltree.display.NodeShape;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
import org.apache.commons.lang.Validate;
import org.bukkit.Location;
@ -26,7 +28,7 @@ public class SkillTreeNode implements ExperienceObject {
private final String name, id;
private final String permissionRequired;
private final int pointConsumption;
private final Map<NodeState, Icon> icons = new HashMap<>();
private final DisplayMap icons;
private final IntegerCoordinates coordinates;
private final int maxLevel, maxChildren;
private final ExperienceTable experienceTable;
@ -42,13 +44,7 @@ public class SkillTreeNode implements ExperienceObject {
this.tree = tree;
// 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.");
}
this.icons = DisplayMap.from(config.getConfigurationSection("display"));
name = Objects.requireNonNull(config.getString("name"), "Could not find node name");
root = config.getBoolean("root", config.getBoolean("is-root")); // backwards compatibility
@ -81,14 +77,6 @@ public class SkillTreeNode implements ExperienceObject {
return tree;
}
public boolean hasIcon(NodeState status) {
return icons.containsKey(status);
}
public Icon getIcon(NodeState status) {
return icons.get(status);
}
public boolean isRoot() {
return root;
}
@ -167,7 +155,7 @@ 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() {
@ -184,6 +172,11 @@ public class SkillTreeNode implements ExperienceObject {
return coordinates;
}
@NotNull
public DisplayMap getIcons() {
return icons;
}
public static final String KEY_PREFIX = "node";
@Override
@ -202,28 +195,28 @@ public class SkillTreeNode implements ExperienceObject {
return experienceTable != null;
}
public NodeType getNodeType() {
public NodeShape getNodeType() {
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;
if (up && right && down && left) return NodeShape.UP_RIGHT_DOWN_LEFT;
else if (up && right && down) return NodeShape.UP_RIGHT_DOWN;
else if (up && right && left) return NodeShape.UP_RIGHT_LEFT;
else if (up && down && left) return NodeShape.UP_DOWN_LEFT;
else if (down && right && left) return NodeShape.DOWN_RIGHT_LEFT;
else if (up && right) return NodeShape.UP_RIGHT;
else if (up && down) return NodeShape.UP_DOWN;
else if (up && left) return NodeShape.UP_LEFT;
else if (down && right) return NodeShape.DOWN_RIGHT;
else if (down && left) return NodeShape.DOWN_LEFT;
else if (right && left) return NodeShape.RIGHT_LEFT;
else if (up) return NodeShape.UP;
else if (down) return NodeShape.DOWN;
else if (right) return NodeShape.RIGHT;
else if (left) return NodeShape.LEFT;
return NodeShape.NO_PATH;
}
@Override
@ -281,4 +274,23 @@ public class SkillTreeNode implements ExperienceObject {
public ExpCurve getExpCurve() {
throw new RuntimeException("Skill trees don't have experience");
}
//region Deprecated
@Deprecated
public boolean hasIcon(NodeState status) {
return getIcon(status) != null;
}
@Deprecated
@Nullable
public IconOptions getIcon(NodeState status) {
for (var shape : NodeShape.values()) {
var found = DisplayMap.getIcon(new NodeDisplayInfo(shape, status), icons);
if (found != null) return found;
}
return null;
}
//endregion
}

View File

@ -1,6 +1,7 @@
package net.Indyuce.mmocore.skilltree;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.skilltree.display.PathShape;
import net.Indyuce.mmocore.skilltree.tree.SkillTree;
public class SkillTreePath {
@ -20,25 +21,25 @@ public class SkillTreePath {
* Defines the status of a path between two nodes, which is determined
* by the pair of states of the two nodes.
*/
public NodeState getStatus(PlayerData playerData) {
public PathState getStatus(PlayerData playerData) {
var from = playerData.getNodeState(this.from);
var to = playerData.getNodeState(this.to);
// Either one is fully locked => gray out path
if (from == NodeState.FULLY_LOCKED || to == NodeState.FULLY_LOCKED) return NodeState.FULLY_LOCKED;
if (from == NodeState.FULLY_LOCKED || to == NodeState.FULLY_LOCKED) return PathState.FULLY_LOCKED;
// Both are unlocked => path is taken, unlocked
if (from.isUnlocked() && to.isUnlocked()) return NodeState.UNLOCKED;
if (from.isUnlocked() && to.isUnlocked()) return PathState.UNLOCKED;
// One of them is unlocked, other one is unlockable => path is not taken yet, but can be
if ((from == NodeState.UNLOCKABLE && to.isUnlocked()) || (from.isUnlocked() && to == NodeState.UNLOCKABLE))
return NodeState.UNLOCKABLE;
return PathState.UNLOCKABLE;
// Otherwise, locked path
return NodeState.LOCKED;
return PathState.LOCKED;
}
public PathType getPathType() {
public PathShape getPathType() {
IntegerCoordinates upCoor = new IntegerCoordinates(coordinates.getX(), coordinates.getY() - 1);
IntegerCoordinates downCoor = new IntegerCoordinates(coordinates.getX(), coordinates.getY() + 1);
IntegerCoordinates rightCoor = new IntegerCoordinates(coordinates.getX() + 1, coordinates.getY());
@ -49,18 +50,18 @@ public class SkillTreePath {
boolean hasLeft = tree.isPath(leftCoor) || leftCoor.equals(from.getCoordinates()) || leftCoor.equals(to.getCoordinates());
if ((hasUp || hasDown) && !hasLeft && !hasRight) {
return PathType.UP;
return PathShape.UP;
} else if ((hasRight || hasLeft) && !hasUp && !hasDown) {
return PathType.RIGHT;
return PathShape.RIGHT;
} else if (hasUp && hasRight) {
return PathType.UP_RIGHT;
return PathShape.UP_RIGHT;
} else if (hasUp && hasLeft) {
return PathType.UP_LEFT;
return PathShape.UP_LEFT;
} else if (hasDown && hasRight) {
return PathType.DOWN_RIGHT;
return PathShape.DOWN_RIGHT;
} else if (hasDown && hasLeft) {
return PathType.DOWN_LEFT;
return PathShape.DOWN_LEFT;
}
return PathType.DEFAULT;
return PathShape.DEFAULT;
}
}

View File

@ -1,18 +0,0 @@
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,96 @@
package net.Indyuce.mmocore.skilltree.display;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.gui.util.IconOptions;
import net.Indyuce.mmocore.skilltree.NodeState;
import net.Indyuce.mmocore.skilltree.PathState;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
public class DisplayMap {
public final Map<Object, IconOptions> icons = new HashMap<>();
public static final IconOptions DEFAULT_ICON = new IconOptions(Material.BARRIER, 0);
public static final DisplayMap EMPTY = new DisplayMap(YamlConfiguration.loadConfiguration(new StringReader("")));
private DisplayMap(@NotNull ConfigurationSection config) {
// Loads all the pathDisplayInfo
if (config.contains("paths")) for (var status : PathState.values())
for (PathShape pathShape : PathShape.values())
try {
final String configPath = "paths." + UtilityMethods.kebabCase(status.name() + "." + pathShape.name());
icons.put(new PathDisplayInfo(pathShape, status), IconOptions.from(config.get(configPath)));
} catch (Exception exception) {
// Ignore
}
// Loads all the nodeDisplayInfo
var nodeConfig = getNodeConfig(config);
if (nodeConfig != null) for (var state : NodeState.values()) {
final var statusConfig = nodeConfig.get(UtilityMethods.kebabCase(state.name()));
if (statusConfig == null) continue;
// Check if it depends on state
if (statusConfig instanceof ConfigurationSection && UtilityMethods.containsOneKey((ConfigurationSection) statusConfig, NodeShape.values(), UtilityMethods::kebabCase))
for (var shape : NodeShape.values()) {
try {
final var configPath = "nodes." + UtilityMethods.kebabCase(state.name() + "." + shape.name());
icons.put(new NodeDisplayInfo(shape, state), IconOptions.from(nodeConfig.get(configPath)));
} catch (Exception exception) {
// Ignore
}
}
// Depends on node type
else {
var iconFound = IconOptions.from(statusConfig);
for (var shape : NodeShape.values())
icons.put(new NodeDisplayInfo(shape, state), iconFound);
}
}
}
@Nullable
private ConfigurationSection getNodeConfig(@Nullable ConfigurationSection config) {
// Null to null
if (config == null) return null;
// 'nodes' subconfig
var subconfig = config.getConfigurationSection("nodes");
if (subconfig != null) return subconfig;
// Validate at least one state
// Not strictly necessary.
if (UtilityMethods.containsOneKey(config, NodeState.values(), UtilityMethods::kebabCase)) return config;
// Wrong syntax..
return null;
}
/**
* @param nodeInfo Either a {@link NodeDisplayInfo} or {@link PathDisplayInfo}
* @return Icon/texture mapping of node/path display info
*/
@Nullable
public static IconOptions getIcon(Object nodeInfo, @NotNull DisplayMap... maps) {
int i = 0;
IconOptions found = null;
while (found == null && i < maps.length) found = maps[i++].icons.get(nodeInfo);
return found;
}
@NotNull
public static DisplayMap from(@Nullable ConfigurationSection config) {
if (config == null) return EMPTY;
return new DisplayMap(config);
}
}

View File

@ -1,22 +1,27 @@
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 class NodeDisplayInfo {
private final NodeShape shape;
private final NodeState state;
public NodeDisplayInfo(@NotNull NodeType type, @NotNull NodeState status) {
super(status);
this.type = type;
public NodeDisplayInfo(@NotNull NodeShape shape, @NotNull NodeState status) {
this.state = status;
this.shape = shape;
}
public NodeType getType() {
return type;
@NotNull
public NodeState getStatus() {
return state;
}
@NotNull
public NodeShape getShape() {
return shape;
}
@Override
@ -24,16 +29,16 @@ public class NodeDisplayInfo extends DisplayInfo {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NodeDisplayInfo that = (NodeDisplayInfo) o;
return state == that.state && type == that.type;
return state == that.state && shape == that.shape;
}
@Override
public int hashCode() {
return Objects.hash(state, type);
return Objects.hash(state, shape);
}
@Override
public String toString() {
return "NodeDisplayInfo{" + "status=" + state + ", type=" + type + '}';
return "NodeDisplayInfo{" + "status=" + state + ", type=" + shape + '}';
}
}

View File

@ -1,9 +1,9 @@
package net.Indyuce.mmocore.skilltree;
package net.Indyuce.mmocore.skilltree.display;
/**
* TODO docs
*/
public enum NodeType {
public enum NodeShape {
UP_RIGHT_DOWN_LEFT,
UP_RIGHT_DOWN,
UP_RIGHT_LEFT,

View File

@ -1,30 +1,32 @@
package net.Indyuce.mmocore.skilltree.display;
import net.Indyuce.mmocore.skilltree.PathType;
import net.Indyuce.mmocore.skilltree.NodeState;
import net.Indyuce.mmocore.skilltree.PathState;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class PathDisplayInfo extends DisplayInfo {
private final PathType type;
public class PathDisplayInfo {
private final PathShape shape;
private final PathState state;
public PathDisplayInfo(PathType type, NodeState status) {
super(status);
this.type = type;
public PathDisplayInfo(@NotNull PathShape shape, @NotNull PathState status) {
this.state = status;
this.shape = shape;
}
public PathType getType() {
return type;
}
public NodeState getStatus() {
@NotNull
public PathState getStatus() {
return state;
}
@NotNull
public PathShape getShape() {
return shape;
}
@Override
public int hashCode() {
return Objects.hash(type, state);
return Objects.hash(shape, state);
}
@Override
@ -32,11 +34,11 @@ public class PathDisplayInfo extends DisplayInfo {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PathDisplayInfo that = (PathDisplayInfo) o;
return type == that.type && state == that.state;
return shape == that.shape && state == that.state;
}
@Override
public String toString() {
return "PathDisplayInfo{" + "type=" + type + ", status=" + state + '}';
return "PathDisplayInfo{" + "type=" + shape + ", status=" + state + '}';
}
}

View File

@ -1,11 +1,11 @@
package net.Indyuce.mmocore.skilltree;
package net.Indyuce.mmocore.skilltree.display;
/**
* 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 {
public enum PathShape {
/**
* up, down

View File

@ -2,15 +2,12 @@ package net.Indyuce.mmocore.skilltree.tree;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.gui.util.IconOptions;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
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 net.Indyuce.mmocore.skilltree.display.DisplayMap;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
@ -42,7 +39,7 @@ public abstract class SkillTree implements RegisteredObject {
protected final Map<String, SkillTreeNode> nodes = new HashMap<>();
protected final int maxPointSpent;
protected final List<SkillTreeNode> roots = new ArrayList<>();
protected final Map<DisplayInfo, Icon> icons = new HashMap<>();
protected final DisplayMap icons;
protected final Map<IntegerCoordinates, SkillTreeNode> coordNodes = new HashMap<>();
protected final Map<IntegerCoordinates, SkillTreePath> coordPaths = new HashMap<>();
@ -87,33 +84,8 @@ public abstract class SkillTree implements RegisteredObject {
}
}
// 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 (var status : NodeState.values()) {
final var anyType = config.get("display.nodes." + MMOCoreUtils.ymlName(status.name()));
// Does not depend on node type.
if (anyType != null) for (var nodeType : NodeType.values())
icons.put(new NodeDisplayInfo(nodeType, status), Icon.from(anyType));
// Depends on node type
else for (var nodeType : NodeType.values())
try {
final var 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
}
}
// Load icons
this.icons = DisplayMap.from(config.getConfigurationSection("display"));
// Setup children and parents for each node
for (SkillTreeNode node : nodes.values())
@ -149,11 +121,6 @@ public abstract class SkillTree implements RegisteredObject {
return customModelData;
}
@Deprecated
public static SkillTree loadSkillTree(ConfigurationSection config) {
return MMOCore.plugin.skillTreeManager.loadSkillTree(config);
}
public void addRoot(@NotNull SkillTreeNode node) {
roots.add(node);
}
@ -366,12 +333,9 @@ public abstract class SkillTree implements RegisteredObject {
return Objects.requireNonNull(nodes.get(name), "Could not find node in tree '" + id + "' with name '" + name + "'");
}
public boolean hasIcon(DisplayInfo displayInfo) {
return icons.containsKey(displayInfo);
}
public Icon getIcon(DisplayInfo displayInfo) {
return icons.get(displayInfo);
@NotNull
public DisplayMap getIcons() {
return icons;
}
public boolean isNode(String name) {
@ -390,4 +354,23 @@ public abstract class SkillTree implements RegisteredObject {
public int hashCode() {
return Objects.hash(id);
}
//region Deprecated
@Deprecated
public static SkillTree loadSkillTree(ConfigurationSection config) {
return MMOCore.plugin.skillTreeManager.loadSkillTree(config);
}
@Deprecated
public boolean hasIcon(Object displayInfo) {
return DisplayMap.getIcon(displayInfo, icons) != null;
}
@Deprecated
public IconOptions getIcon(Object displayInfo) {
return DisplayMap.getIcon(displayInfo, icons);
}
//endregion
}

View File

@ -14,7 +14,11 @@ import java.util.Objects;
/**
* The material and custom model-data of a node
*
* @see io.lumine.mythic.lib.gui.util.IconOptions
* @deprecated
*/
@Deprecated
public class Icon {
private final Material material;
private final int modelData;
@ -77,7 +81,7 @@ public class Icon {
if (object instanceof ConfigurationSection) {
final ConfigurationSection config = (ConfigurationSection) object;
final Material material = Material.valueOf(UtilityMethods.enumName(((ConfigurationSection) object).getString("item")));
final Material material = Material.valueOf(UtilityMethods.enumName(((ConfigurationSection) object).getString("item", "none")));
final int modelData = config.getInt("model-data", config.getInt("custom-model-data")); // Backwards compatibility
return new Icon(material, modelData);
}

View File

@ -5,6 +5,13 @@ import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* @param <L> Left-hand side type
* @param <R> Right-hand side type
* @see io.lumine.mythic.lib.util.Pair
* @deprecated
*/
@Deprecated
public class Pair<L, R> {
private final L left;
private final R right;

View File

@ -1,8 +1,8 @@
package net.Indyuce.mmocore.waypoint;
import io.lumine.mythic.lib.util.Pair;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -13,6 +13,8 @@
<build>
<finalName>MMOCore-${project.version}</finalName>
<!--
Includes default files in the Bukkit module
-->
@ -34,7 +36,6 @@
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<finalName>MMOCore-${project.version}</finalName>
<outputDirectory>../target/</outputDirectory>
</configuration>
</plugin>

View File

@ -4,13 +4,16 @@
display:
name: 'Human'
# This is the default class which players have when
# joining the server. They can level it up, but they
# cannot choose this class again after changing their
# class because it is not displayed in /class
# No need for an item since the default class is not displayed
# anywhere by default.
#item: LEATHER_CHESTPLATE
# This is the default class granted to players when joining the server.
# They may level it up, but they cannot choose this class again
# after switching class, since it is not displayed in /class
options:
default: true
display: false
default: true # Default class when joining the server for the first time
display: false # Does not display in the class UI
needs-permission: false # False by default
# Only regens when out of combat

View File

@ -26,7 +26,15 @@ display:
- '&7 Max Mana: &930 &7(+&91.3&7)'
- '&7 Health Regen: &90.13 &7(+&90&7)'
- '&7 Mana Regen: &90.2 &7(+&90.04&7)'
item: BLAZE_POWDER:1 # Supports custom model data/texture by durability
item: 'BLAZE_POWDER:1' # Syntax: '<ITEM>:<custom_model_data_int>'
# Alternate syntax for string custom model data, item model....
#item:
# item: BLAZE_POWDER
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels

View File

@ -24,7 +24,17 @@ display:
- '&7 Max Mana: &927 &7(+&91.2&7)'
- '&7 Health Regen: &90.13 &7(+&90&7)'
- '&7 Mana Regen: &90.2 &7(+&90.04&7)'
item: BLAZE_POWDER
# Alternate syntax for custom model data
#item: 'BLAZE_POWDER:10'
# Alternate syntax for custom model data, item model....
#item:
# item: BLAZE_POWDER
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels

View File

@ -23,7 +23,17 @@ display:
- '&7 Max Mana: &920 &7(+&90&7)'
- '&7 Health Regen: &90.1 &7(+&90&7)'
- '&7 Mana Regen: &90.166 &7(+&90&7)'
item: BOW
# Alternate syntax for custom model data
#item: 'BOW:10'
# Alternate syntax for custom model data, item model....
#item:
# item: BOW
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels

View File

@ -25,7 +25,15 @@ display:
- '&7 Max Mana: &920 &7(+&90&7)'
- '&7 Health Regen: &90.1 &7(+&90&7)'
- '&7 Mana Regen: &90.166 &7(+&90&7)'
item: ENCHANTED_GOLDEN_APPLE:0 # Supports custom model data/texture by durability
item: 'ENCHANTED_GOLDEN_APPLE:100'
# Alternate syntax for custom model data, item model....
#item:
# item: ENCHANTED_GOLDEN_APPLE
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels

View File

@ -23,7 +23,18 @@ display:
- '&7 Max Mana: &920 &7(+&90&7)'
- '&7 Health Regen: &90.1 &7(+&90&7)'
- '&7 Mana Regen: &90.166 &7(+&90&7)'
# Change class icon here
item: LEATHER_BOOTS
# Alternate syntax for custom model data
#item: 'LEATHER_BOOTS:10'
# Alternate syntax for custom model data, item model....
#item:
# item: IRON_SWORD
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels

View File

@ -28,7 +28,16 @@ display:
- '&7 Max Rage: &c20 &7(+&c1&7)'
- '&7 Health Regen: &90.1 &7(+&90&7)'
- '&7 Rage Degeneration: &9-0.5&7/s'
item: IRON_SWORD:0 # Supports custom model data/texture by durability
# Change class icon here
item: 'IRON_SWORD:0' # Syntax: '<ITEM>:<custom_model_data_int>'
# Alternate syntax for custom model data, item model....
#item:
# item: IRON_SWORD
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels

View File

@ -210,54 +210,22 @@ display:
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
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"

View File

@ -81,7 +81,15 @@ status-names:
locked: 'Locked'
fully-locked: 'Fully Locked'
# This section is to change display options for nodes and paths depending on:
# - their shape: each path has a shape given its neighboring connections (left + up, )... kind of like a vanilla redstone wire.
# - their state: is the node/path unlocked, locked, maxed out... ?
#
# All of this is to "hide" the grid pattern of the chest inventory UI and give the
# impression of a clean 2D skill tree UI.
#
# Please refer to skill-tree/combat.yml for syntax examples.
#####################
display:
paths:
unlocked:

View File

@ -430,15 +430,19 @@ nodes:
1:
- "&eAdditional on-hit weapon damage in +%4"
# 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.
# This section is to change display options for nodes and paths depending on:
# - their shape: each path has a shape given its neighboring connections (left + up, )... kind of like a vanilla redstone wire.
# - their state: is the node/path unlocked, locked, maxed out... ?
#
# All of this is to "hide" the grid pattern of the chest inventory UI and give the
# impression of a clean 2D skill tree UI.
#
# This config section is optional; if you do not provide it the one provided in the skill tree CUSTOM GUI CONFIG will be used.
#######################
#display:
# paths:
# unlocked:
# up: 'WHITE_DYE:0'
# up: 'WHITE_DYE:0' # Syntax is 'MATERIAL:INT_CUSTOM_MODEL_DATA'
# up-right: 'WHITE_DYE:0'
# up-left: 'WHITE_DYE:0'
# down-right: 'WHITE_DYE:0'
@ -556,15 +560,39 @@ nodes:
# down: 'BLACK_CONCRETE:0'
# no-path: 'BLACK_CONCRETE:0'
# The following syntax works too, it applies the same texture
# no matter the neighboring paths.
#
# The following syntax shows how to use an item model, int custom model data
# or string custom model data. This works on both nodes and paths.
#######################
#display:
# paths:
# unlocked:
# up:
# item: DIAMOND
# item-model: 'minecraft:dirt'
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# ........
# ......
# nodes:
# unlocked:
# up-right-down-left:
# item: DIAMOND
# item-model: 'minecraft:dirt'
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# .......
# .......
# To make your configs lighter, you can have the node textures
# only depend on their state (locked, unlocked...) and not shape.
#######################
#display:
# paths:
# .......
# nodes:
# unlocked: 'WHITE_CONCRETE:0'
# maxed-out: 'GREEN_CONCRETE:0'
# locked: 'GRAY_CONCRETE:0'
# unlockable: 'BLUE_CONCRETE:0'
# fully-locked: 'BLACK_CONCRETE:0'
# unlocked: # No shape, only state!
# item: DIAMOND
# item-model: 'minecraft:dirt'
# custom-model-data: 10
# custom-model-data-string: 'whatever'
# ....

View File

@ -426,141 +426,22 @@ nodes:
1:
- "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.
# 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.
# This section is to change display options for nodes and paths depending on:
# - their shape: each path has a shape given its neighboring connections (left + up, )... kind of like a vanilla redstone wire.
# - their state: is the node/path unlocked, locked, maxed out... ?
#
# All of this is to "hide" the grid pattern of the chest inventory UI and give the
# impression of a clean 2D skill tree UI.
#
# This config section is optional; if you do not provide it the one provided in the skill tree CUSTOM GUI CONFIG will be used.
#
# Please refer to the default config file 'combat.yml' for syntax examples and explanations.
#########################
#display:
# paths:
# unlocked:
# 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: '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: '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: '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: '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'
# maxed-out:
# up-right-down-left: 'GREEN_CONCRETE:0'
# up-right-down: 'GREEN_CONCRETE:0'
# up-right-left: 'GREEN_CONCRETE:0'
# up-down-left: 'GREEN_CONCRETE:0'
# down-right-left: 'GREEN_CONCRETE:0'
# up-right: 'GREEN_CONCRETE:0'
# up-down: 'GREEN_CONCRETE:0'
# up-left: 'GREEN_CONCRETE:0'
# down-right: 'GREEN_CONCRETE:0'
# down-left: 'GREEN_CONCRETE:0'
# right-left: 'GREEN_CONCRETE:0'
# right: 'GREEN_CONCRETE:0'
# left: 'GREEN_CONCRETE:0'
# up: 'GREEN_CONCRETE:0'
# down: 'GREEN_CONCRETE:0'
# no-path: 'GREEN_CONCRETE:0'
# locked:
# 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: '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: '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'
# The following syntax works too, it applies the same texture
# no matter the neighboring paths.
#
#display:
# paths:
# .......
# nodes:
# unlocked: 'WHITE_CONCRETE:0'
# maxed-out: 'GREEN_CONCRETE:0'
# locked: 'GRAY_CONCRETE:0'
# unlockable: 'BLUE_CONCRETE:0'
# fully-locked: 'BLACK_CONCRETE:0'
# ....

View File

@ -183,3 +183,23 @@ nodes:
- "&eLorem ipsum dolor sit amet"
coordinates: -2,0
# This section is to change display options for nodes and paths depending on:
# - their shape: each path has a shape given its neighboring connections (left + up, )... kind of like a vanilla redstone wire.
# - their state: is the node/path unlocked, locked, maxed out... ?
#
# All of this is to "hide" the grid pattern of the chest inventory UI and give the
# impression of a clean 2D skill tree UI.
#
# This config section is optional; if you do not provide it the one provided in the skill tree CUSTOM GUI CONFIG will be used.
#
# Please refer to the default config file 'combat.yml' for syntax examples and explanations.
#########################
#display:
# paths:
# unlocked:
# up: 'WHITE_DYE:0'
# ....
# ...
# nodes:
# ....

View File

@ -422,3 +422,23 @@ nodes:
- "&eAdditional magic skill damage in +%2"
1:
- "&eAdditional magic skill damage in +%2"
# This section is to change display options for nodes and paths depending on:
# - their shape: each path has a shape given its neighboring connections (left + up, )... kind of like a vanilla redstone wire.
# - their state: is the node/path unlocked, locked, maxed out... ?
#
# All of this is to "hide" the grid pattern of the chest inventory UI and give the
# impression of a clean 2D skill tree UI.
#
# This config section is optional; if you do not provide it the one provided in the skill tree CUSTOM GUI CONFIG will be used.
#
# Please refer to the default config file 'combat.yml' for syntax examples and explanations.
#########################
#display:
# paths:
# unlocked:
# up: 'WHITE_DYE:0'
# ....
# ...
# nodes:
# ....

View File

@ -422,3 +422,23 @@ nodes:
- "&eAdditional skill/weapon projectile damage +%6"
1:
- "&eAdditional skill/weapon projectile damage +%6"
# This section is to change display options for nodes and paths depending on:
# - their shape: each path has a shape given its neighboring connections (left + up, )... kind of like a vanilla redstone wire.
# - their state: is the node/path unlocked, locked, maxed out... ?
#
# All of this is to "hide" the grid pattern of the chest inventory UI and give the
# impression of a clean 2D skill tree UI.
#
# This config section is optional; if you do not provide it the one provided in the skill tree CUSTOM GUI CONFIG will be used.
#
# Please refer to the default config file 'combat.yml' for syntax examples and explanations.
#########################
#display:
# paths:
# unlocked:
# up: 'WHITE_DYE:0'
# ....
# ...
# nodes:
# ....

View File

@ -422,3 +422,23 @@ nodes:
- "&eAdditional on-hit weapon damage in +%2."
1:
- "&eAdditional on-hit weapon damage in +%2."
# This section is to change display options for nodes and paths depending on:
# - their shape: each path has a shape given its neighboring connections (left + up, )... kind of like a vanilla redstone wire.
# - their state: is the node/path unlocked, locked, maxed out... ?
#
# All of this is to "hide" the grid pattern of the chest inventory UI and give the
# impression of a clean 2D skill tree UI.
#
# This config section is optional; if you do not provide it the one provided in the skill tree CUSTOM GUI CONFIG will be used.
#
# Please refer to the default config file 'combat.yml' for syntax examples and explanations.
#########################
#display:
# paths:
# unlocked:
# up: 'WHITE_DYE:0'
# ....
# ...
# nodes:
# ....