Skill Trees in MMOCore

This commit is contained in:
Guillaume 2022-05-24 15:26:22 +02:00
parent a2630824c9
commit fece7347ef
25 changed files with 1590 additions and 656 deletions

View File

@ -78,7 +78,6 @@ public class MMOCore extends LuminePlugin {
public final LootChestManager lootChests = new LootChestManager(); public final LootChestManager lootChests = new LootChestManager();
public final MMOLoadManager loadManager = new MMOLoadManager(); public final MMOLoadManager loadManager = new MMOLoadManager();
public final RestrictionManager restrictionManager = new RestrictionManager(); public final RestrictionManager restrictionManager = new RestrictionManager();
@Deprecated
public final SkillTreeManager skillTreeManager = new SkillTreeManager(); public final SkillTreeManager skillTreeManager = new SkillTreeManager();
// Profession managers // Profession managers
@ -242,7 +241,6 @@ public class MMOCore extends LuminePlugin {
} }
// Load party module // Load party module
try { try {
String partyPluginName = UtilityMethods.enumName(getConfig().getString("party-plugin")); String partyPluginName = UtilityMethods.enumName(getConfig().getString("party-plugin"));
@ -330,7 +328,8 @@ public class MMOCore extends LuminePlugin {
commandMap.register("mmocore", new PartyCommand(config.getConfigurationSection("party"))); commandMap.register("mmocore", new PartyCommand(config.getConfigurationSection("party")));
if (config.contains("guild")) if (config.contains("guild"))
commandMap.register("mmocore", new GuildCommand(config.getConfigurationSection("guild"))); commandMap.register("mmocore", new GuildCommand(config.getConfigurationSection("guild")));
if (config.contains("skill-tree"))
commandMap.register("mmocore", new SkillTreeCommand(config.getConfigurationSection("skill-tree")));
if (hasEconomy() && economy.isValid()) { if (hasEconomy() && economy.isValid()) {
if (config.contains("withdraw")) if (config.contains("withdraw"))
commandMap.register("mmocore", new WithdrawCommand(config.getConfigurationSection("withdraw"))); commandMap.register("mmocore", new WithdrawCommand(config.getConfigurationSection("withdraw")));
@ -391,7 +390,7 @@ public class MMOCore extends LuminePlugin {
* Called either when the server starts when initializing the manager for * Called either when the server starts when initializing the manager for
* the first time, or when issuing a plugin reload; in that case, stuff * the first time, or when issuing a plugin reload; in that case, stuff
* like listeners must all be cleared before. * like listeners must all be cleared before.
* * <p>
* Also see {@link MMOCoreManager} * Also see {@link MMOCoreManager}
* *
* @param clearBefore True when issuing a plugin reload * @param clearBefore True when issuing a plugin reload
@ -427,6 +426,7 @@ public class MMOCore extends LuminePlugin {
requestManager.initialize(clearBefore); requestManager.initialize(clearBefore);
soundManager.initialize(clearBefore); soundManager.initialize(clearBefore);
configItems.initialize(clearBefore); configItems.initialize(clearBefore);
skillTreeManager.initialize(clearBefore);
if (getConfig().isConfigurationSection("action-bar")) if (getConfig().isConfigurationSection("action-bar"))
actionBarManager.reload(getConfig().getConfigurationSection("action-bar")); actionBarManager.reload(getConfig().getConfigurationSection("action-bar"));

View File

@ -1,6 +1,5 @@
package net.Indyuce.mmocore.api.player; package net.Indyuce.mmocore.api.player;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.player.MMOPlayerData; import io.lumine.mythic.lib.api.player.MMOPlayerData;
import io.lumine.mythic.lib.player.TemporaryPlayerData; import io.lumine.mythic.lib.player.TemporaryPlayerData;
import io.lumine.mythic.lib.player.cooldown.CooldownMap; import io.lumine.mythic.lib.player.cooldown.CooldownMap;
@ -8,7 +7,9 @@ import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.ConfigMessage; import net.Indyuce.mmocore.api.ConfigMessage;
import net.Indyuce.mmocore.api.SoundEvent; import net.Indyuce.mmocore.api.SoundEvent;
import net.Indyuce.mmocore.player.Unlockable; import net.Indyuce.mmocore.player.Unlockable;
import net.Indyuce.mmocore.waypoint.CostType; import net.Indyuce.mmocore.tree.NodeState;
import net.Indyuce.mmocore.tree.SkillTreeNode;
import net.Indyuce.mmocore.tree.skilltree.SkillTree;
import net.Indyuce.mmocore.waypoint.Waypoint; import net.Indyuce.mmocore.waypoint.Waypoint;
import net.Indyuce.mmocore.api.event.PlayerExperienceGainEvent; import net.Indyuce.mmocore.api.event.PlayerExperienceGainEvent;
import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent; import net.Indyuce.mmocore.api.event.PlayerLevelUpEvent;
@ -49,10 +50,12 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Node;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
public class PlayerData extends OfflinePlayerData implements Closable, ExperienceTableClaimer { public class PlayerData extends OfflinePlayerData implements Closable, ExperienceTableClaimer {
@ -113,6 +116,10 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
*/ */
private boolean fullyLoaded = false; private boolean fullyLoaded = false;
//Value of the last skill tree the player was viewing
private SkillTree cachedSkillTree = null;
private final HashMap<SkillTreeNode, NodeState> nodeStates= new HashMap<>();
/** /**
* If the player data was loaded using temporary data. * If the player data was loaded using temporary data.
* See {@link TemporaryPlayerData} for more info * See {@link TemporaryPlayerData} for more info
@ -168,6 +175,14 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
} }
} }
public NodeState getNodeState(SkillTreeNode node) {
return nodeStates.get(node);
}
public void setNodeState(SkillTreeNode node,NodeState nodeState) {
nodeStates.put(node,nodeState);
}
@Override @Override
public void close() { public void close() {
@ -226,6 +241,17 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
return Math.max(1, level); return Math.max(1, level);
} }
public void setCachedSkillTree(SkillTree cachedSkillTree) {
this.cachedSkillTree = cachedSkillTree;
}
public SkillTree getCachedSkillTree() {
if (cachedSkillTree == null)
return MMOCore.plugin.skillTreeManager.getAll().stream().collect(Collectors.toList()).get(0);
return cachedSkillTree;
}
@Nullable @Nullable
public AbstractParty getParty() { public AbstractParty getParty() {
return MMOCore.plugin.partyModule.getParty(this); return MMOCore.plugin.partyModule.getParty(this);

View File

@ -29,6 +29,7 @@ import java.io.ByteArrayOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale;
public class MMOCoreUtils { public class MMOCoreUtils {
public static boolean pluginItem(ItemStack item) { public static boolean pluginItem(ItemStack item) {
@ -54,6 +55,10 @@ public class MMOCoreUtils {
return builder.toString(); return builder.toString();
} }
public static String toEnumName(String str) {
return str.replace("-", "_").toUpperCase();
}
/** /**
* Displays an in game indicator using a hologram. This uses * Displays an in game indicator using a hologram. This uses
* LumineUtils hologramFactory to summon holograms * LumineUtils hologramFactory to summon holograms

View File

@ -0,0 +1,38 @@
package net.Indyuce.mmocore.command;
import net.Indyuce.mmocore.api.event.MMOCommandEvent;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.manager.InventoryManager;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public class SkillTreeCommand extends BukkitCommand {
public SkillTreeCommand(ConfigurationSection config) {
super(config.getString("main"));
setAliases(config.getStringList("aliases"));
setDescription("Opens the skills menu.");
}
@Override
public boolean execute(@NotNull CommandSender sender, String s, String[] args) {
if (!(sender instanceof Player))
return false;
PlayerData data = PlayerData.get((Player) sender);
MMOCommandEvent event = new MMOCommandEvent(data, "skills");
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled())
return true;
InventoryManager.TREE_VIEW.newInventory(data).open();
return true;
}
}

View File

@ -0,0 +1,345 @@
package net.Indyuce.mmocore.gui;
import io.lumine.mythic.lib.player.modifier.PlayerModifier;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.gui.api.EditableInventory;
import net.Indyuce.mmocore.gui.api.GeneratedInventory;
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.tree.IntegerCoordinates;
import net.Indyuce.mmocore.tree.NodeState;
import net.Indyuce.mmocore.tree.skilltree.SkillTree;
import net.Indyuce.mmocore.tree.SkillTreeNode;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import java.util.List;
public class SkillTreeViewer extends EditableInventory {
public SkillTreeViewer() {
super("skill-tree");
}
@Override
public InventoryItem load(String function, ConfigurationSection config) {
if (function.equals("skill-tree")) {
return new SkillTreeItem(config);
}
if (function.equals("skill-tree-node"))
return new SkillTreeNodeItem(config);
if (function.equals("next-tree-list-page")) {
return new NextTreeListPageItem(config);
}
if (function.equals("previous-tree-list-page")) {
return new PreviousTreeListPageItem(config);
}
return null;
}
public SkillTreeInventory newInventory(PlayerData playerData) {
return new SkillTreeInventory(playerData, this);
}
public class SkillTreeItem extends InventoryItem<SkillTreeInventory> {
public SkillTreeItem(ConfigurationSection config) {
super(config);
}
@Override
public ItemStack display(SkillTreeInventory inv, int n) {
int index = 4 * inv.treeListPage + n;
SkillTree skillTree = MMOCore.plugin.skillTreeManager.get(index);
//We display with the material corresponding to the skillTree
ItemStack item = super.display(inv, n, skillTree.getGuiMaterial());
ItemMeta meta = item.getItemMeta();
PersistentDataContainer container = meta.getPersistentDataContainer();
container.set(new NamespacedKey(MMOCore.plugin, "skill-tree-id"), PersistentDataType.STRING, skillTree.getId());
item.setItemMeta(meta);
return item;
}
@Override
public Placeholders getPlaceholders(SkillTreeInventory inv, int n) {
int index = 4 * inv.treeListPage + n;
SkillTree skillTree = MMOCore.plugin.skillTreeManager.get(index);
Placeholders holders = new Placeholders();
holders.register("name", skillTree.getName());
holders.register("id", skillTree.getId());
return holders;
}
}
public class NextTreeListPageItem extends SimplePlaceholderItem<SkillTreeInventory> {
public NextTreeListPageItem(ConfigurationSection config) {
super(config);
}
@Override
public boolean canDisplay(SkillTreeInventory inv) {
return inv.getTreeListPage() < inv.getMaxTreeListPage();
}
}
public class PreviousTreeListPageItem extends SimplePlaceholderItem<SkillTreeInventory> {
public PreviousTreeListPageItem(ConfigurationSection config) {
super(config);
}
@Override
public boolean canDisplay(SkillTreeInventory inv) {
return inv.getTreeListPage() > 0;
}
}
public class SkillTreeNodeItem extends InventoryItem<SkillTreeInventory> {
private final LockedSkillNodeItem lockedSkillNode;
private final UnlockedSkillNodeItem unlockedSkillNode;
private final PathTreeNodeItem pathTreeNode;
public SkillTreeNodeItem(ConfigurationSection config) {
super(config);
Validate.isTrue(config.contains("locked-skill-node"));
Validate.isTrue(config.contains("unlocked-skill-node"));
Validate.isTrue(config.contains("path-tree-node"));
lockedSkillNode = new LockedSkillNodeItem(config.getConfigurationSection("locked-skill-node"));
unlockedSkillNode = new UnlockedSkillNodeItem(config.getConfigurationSection("unlocked-skill-node"));
pathTreeNode = new PathTreeNodeItem(config.getConfigurationSection("path-tree-node"));
}
@Override
public ItemStack display(SkillTreeInventory inv, int n) {
int slot = getSlots().get(n);
int deltaX = (slot - inv.getMinSlot()) % 9;
int deltaY = (slot - inv.getMinSlot()) / 9;
IntegerCoordinates coordinates = new IntegerCoordinates(inv.getX() + deltaX, inv.getY() + deltaY);
ItemStack item=null;
if (inv.getSkillTree().isNode(coordinates)) {
SkillTreeNode node = inv.getSkillTree().getNode(coordinates);
if (inv.getPlayerData().getNodeState(node).equals(NodeState.UNLOCKED))
item = unlockedSkillNode.display(inv, n, coordinates);
else if (inv.getPlayerData().getNodeState(node).equals(NodeState.LOCKED))
item = lockedSkillNode.display(inv, n, coordinates);
} //We check if is a path only if the skillTree is an automatic Skill Tree
else if (inv.getSkillTree().isPath(coordinates))
item = pathTreeNode.display(inv, n);
else
//If it is none of the above we just display air
return new ItemStack(Material.AIR);
//We save the coordinates of the node
ItemMeta meta = item.getItemMeta();
PersistentDataContainer container = meta.getPersistentDataContainer();
container.set(new NamespacedKey(MMOCore.plugin, "coordinates.x"), PersistentDataType.INTEGER, coordinates.getX());
container.set(new NamespacedKey(MMOCore.plugin, "coordinates.y"), PersistentDataType.INTEGER, coordinates.getY());
item.setItemMeta(meta);
return item;
}
@Override
public Placeholders getPlaceholders(SkillTreeInventory inv, int n) {
return new Placeholders();
}
}
public class LockedSkillNodeItem extends InventoryItem<SkillTreeInventory> {
public LockedSkillNodeItem(ConfigurationSection config) {
super(config);
}
public ItemStack display(SkillTreeInventory inv, int n, IntegerCoordinates coordinates) {
return super.display(inv, n);
}
@Override
public Placeholders getPlaceholders(SkillTreeInventory inv, int n) {
int slot = getSlots().get(n);
int deltaX = (slot - inv.getMinSlot()) % 9;
int deltaY = (slot - inv.getMinSlot()) / 9;
IntegerCoordinates coordinates = new IntegerCoordinates(inv.getX() + deltaX, inv.getY() + deltaY);
SkillTreeNode treeNode = inv.getSkillTree().getNode(coordinates);
Placeholders holders = new Placeholders();
holders.register("name", treeNode.getName());
holders.register("node-state", inv.getPlayerData().getNodeState(treeNode));
//Display what nodes this node unlocks
String str = "";
for (SkillTreeNode node : treeNode.getChildren())
str += node.getName() + ",";
//We remove the last comma
str = str.substring(0, str.length() - 1);
holders.register("unlocks", str);
//Display all the modifiers this node gives
str = "";
for (PlayerModifier playerModifier : treeNode.getModifiers()) {
//TODO
str += "\n" + playerModifier.getKey();
}
holders.register("modifiers", str);
return holders;
}
}
public class UnlockedSkillNodeItem extends InventoryItem<SkillTreeInventory> {
public UnlockedSkillNodeItem(ConfigurationSection config) {
super(config);
}
public ItemStack display(SkillTreeInventory inv, int n, IntegerCoordinates coordinates) {
return super.display(inv, n);
}
@Override
public Placeholders getPlaceholders(SkillTreeInventory inv, int n) {
int slot = getSlots().get(n);
int deltaX = (slot - inv.getMinSlot()) % 9;
int deltaY = (slot - inv.getMinSlot()) / 9;
IntegerCoordinates coordinates = new IntegerCoordinates(inv.getX() + deltaX, inv.getY() + deltaY);
SkillTreeNode treeNode = inv.getSkillTree().getNode(coordinates);
Placeholders holders = new Placeholders();
holders.register("name", treeNode.getName());
holders.register("node-state", inv.getPlayerData().getNodeState(treeNode));
String str = "";
for (SkillTreeNode node : treeNode.getChildren())
str += node.getName() + ",";
//We remove the last comma
str = str.substring(0, str.length() - 1);
holders.register("unlocks", str);
str = "";
for (PlayerModifier playerModifier : treeNode.getModifiers()) {
//TODO
str += "\n" + playerModifier.getKey();
}
holders.register("modifiers", str);
return holders;
}
}
public class PathTreeNodeItem extends SimplePlaceholderItem<SkillTreeInventory> {
public PathTreeNodeItem(ConfigurationSection config) {
super(config);
}
}
public class SkillTreeInventory extends GeneratedInventory {
private int x, y;
//width and height correspond to the the size of the 'board' representing the skill tree
private int minSlot, middleSlot, maxSlot;
private final int width, height;
private int treeListPage;
private final int maxTreeListPage;
private final SkillTree skillTree;
public SkillTreeInventory(PlayerData playerData, EditableInventory editable) {
super(playerData, editable);
skillTree = playerData.getCachedSkillTree();
maxTreeListPage = MMOCore.plugin.skillTreeManager.getAll().size() / 4;
//We get the width and height of the GUI(corresponding to the slots given)
List<Integer> slots = getByFunction("skill-tree-node").getSlots();
minSlot = 64;
maxSlot = 0;
for (int slot : slots) {
if (slot < minSlot)
minSlot = slot;
if (slot > maxSlot)
maxSlot = slot;
}
width = (maxSlot - minSlot) % 9;
height = (maxSlot - minSlot) / 9;
middleSlot = minSlot + width / 2 + 9 * (height / 2);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getTreeListPage() {
return treeListPage;
}
public int getMaxTreeListPage() {
return maxTreeListPage;
}
@Override
public String calculateName() {
return getEditable().getName().replace("{skill-tree-name}", skillTree.getName()).replace("{skill-tree-id}", skillTree.getId());
}
public SkillTree getSkillTree() {
return skillTree;
}
public int getMinSlot() {
return minSlot;
}
@Override
public void whenClicked(InventoryClickEvent event, InventoryItem item) {
if (event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) {
int offset = event.getSlot() - middleSlot;
x += offset % 9;
y += offset / 9;
open();
return;
}
if (item.getFunction().equals("next-tree-list-page")) {
treeListPage++;
open();
}
if (item.getFunction().equals("previous-tree-list-page")) {
treeListPage--;
open();
}
if (item.getFunction().equals("skill-tree")) {
String id = event.getCurrentItem().getItemMeta().getPersistentDataContainer().get(
new NamespacedKey(MMOCore.plugin, "skill-tree-id"), PersistentDataType.STRING);
playerData.setCachedSkillTree(MMOCore.plugin.skillTreeManager.get(id));
newInventory(playerData).open();
}
if (item.getFunction().equals("skill-tree-node")) {
}
}
}
}

View File

@ -134,9 +134,13 @@ public abstract class InventoryItem<T extends GeneratedInventory> {
} }
public ItemStack display(T inv,int n) { public ItemStack display(T inv,int n) {
return display(inv,n,null);
}
public ItemStack display(T inv, int n,Material specificMaterial) {
Placeholders placeholders = getPlaceholders(inv, n); Placeholders placeholders = getPlaceholders(inv, n);
ItemStack item = new ItemStack(material); ItemStack item = new ItemStack(specificMaterial==null?material:specificMaterial);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (texture != null && meta instanceof SkullMeta) if (texture != null && meta instanceof SkullMeta)

View File

@ -6,15 +6,7 @@ import java.util.logging.Level;
import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.ConfigFile; import net.Indyuce.mmocore.api.ConfigFile;
import net.Indyuce.mmocore.gui.AttributeView; import net.Indyuce.mmocore.gui.*;
import net.Indyuce.mmocore.gui.ClassConfirmation;
import net.Indyuce.mmocore.gui.ClassSelect;
import net.Indyuce.mmocore.gui.PlayerStats;
import net.Indyuce.mmocore.gui.QuestViewer;
import net.Indyuce.mmocore.gui.SkillList;
import net.Indyuce.mmocore.gui.SubclassConfirmation;
import net.Indyuce.mmocore.gui.SubclassSelect;
import net.Indyuce.mmocore.gui.WaypointViewer;
import net.Indyuce.mmocore.gui.api.EditableInventory; import net.Indyuce.mmocore.gui.api.EditableInventory;
import net.Indyuce.mmocore.gui.social.friend.EditableFriendList; import net.Indyuce.mmocore.gui.social.friend.EditableFriendList;
import net.Indyuce.mmocore.gui.social.friend.EditableFriendRemoval; import net.Indyuce.mmocore.gui.social.friend.EditableFriendRemoval;
@ -39,7 +31,7 @@ public class InventoryManager {
public static final EditableGuildCreation GUILD_CREATION = new EditableGuildCreation(); public static final EditableGuildCreation GUILD_CREATION = new EditableGuildCreation();
public static final QuestViewer QUEST_LIST = new QuestViewer(); public static final QuestViewer QUEST_LIST = new QuestViewer();
public static final AttributeView ATTRIBUTE_VIEW = new AttributeView(); public static final AttributeView ATTRIBUTE_VIEW = new AttributeView();
public static final SkillTreeViewer TREE_VIEW = new SkillTreeViewer();
public static final List<EditableInventory> list = Arrays.asList(PLAYER_STATS, ATTRIBUTE_VIEW, SKILL_LIST, CLASS_SELECT, SUBCLASS_SELECT, SUBCLASS_CONFIRM, QUEST_LIST, WAYPOINTS, CLASS_CONFIRM, FRIEND_LIST, FRIEND_REMOVAL, PARTY_VIEW, PARTY_CREATION, GUILD_VIEW, GUILD_CREATION); public static final List<EditableInventory> list = Arrays.asList(PLAYER_STATS, ATTRIBUTE_VIEW, SKILL_LIST, CLASS_SELECT, SUBCLASS_SELECT, SUBCLASS_CONFIRM, QUEST_LIST, WAYPOINTS, CLASS_CONFIRM, FRIEND_LIST, FRIEND_REMOVAL, PARTY_VIEW, PARTY_CREATION, GUILD_VIEW, GUILD_CREATION);
public static void load() { public static void load() {

View File

@ -1,21 +1,63 @@
package net.Indyuce.mmocore.manager; package net.Indyuce.mmocore.manager;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.gui.SkillTreeViewer;
import net.Indyuce.mmocore.manager.registry.MMOCoreRegister; import net.Indyuce.mmocore.manager.registry.MMOCoreRegister;
import net.Indyuce.mmocore.tree.SkillTree; import net.Indyuce.mmocore.tree.SkillTreeNode;
import net.Indyuce.mmocore.tree.skilltree.SkillTree;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Deprecated
public class SkillTreeManager extends MMOCoreRegister<SkillTree> { public class SkillTreeManager extends MMOCoreRegister<SkillTree> {
private final ArrayList<SkillTreeNode> skillTreeNodes = new ArrayList<>();
@Override @Override
public void register(SkillTree tree){
super.register(tree);
tree.getNodes().forEach((node)->skillTreeNodes.add(node));
}
public ArrayList<SkillTreeNode> getAllNodes() {
return skillTreeNodes;
}
public SkillTree get(int index) {
return registered.values().stream().collect(Collectors.toList()).get(index);
}
@Override
public String getRegisteredObjectName() { public String getRegisteredObjectName() {
return "skill tree"; return "skill tree";
} }
@Override @Override
public void initialize(boolean clearBefore) { public void initialize(boolean clearBefore) {
if (clearBefore) if (clearBefore)
registered.clear(); registered.clear();
File file = new File(MMOCore.plugin.getDataFolder() + "/skillTree");
if (!file.exists())
file.mkdirs();
load(file);
}
// TODO
public void load(File file) {
if (file.isDirectory()) {
List<File> fileList = Arrays.asList(file.listFiles()).stream().sorted().collect(Collectors.toList());
for (File child : fileList) {
load(child);
}
} else {
register(SkillTree.loadSkillTree(YamlConfiguration.loadConfiguration(file)));
}
} }
} }

View File

@ -10,6 +10,8 @@ import net.Indyuce.mmocore.api.player.stats.StatType;
import net.Indyuce.mmocore.guild.provided.Guild; import net.Indyuce.mmocore.guild.provided.Guild;
import net.Indyuce.mmocore.manager.data.DataProvider; import net.Indyuce.mmocore.manager.data.DataProvider;
import net.Indyuce.mmocore.manager.data.PlayerDataManager; import net.Indyuce.mmocore.manager.data.PlayerDataManager;
import net.Indyuce.mmocore.tree.NodeState;
import net.Indyuce.mmocore.tree.SkillTreeNode;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -67,11 +69,16 @@ public class YAMLPlayerDataManager extends PlayerDataManager {
for (String id : config.getStringList("bound-skills")) for (String id : config.getStringList("bound-skills"))
if (data.getProfess().hasSkill(id)) if (data.getProfess().hasSkill(id))
data.getBoundSkills().add(data.getProfess().getSkill(id)); data.getBoundSkills().add(data.getProfess().getSkill(id));
if (config.contains("times-claimed")) if (config.contains("times-claimed"))
for (String key : config.getConfigurationSection("times-claimed").getKeys(true)) for (String key : config.getConfigurationSection("times-claimed").getKeys(true))
data.getItemClaims().put(key, config.getInt("times-claimed." + key)); data.getItemClaims().put(key, config.getInt("times-claimed." + key));
for (SkillTreeNode node : MMOCore.plugin.skillTreeManager.getAllNodes()) {
String str = config.getString("skill-tree-nodes." + node.getTree().getId() + "." + node.getId());
if (str == null)
data.setNodeState(node, NodeState.LOCKED);
else
data.setNodeState(node,NodeState.valueOf(str));
}
// Load class slots, use try so the player can log in. // Load class slots, use try so the player can log in.
if (config.contains("class-info")) if (config.contains("class-info"))
for (String key : config.getConfigurationSection("class-info").getKeys(false)) for (String key : config.getConfigurationSection("class-info").getKeys(false))
@ -108,6 +115,10 @@ public class YAMLPlayerDataManager extends PlayerDataManager {
data.mapSkillLevels().forEach((key1, value) -> config.set("skill." + key1, value)); data.mapSkillLevels().forEach((key1, value) -> config.set("skill." + key1, value));
data.getItemClaims().forEach((key, times) -> config.set("times-claimed." + key, times)); data.getItemClaims().forEach((key, times) -> config.set("times-claimed." + key, times));
//Save the node states for the player
for (SkillTreeNode node : MMOCore.plugin.skillTreeManager.getAllNodes()) {
config.set("skill-tree-nodes." + node.getTree().getId() + "." + node.getId(),data.getNodeState(node));
}
List<String> boundSkills = new ArrayList<>(); List<String> boundSkills = new ArrayList<>();
data.getBoundSkills().forEach(skill -> boundSkills.add(skill.getSkill().getHandler().getId())); data.getBoundSkills().forEach(skill -> boundSkills.add(skill.getSkill().getHandler().getId()));
config.set("bound-skills", boundSkills); config.set("bound-skills", boundSkills);

View File

@ -6,8 +6,9 @@ import net.Indyuce.mmocore.quest.compat.QuestModule;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class MMOCoreQuestModule implements QuestModule { public class MMOCoreQuestModule implements QuestModule {
@Override @Override
public AbstractQuest getQuestOrThrow(String id) { public AbstractQuest getQuest(String id) {
Quest quest=MMOCore.plugin.questManager.get(id); Quest quest=MMOCore.plugin.questManager.get(id);
if(quest==null) if(quest==null)
return null; return null;

View File

@ -8,11 +8,13 @@ import fr.skytasul.quests.structure.Quest;
import net.Indyuce.mmocore.quest.AbstractQuest; import net.Indyuce.mmocore.quest.AbstractQuest;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.annotation.Nonnull;
public class BeautyQuestModule implements QuestModule<BeautyQuestModule.BeautyQuestQuest> { public class BeautyQuestModule implements QuestModule<BeautyQuestModule.BeautyQuestQuest> {
@Override @Override
public BeautyQuestQuest getQuestOrThrow(String questId) { public BeautyQuestQuest getQuest(String questId) {
Quest quest=QuestsAPI.getQuests().getQuest(Integer.parseInt(questId)); Quest quest=QuestsAPI.getQuests().getQuest(Integer.parseInt(questId));
return quest==null?null:new BeautyQuestQuest(quest); return quest==null?null:new BeautyQuestQuest(quest);
} }
@ -20,15 +22,15 @@ public class BeautyQuestModule implements QuestModule<BeautyQuestModule.BeautyQu
@Override @Override
public boolean hasCompletedQuest(String questId, Player player) { public boolean hasCompletedQuest(String questId, Player player) {
PlayerAccount account=PlayersManager.getPlayerAccount(player); PlayerAccount account=PlayersManager.getPlayerAccount(player);
PlayerQuestDatas quest=account.getQuestDatas(QuestsAPI.getQuests().getQuest(Integer.parseInt(questId))); Quest quest=QuestsAPI.getQuests().getQuest(Integer.parseInt(questId));
return quest.isFinished(); PlayerQuestDatas questData=account.getQuestDatas(quest);
return questData.isFinished();
} }
public class BeautyQuestQuest implements AbstractQuest { public class BeautyQuestQuest implements AbstractQuest {
Quest quest; private final Quest quest;
public BeautyQuestQuest(Quest quest) { public BeautyQuestQuest(Quest quest) {
this.quest = quest; this.quest = quest;

View File

@ -14,16 +14,17 @@ public class BlackVeinQuestsModule implements QuestModule<BlackVeinQuestsModule.
@Override @Override
public BlackVeinQuestQuest getQuestOrThrow(String id) { public BlackVeinQuestQuest getQuest(String id) {
Quests plugin = (Quests) Bukkit.getPluginManager().getPlugin("Quests"); Quest quest=plugin.getQuestById(id);
return plugin.getQuestById(id)==null?null:new BlackVeinQuestQuest(plugin.getQuestById(id)); return quest==null?null:new BlackVeinQuestQuest(quest);
} }
@Override @Override
public boolean hasCompletedQuest(String questId, Player player) { public boolean hasCompletedQuest(String questId, Player player) {
Quester quester = plugin.getQuester(player.getUniqueId()); Quester quester = plugin.getQuester(player.getUniqueId());
if(quester==null)
return false;
for(Quest quest:quester.getCompletedQuests()) { for(Quest quest:quester.getCompletedQuests()) {
if(quest.getId().equals(questId)) if(quest.getId().equals(questId))
return true; return true;

View File

@ -14,14 +14,15 @@ import java.util.List;
public class QuestCreatorModule implements QuestModule<QuestCreatorModule.QuestCreatorQuest>{ public class QuestCreatorModule implements QuestModule<QuestCreatorModule.QuestCreatorQuest>{
@Override @Override
public QuestCreatorQuest getQuestOrThrow(String id) { public QuestCreatorQuest getQuest(String id) {
return new QuestCreatorQuest(id); return new QuestCreatorQuest(id);
} }
@Override @Override
public boolean hasCompletedQuest(String questId, Player player) { public boolean hasCompletedQuest(String questId, Player player) {
UserQC playerData=UserQC.cachedOrNull(player); UserQC playerData=UserQC.cachedOrNull(player);
Validate.notNull(playerData,"QuestCreator User hasn't been loaded!"); if(playerData==null)
return false;
//Gets all the quests the player has succeeded at //Gets all the quests the player has succeeded at
List<QuestHistoryElement> elements=playerData.getQuestHistory().getElements(questId, Arrays.asList(QuestEndType.SUCCESS),0); List<QuestHistoryElement> elements=playerData.getQuestHistory().getElements(questId, Arrays.asList(QuestEndType.SUCCESS),0);
for(QuestHistoryElement el:elements) { for(QuestHistoryElement el:elements) {

View File

@ -3,12 +3,15 @@ package net.Indyuce.mmocore.quest.compat;
import net.Indyuce.mmocore.quest.AbstractQuest; import net.Indyuce.mmocore.quest.AbstractQuest;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.annotation.Nullable;
public interface QuestModule<T extends AbstractQuest> { public interface QuestModule<T extends AbstractQuest> {
/** /**
* @return Quest with given name * @return Quest with given name
*/ */
public T getQuestOrThrow(String id); @Nullable
public T getQuest(String id);
/** /**
* @return If a specific player did a certain quest * @return If a specific player did a certain quest

View File

@ -0,0 +1,5 @@
package net.Indyuce.mmocore.tree;
public enum NodeState {
LOCKED,UNLOCKED;
}

View File

@ -1,79 +0,0 @@
package net.Indyuce.mmocore.tree;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.manager.registry.RegisterObject;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
/**
* A passive skill tree that features nodes, or passive skills.
* <p>
* The player can explore the passive skill tree using the right GUI
* and unlock nodes by spending passive skill points. Unlocking nodes
* grant permanent player modifiers, including
* - stats
* - active or passive MythicLib skills
* - active or passive MMOCore skills
* - extra attribute pts
* - particle or potion effects
*
* @author jules
* @see {@link SkillTreeNode}
*/
public class SkillTree implements RegisterObject {
private final String id, name;
private final Map<IntegerCoordinates, SkillTreeNode> nodes = new HashMap<>();
public SkillTree(ConfigurationSection config) {
this.id = config.getName();
this.name = Objects.requireNonNull(config.getString("name"), "Could not find skill tree name");
Validate.isTrue(config.isConfigurationSection("nodes"), "Could not find tree passive skills");
for (String xKey : config.getConfigurationSection("nodes").getKeys(false))
for (String yKey : config.getConfigurationSection("nodes." + xKey).getKeys(false))
try {
int x = Integer.parseInt(xKey), y = Integer.parseInt(yKey);
SkillTreeNode node = new SkillTreeNode(this, x, y, config.getConfigurationSection("nodes." + xKey + "." + yKey));
nodes.put(node.getCoordinates(), node);
} catch (RuntimeException exception) {
MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load tree node '" + xKey + "." + yKey + "' for skill tree '" + id + "': " + exception.getMessage());
}
}
@Override
public String getId() {
return id;
}
public String getName() {
return name;
}
public Collection<SkillTreeNode> getNodes() {
return nodes.values();
}
@NotNull
public SkillTreeNode getNode(IntegerCoordinates coords) {
return Objects.requireNonNull(nodes.get(coords), "Could not find node in tree '" + id + "' with coordinates '" + coords.toString() + "'");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SkillTree skillTree = (SkillTree) o;
return id.equals(skillTree.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@ -4,27 +4,53 @@ import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.player.modifier.PlayerModifier; import io.lumine.mythic.lib.player.modifier.PlayerModifier;
import io.lumine.mythic.lib.util.configobject.ConfigSectionObject; import io.lumine.mythic.lib.util.configobject.ConfigSectionObject;
import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
import net.Indyuce.mmocore.player.Unlockable; import net.Indyuce.mmocore.player.Unlockable;
import net.Indyuce.mmocore.tree.skilltree.AutomaticSkillTree;
import net.Indyuce.mmocore.tree.skilltree.SkillTree;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.HashSet; import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class SkillTreeNode implements Unlockable { public class SkillTreeNode implements Unlockable {
private final SkillTree tree; private final SkillTree tree;
private final String name; private final String name,id;
private final IntegerCoordinates coordinates; private IntegerCoordinates coordinates;
private final List<String> lore; private final List<String> lore;
private final Set<PlayerModifier> modifiers = new HashSet<>(); private final Set<PlayerModifier> modifiers = new HashSet<>();
private final ArrayList<SkillTreeNode> children = new ArrayList<>();
private final ArrayList<SkillTreeNode> parents=new ArrayList<>();
public SkillTreeNode(SkillTree tree, ConfigurationSection config) {
Validate.notNull(config, "Config cannot be null");
this.id=config.getName();
this.tree = tree;
name = Objects.requireNonNull(config.getString("name"), "Could not find node name");
lore = config.getStringList("lore");
Validate.isTrue(config.contains("node-type"),"Could not find the node type");
//If coordinates are precised adn we are not wiht an automaticTreewe set them up
if((!(tree instanceof AutomaticSkillTree))&&config.contains("coordinates.x")&&config.contains("coordinates.y")) {
coordinates=new IntegerCoordinates(config.getInt("coordinates.x"),config.getInt("coordinates.y"));
}
for (String key : config.getConfigurationSection("modifiers").getKeys(false)) {
PlayerModifier mod = MythicLib.plugin.getModifiers().loadPlayerModifier(new ConfigSectionObject(config.getConfigurationSection(key)));
modifiers.add(mod);
}
}
public SkillTreeNode(SkillTree tree, int x, int y, ConfigurationSection config) { public SkillTreeNode(SkillTree tree, int x, int y, ConfigurationSection config) {
Validate.notNull(config, "Config cannot be null"); Validate.notNull(config, "Config cannot be null");
this.id=config.getName();
this.tree = tree; this.tree = tree;
name = Objects.requireNonNull(config.getString("name"), "Could not find node name"); name = Objects.requireNonNull(config.getString("name"), "Could not find node name");
Validate.isTrue(config.contains("node-type"),"Could not find the node type");
coordinates = new IntegerCoordinates(x, y); coordinates = new IntegerCoordinates(x, y);
lore = config.getStringList("lore"); lore = config.getStringList("lore");
for (String key : config.getConfigurationSection("modifiers").getKeys(false)) { for (String key : config.getConfigurationSection("modifiers").getKeys(false)) {
@ -33,6 +59,44 @@ public class SkillTreeNode implements Unlockable {
} }
} }
/**
*
*/
protected void whenPostLoaded(@NotNull ConfigurationSection config) {
}
public SkillTree getTree() {
return tree;
}
//Used when postLoaded
public void addParent(SkillTreeNode parent) {
parents.add(parent);
}
public void addChild(SkillTreeNode child) {children.add(child);}
public void setCoordinates(IntegerCoordinates coordinates) {
this.coordinates = coordinates;
}
public ArrayList<SkillTreeNode> getParents() {
return parents;
}
public ArrayList<SkillTreeNode> getChildren() {
return children;
}
public String getId() {
return id;
}
public String getName() { public String getName() {
return name; return name;
} }
@ -57,6 +121,8 @@ public class SkillTreeNode implements Unlockable {
return "skill_tree:" + tree.getId() + "_" + coordinates.getX() + "_" + coordinates.getY(); return "skill_tree:" + tree.getId() + "_" + coordinates.getX() + "_" + coordinates.getY();
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -91,4 +157,5 @@ public class SkillTreeNode implements Unlockable {
String treeId = treeIdBuilder.toString(); String treeId = treeIdBuilder.toString();
return MMOCore.plugin.skillTreeManager.get(treeId).getNode(coords); return MMOCore.plugin.skillTreeManager.get(treeId).getNode(coords);
} }
} }

View File

@ -0,0 +1,167 @@
package net.Indyuce.mmocore.tree.skilltree;
import net.Indyuce.mmocore.tree.IntegerCoordinates;
import net.Indyuce.mmocore.tree.SkillTreeNode;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import java.util.ArrayList;
import java.util.HashMap;
public class AutomaticSkillTree extends SkillTree {
private SkillTreeNode root;
//Represents all the coordinates that will be displayed as a path (between 2 nodes of the tree)
private final ArrayList<IntegerCoordinates> pathToParents = new ArrayList<>();
//Hash map to store the left and right branches of each node
private final HashMap<SkillTreeNode, Branches> nodeBranches = new HashMap<>();
public AutomaticSkillTree(ConfigurationSection config) {
super(config);
}
@Override
public void whenPostLoaded(ConfigurationSection config) {
//We setup the children and parents for each node.
for (SkillTreeNode node : nodes.values()) {
ConfigurationSection section = config.getConfigurationSection(node.getId());
for (String child : section.getStringList("children")) {
node.addChild(getNode(child));
getNode(child).addParent(node);
}
}
//We find the root of the tree wich is
for (SkillTreeNode node : nodes.values()) {
if (node.getParents().size() == 0) {
Validate.isTrue(root == null, "Their can't be more than 1 root in the skillTree!");
root = node;
}
}
//We setup the width of all the nodes recursively
setupTreeWidth(root);
//We recursively setup all the coordinates of the tree nodes
root.setCoordinates(new IntegerCoordinates(0, 0));
setupCoordinates(root);
//We get and cache the values of minX,minY,maxX and maxY
minX = nodeBranches.get(root).getLeftBranches();
minY = 0;
maxX = nodeBranches.get(root).getRightBranches();
for (SkillTreeNode node : nodes.values()) {
if (node.getCoordinates().getY() > maxY)
maxY = node.getCoordinates().getY();
}
//Eventually we setup the coordinateNodesMap
super.setupCoordinatesNodesMap();
}
/**
* Recursive algorithm to automatically calculate the integercoordinates each node should have to have a good display.
* It also fills the list pathToParents representing all the coordinates corresponding to a path between 2 nodes (for the GUI)
*
* @param node the root
*/
private void setupCoordinates(SkillTreeNode node) {
int childrenSize = node.getChildren().size();
int x = node.getCoordinates().getX();
;
int y = node.getCoordinates().getY();
;
int leftOffset = 0;
int rightOffset = 0;
for (int i = 0; i < childrenSize; i++) {
SkillTreeNode child = node.getChildren().get(i);
if (childrenSize % 2 == 0 && i == 0) {
child.setCoordinates(new IntegerCoordinates(x, y + 2));
leftOffset += 2 + nodeBranches.get(child).getLeftBranches();
rightOffset += 2 + nodeBranches.get(child).getRightBranches();
} else if (i % 2 == 0) {
child.setCoordinates(new IntegerCoordinates(x - leftOffset - 2 - nodeBranches.get(child).getWidth(), y + 2));
leftOffset += 2 + nodeBranches.get(child).getWidth();
} else {
child.setCoordinates(new IntegerCoordinates(x + rightOffset + 2 + nodeBranches.get(child).getWidth(), y + 2));
rightOffset += 2 + nodeBranches.get(child).getWidth();
}
//We setup the path to parent variable (Used for the GUI)
int childX = child.getCoordinates().getX();
int childY = child.getCoordinates().getY();
int parentX = node.getParents().get(0).getCoordinates().getX();
pathToParents.add(new IntegerCoordinates(childX, childY - 1));
int offset = childX > parentX ? -1 : 1;
while (childX != parentX) {
pathToParents.add(new IntegerCoordinates(childX, childY - 2));
childX += offset;
}
//We setup the coordinates for the associated child
setupCoordinates(child);
}
}
public Branches getBranches(SkillTreeNode node) {
return nodeBranches.get(node);
}
/**
* Recursively sed to setup all the right and left branches of the node to later determine its coordinates for GUI display
*/
public void setupTreeWidth(SkillTreeNode node) {
int childrenSize = node.getChildren().size();
int leftBranches = 0;
int rightBranches = 0;
for (int i = 0; i < childrenSize; i++) {
SkillTreeNode child = node.getChildren().get(i);
setupTreeWidth(child);
if (childrenSize % 2 == 0 && i == 0) {
leftBranches += nodeBranches.get(child).getLeftBranches();
rightBranches += nodeBranches.get(child).getRightBranches();
} else if (i % 2 == 0) {
leftBranches += nodeBranches.get(child).getWidth() + 2;
} else {
rightBranches += nodeBranches.get(child).getWidth() + 2;
}
}
}
@Override
public boolean isPath(IntegerCoordinates coordinates) {
return pathToParents.contains(coordinates);
}
private class Branches {
private final int leftBranches, rightBranches;
public Branches(int leftBranches, int rightBranches) {
this.leftBranches = leftBranches;
this.rightBranches = rightBranches;
}
public int getLeftBranches() {
return leftBranches;
}
public int getRightBranches() {
return rightBranches;
}
public int getWidth() {
return leftBranches + rightBranches;
}
}
}

View File

@ -0,0 +1,21 @@
package net.Indyuce.mmocore.tree.skilltree;
import net.Indyuce.mmocore.tree.IntegerCoordinates;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
public class CustomSkillTree extends SkillTree{
public CustomSkillTree(ConfigurationSection config) {
super(config);
}
@Override
protected void whenPostLoaded(@NotNull ConfigurationSection configurationSection) {
}
@Override
public boolean isPath(IntegerCoordinates coordinates) {
return false;
}
}

View File

@ -0,0 +1,64 @@
package net.Indyuce.mmocore.tree.skilltree;
import io.netty.handler.codec.http.cookie.CookieDecoder;
import net.Indyuce.mmocore.tree.IntegerCoordinates;
import net.Indyuce.mmocore.tree.SkillTreeNode;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
public class LinkedSkillTree extends SkillTree{
public LinkedSkillTree(ConfigurationSection config) {
super(config);
//We setup the coordinate map because coordinates are given in the yml for linked skill tree
setupCoordinatesNodesMap();
}
@Override
protected void whenPostLoaded(@NotNull ConfigurationSection configurationSection) {
SkillTreeNode root=getNode(new IntegerCoordinates(0,0));
Validate.notNull(root,"Their must be a node(the root of the tree) at the coordinates (0,0) ");
//We setup all the children and parent relations between the nodes
setupChildren(root);
}
/**
* There is no paths on a linked skill tree
*/
@Override
public boolean isPath(IntegerCoordinates coordinates) {
return false;
}
/**
* Recursive algorithm to setup the parents and children of each skillTreeNode
*/
public void setupChildren(SkillTreeNode node) {
int x=node.getCoordinates().getX();
int y=node.getCoordinates().getY();
List<IntegerCoordinates> checkCoordinates= Arrays.asList(new IntegerCoordinates(x+1,y),
new IntegerCoordinates(x-1,y),new IntegerCoordinates(x,y+1),new IntegerCoordinates(x,y-1));
for(IntegerCoordinates coor:checkCoordinates) {
//We add Parent and child only if the node exists and doesn't have a parent already
if(isNode(coor)) {
SkillTreeNode child=getNode(coor);
if(child.getParents().size()==0) {
child.addParent(node);
node.addChild(child);
//We call recursively the algorithm
setupChildren(child);
}}
}
}
}

View File

@ -0,0 +1,154 @@
package net.Indyuce.mmocore.tree.skilltree;
import io.lumine.mythic.lib.api.util.PostLoadObject;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
import net.Indyuce.mmocore.manager.registry.RegisterObject;
import net.Indyuce.mmocore.tree.IntegerCoordinates;
import net.Indyuce.mmocore.tree.NodeState;
import net.Indyuce.mmocore.tree.SkillTreeNode;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* A passive skill tree that features nodes, or passive skills.
* <p>
* The player can explore the passive skill tree using the right GUI
* and unlock nodes by spending passive skill points. Unlocking nodes
* grant permanent player modifiers, including
* - stats
* - active or passive MythicLib skills
* - active or passive MMOCore skills
* - extra attribute pts
* - particle or potion effects
*
* @author jules
* @author Ka0rX
* @see {@link SkillTreeNode}
*/
public abstract class SkillTree extends PostLoadObject implements RegisterObject {
private final String id, name;
private final Material guiMaterial;
//2 different maps to get the nodes
protected final Map<IntegerCoordinates, SkillTreeNode> coordinatesNodes = new HashMap<>();
protected final Map<String, SkillTreeNode> nodes = new HashMap<>();
//Caches the height of the skill tree
protected int minX, minY, maxX, maxY;
public SkillTree(ConfigurationSection config) {
super(config);
this.id = Objects.requireNonNull(config.getString("id"), "Could not find skill tree id");
this.name = Objects.requireNonNull(config.getString("name"), "Could not find skill tree name");
this.guiMaterial = Material.valueOf(MMOCoreUtils.toEnumName(Objects.requireNonNull(config.getString("material"))));
Validate.isTrue(config.isConfigurationSection("nodes"), "Could not find any nodes in the tree");
for (String key : config.getConfigurationSection("nodes").getKeys(false)) {
SkillTreeNode node = new SkillTreeNode(this, config.getConfigurationSection("nodes." + key));
}
}
public void setupCoordinatesNodesMap() {
for (SkillTreeNode node : nodes.values()) {
coordinatesNodes.put(node.getCoordinates(), node);
}
}
@Override
protected abstract void whenPostLoaded(@NotNull ConfigurationSection configurationSection);
public int getMaxX() {
return maxX;
}
public int getMinX() {
return minX;
}
public int getMinY() {
return minY;
}
public int getMaxY() {
return maxY;
}
public static SkillTree loadSkillTree(ConfigurationSection config) {
String string = config.getString("type");
Validate.isTrue(string.equals("automatic") || string.equals("linked") || string.equals("custom"), "You must precise the type of the skill tree in the yml!" +
"\nAllowed values: 'automatic','linked','custom'");
SkillTree skillTree = null;
if (string.equals("automatic")) {
skillTree = new AutomaticSkillTree(config);
skillTree.postLoad();
}
if (string.equals("linked")) {
skillTree = new LinkedSkillTree(config);
skillTree.postLoad();
}
if (string.equals("custom")) {
skillTree = new CustomSkillTree(config);
skillTree.postLoad();
}
return skillTree;
}
@Nullable
/**
* Returns null if it is not a node and returns the node type if it a node
*/
public boolean isNode(IntegerCoordinates coordinates) {
for (SkillTreeNode node : nodes.values()) {
if (node.getCoordinates().equals(coordinates))
return true;
}
return false;
}
public abstract boolean isPath(IntegerCoordinates coordinates);
public Material getGuiMaterial() {
return guiMaterial;
}
@Override
public String getId() {
return id;
}
public String getName() {
return name;
}
public Collection<SkillTreeNode> getNodes() {
return nodes.values();
}
@NotNull
public SkillTreeNode getNode(IntegerCoordinates coords) {
return Objects.requireNonNull(nodes.get(coords), "Could not find node in tree '" + id + "' with coordinates '" + coords.toString() + "'");
}
@NotNull
public SkillTreeNode getNode(String name) {
return Objects.requireNonNull(nodes.get(name), "Could not find node in tree '" + id + "' with name '" + name + "'");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SkillTree skillTree = (SkillTree) o;
return id.equals(skillTree.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@ -0,0 +1,7 @@
package net.Indyuce.mmocore.tree.skilltree;
public enum SkillTreeType {
AUTOMATIC_SKILL_TREE(),
LINKED_SKILL_TREE,
CUSTOM_SKILL_TREE;
}

View File

@ -28,6 +28,9 @@ guild:
withdraw: withdraw:
main: "withdraw" main: "withdraw"
aliases: ["w"] aliases: ["w"]
skill-tree:
main: "skilltree"
aliase: ["st"]
deposit: deposit:
main: "deposit" main: "deposit"
aliases: ["d"] aliases: ["d"]

View File

@ -0,0 +1,25 @@
# GUI display name
name: 'Current Skill Tree: &6{skill-tree-name}'
# Number of slots in your inventory. Must be
# between 9 and 54 and must be a multiple of 9.
slots: 54
items:
skill-tree:
name: '{skill-tree-node}'
function: 'skill-tree'
slots: [9,18,27,36]
next-tree-list-page:
function: 'next-tree-list-page'
material: 'ARROW'
slots: [ 45 ]
previous-tree-list-page:
function: 'previous-tree-list-page'
material: "ARROW"
slots: [ 0 ]
skill-tree-node:
function: 'skill-tree-node'

View File

@ -0,0 +1,29 @@
id: 'combat'
name: 'Combat Skill Tree'
#The type of the Skill tree, can be :
#'linked'->You must only precise nodes coordinates. 2 adjacent nodes will be affiliated. The root is at coordinates 0,0.
#'automatic'-> You must only precise the children(there can more than 1) of each node. Each node can only have one parent and the root has none.
# The display coordinates will be automatically calculated to have a good render.
#'custom'-> You must precise coordinates and children for each node, each node can have multiple parents and children.
# The coordinates are only used for display on the GUI but will not have any impact on the affiliation between nodes.
type: 'linked'
#The material that will represent the skill tree in the GUI
material: 'DIAMOND_SWORD'
nodes:
strength:
name: 'Combat strength'
#Coordinates of the node
coordinates:
x: 0
y: 0
strength2:
name: 'Combat strength 2'
coordinates:
x: 1
y: 0