From 070c7257129cd8d381636427c38aa6574be7b90c Mon Sep 17 00:00:00 2001 From: Ka0rX Date: Sun, 9 Jul 2023 19:17:25 +0100 Subject: [PATCH] Added the possibility to open specific skill tree GUI and customize the GUI differently for each skill tree through specific-skill-tree/ folder. --- .../mmocore/command/SkillTreesCommand.java | 53 ++- .../gui/skilltree/SkillTreeViewer.java | 30 +- .../mmocore/manager/ConfigManager.java | 5 +- .../mmocore/manager/InventoryManager.java | 92 +++-- MMOCore-Dist/src/main/resources/config.yml | 7 +- .../main/resources/default/gui/skill-tree.yml | 1 + .../specific-skill-tree-default.yml | 376 ++++++++++++++++++ 7 files changed, 522 insertions(+), 42 deletions(-) create mode 100644 MMOCore-Dist/src/main/resources/default/gui/specific-skill-tree/specific-skill-tree-default.yml diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/SkillTreesCommand.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/SkillTreesCommand.java index bf1513a0..cf3d60de 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/SkillTreesCommand.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/command/SkillTreesCommand.java @@ -1,5 +1,6 @@ package net.Indyuce.mmocore.command; +import io.lumine.mythic.lib.UtilityMethods; import net.Indyuce.mmocore.MMOCore; import net.Indyuce.mmocore.api.player.PlayerData; import net.Indyuce.mmocore.api.event.MMOCommandEvent; @@ -7,14 +8,19 @@ import net.Indyuce.mmocore.command.api.RegisteredCommand; import net.Indyuce.mmocore.command.api.ToggleableCommand; import net.Indyuce.mmocore.manager.InventoryManager; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import java.util.stream.Collectors; + public class SkillTreesCommand extends RegisteredCommand { public SkillTreesCommand(ConfigurationSection config) { super(config, ToggleableCommand.SKILL_TREES); + + } @Override @@ -27,14 +33,47 @@ public class SkillTreesCommand extends RegisteredCommand { MMOCommandEvent event = new MMOCommandEvent(data, "skilltrees"); Bukkit.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) - return true; - if (data.getProfess().getSkillTrees().size() != 0) { - InventoryManager.TREE_VIEW.newInventory(data).open(); + return false; + + // Default skilltree command + if (args.length == 0) { + if (!MMOCore.plugin.configManager.enableGlobalSkillTreeGUI) { + sender.sendMessage(ChatColor.RED + "Usage: /skilltrees "); + return false; + } + if (data.getProfess().getSkillTrees().size() != 0) { + InventoryManager.TREE_VIEW.newInventory(data).open(); + return true; + } else { + MMOCore.plugin.configManager.getSimpleMessage("no-skill-tree").send(player); + return false; + } + } + if (args.length == 1) { + if (!MMOCore.plugin.configManager.enableSpecificSkillTreeGUI) { + sender.sendMessage(ChatColor.RED + "Usage: /skilltrees "); + return false; + } + + if (data.getProfess().getSkillTrees() + .stream() + .filter(skillTree -> UtilityMethods.ymlName(skillTree.getId()).equals(UtilityMethods.ymlName(args[0]))) + .collect(Collectors.toList()) + .size() != 0) { + InventoryManager.SPECIFIC_TREE_VIEW.get(UtilityMethods.ymlName(args[0])).newInventory(data).open(); + return true; + } else { + sender.sendMessage(ChatColor.RED + "Your class does not have a skill tree with id: " + args[0]); + return false; + } + } else { + if (MMOCore.plugin.configManager.enableSpecificSkillTreeGUI) + sender.sendMessage(ChatColor.RED + "Usage: /skilltrees "); + else + sender.sendMessage(ChatColor.RED + "Usage: /skilltrees"); return false; } - else { - MMOCore.plugin.configManager.getSimpleMessage("no-skill-tree").send(player); - return true; - } + } + } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/skilltree/SkillTreeViewer.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/skilltree/SkillTreeViewer.java index a7f9b52a..a2623a1b 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/skilltree/SkillTreeViewer.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/gui/skilltree/SkillTreeViewer.java @@ -30,6 +30,8 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.stream.Collectors; @@ -38,8 +40,21 @@ public class SkillTreeViewer extends EditableInventory { protected final Map icons = new HashMap<>(); protected final Map statusNames = new HashMap<>(); + @Nullable + /** + * A null skillTree means the global skill tree view is opened. + * Else this GUI represents a specific skill tree. + */ + private final SkillTree skillTree; + public SkillTreeViewer() { super("skill-tree"); + this.skillTree = null; + } + + public SkillTreeViewer(SkillTree skillTree, boolean isDefault) { + super("specific-skill-tree-" + (isDefault ? "default" : UtilityMethods.ymlName(skillTree.getId()))); + this.skillTree = skillTree; } @Override @@ -113,7 +128,7 @@ public class SkillTreeViewer extends EditableInventory { public SkillTreeInventory newInventory(PlayerData playerData) { - return new SkillTreeInventory(playerData, this); + return new SkillTreeInventory(playerData, this, skillTree); } @@ -309,20 +324,24 @@ public class SkillTreeViewer extends EditableInventory { 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 int minSlot, maxSlot; private final int width, height; private int treeListPage; private final int maxTreeListPage; private final List skillTrees; + @NotNull private SkillTree skillTree; private final List slots; - public SkillTreeInventory(PlayerData playerData, EditableInventory editable) { + public SkillTreeInventory(PlayerData playerData, EditableInventory editable, SkillTree skillTree) { super(playerData, editable); skillTrees = playerData.getProfess().getSkillTrees(); - skillTree = skillTrees.get(0); - maxTreeListPage = (skillTrees.size() - 1) / editable.getByFunction("skill-tree").getSlots().size(); + this.skillTree = skillTree == null ? skillTrees.get(0) : skillTree; + if (skillTree == null) + maxTreeListPage = (skillTrees.size() - 1) / editable.getByFunction("skill-tree").getSlots().size(); + else + maxTreeListPage = 0; //We get the width and height of the GUI(corresponding to the slots given) slots = editable.getByFunction("skill-tree-node").getSlots(); minSlot = 64; @@ -335,7 +354,6 @@ public class SkillTreeViewer extends EditableInventory { } width = (maxSlot - minSlot) % 9; height = (maxSlot - minSlot) / 9; - middleSlot = minSlot + width / 2 + 9 * (height / 2); x -= width / 2; y -= height / 2; } diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java index 2e030706..891bc969 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/ConfigManager.java @@ -28,7 +28,7 @@ public class ConfigManager { public final CommandVerbose commandVerbose = new CommandVerbose(); public boolean overrideVanillaExp, canCreativeCast, passiveSkillNeedBound, cobbleGeneratorXP, saveDefaultClassInfo, splitMainExp, splitProfessionExp, disableQuestBossBar, - pvpModeEnabled, pvpModeInvulnerabilityCanDamage, forceClassSelection; + pvpModeEnabled, pvpModeInvulnerabilityCanDamage, forceClassSelection,enableGlobalSkillTreeGUI,enableSpecificSkillTreeGUI; public String partyChatPrefix, noSkillBoundPlaceholder; public ChatColor staminaFull, staminaHalf, staminaEmpty; public long combatLogTimer, lootChestExpireTime, lootChestPlayerCooldown, globalSkillCooldown; @@ -120,7 +120,8 @@ public class ConfigManager { } catch (Exception exception) { MMOCore.plugin.getLogger().log(Level.WARNING, "Could not find damage cause called '" + key + "'"); } - + enableGlobalSkillTreeGUI = MMOCore.plugin.getConfig().getBoolean("enable-global-skill-tree-gui"); + enableSpecificSkillTreeGUI = MMOCore.plugin.getConfig().getBoolean("enable-specific-skill-tree-gui"); lootChestExpireTime = Math.max(MMOCore.plugin.getConfig().getInt("loot-chests.chest-expire-time"), 1) * 20; lootChestPlayerCooldown = (long) MMOCore.plugin.getConfig().getDouble("player-cooldown") * 1000L; globalSkillCooldown = MMOCore.plugin.getConfig().getLong("global-skill-cooldown") * 50; diff --git a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/InventoryManager.java b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/InventoryManager.java index 3186e776..44bc68d9 100644 --- a/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/InventoryManager.java +++ b/MMOCore-API/src/main/java/net/Indyuce/mmocore/manager/InventoryManager.java @@ -1,28 +1,29 @@ package net.Indyuce.mmocore.manager; -import java.util.*; -import java.util.logging.Level; - +import io.lumine.mythic.lib.UtilityMethods; import net.Indyuce.mmocore.MMOCore; -import net.Indyuce.mmocore.api.player.profess.PlayerClass; -import net.Indyuce.mmocore.api.util.MMOCoreUtils; +import net.Indyuce.mmocore.api.ConfigFile; +import net.Indyuce.mmocore.gui.*; +import net.Indyuce.mmocore.gui.api.EditableInventory; import net.Indyuce.mmocore.gui.skilltree.SkillTreeViewer; import net.Indyuce.mmocore.gui.social.friend.EditableFriendList; -import net.Indyuce.mmocore.gui.*; import net.Indyuce.mmocore.gui.social.friend.EditableFriendRemoval; import net.Indyuce.mmocore.gui.social.guild.EditableGuildCreation; import net.Indyuce.mmocore.gui.social.guild.EditableGuildView; import net.Indyuce.mmocore.gui.social.party.EditablePartyCreation; import net.Indyuce.mmocore.gui.social.party.EditablePartyView; -import net.Indyuce.mmocore.api.ConfigFile; -import net.Indyuce.mmocore.gui.api.EditableInventory; +import org.bukkit.Bukkit; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.logging.Level; +import java.util.stream.Collectors; public class InventoryManager { public static final PlayerStats PLAYER_STATS = new PlayerStats(); public static final SkillList SKILL_LIST = new SkillList(); public static final ClassSelect CLASS_SELECT = new ClassSelect(); public static final SubclassSelect SUBCLASS_SELECT = new SubclassSelect(); - public static final Map CLASS_CONFIRM = new HashMap<>(); public static final WaypointViewer WAYPOINTS = new WaypointViewer(); public static final EditableFriendList FRIEND_LIST = new EditableFriendList(); public static final EditableFriendRemoval FRIEND_REMOVAL = new EditableFriendRemoval(); @@ -33,34 +34,73 @@ public class InventoryManager { public static final QuestViewer QUEST_LIST = new QuestViewer(); public static final AttributeView ATTRIBUTE_VIEW = new AttributeView(); public static final SkillTreeViewer TREE_VIEW = new SkillTreeViewer(); + public static Map SPECIFIC_TREE_VIEW = new HashMap<>(); + public static final Map CLASS_CONFIRM = new HashMap<>(); + public static final List list = new ArrayList(Arrays.asList(PLAYER_STATS, ATTRIBUTE_VIEW, TREE_VIEW, SKILL_LIST, CLASS_SELECT, SUBCLASS_SELECT, QUEST_LIST, WAYPOINTS, FRIEND_LIST, FRIEND_REMOVAL, PARTY_VIEW, PARTY_CREATION, GUILD_VIEW, GUILD_CREATION)); public static void load() { - final String classConfirmFolder = "gui/class-confirm"; - try { - MMOCore.plugin.configManager.loadDefaultFile(classConfirmFolder, "class-confirm-default.yml"); - } catch (Exception exception) { - MMOCore.log(Level.WARNING, "Could not load inventory 'class-confirm/class-confirm-default" + "': " + exception.getMessage()); + //Loads the specific inventories + for (SpecificInventoryLoader loader : SpecificInventoryLoader.values()) { + try { + MMOCore.plugin.configManager.loadDefaultFile("gui/" + loader.name, loader.name + "-default.yml"); + } catch (Exception exception) { + MMOCore.log(Level.WARNING, "Could not load inventory 'gui/" + loader.name + "/" + loader.name + "-default" + "': " + exception.getMessage()); + } + for (String id : loader.ids) { + String formattedId = UtilityMethods.ymlName(id); + final ConfigFile configFile = new ConfigFile("gui/" + loader.name, loader.name + "-" + formattedId); + final EditableInventory GUI = loader.provider.apply(id, !configFile.exists()); + loader.inventories.put(formattedId, GUI); + MMOCore.log("/gui/" + loader.name + "/" + GUI.getId()); + GUI.reload(new ConfigFile("/gui/" + loader.name, GUI.getId()).getConfig()); + } } - - for (PlayerClass playerClass : MMOCore.plugin.classManager.getAll()) { - final String classId = MMOCoreUtils.ymlName(playerClass.getId()); - final ConfigFile configFile = new ConfigFile(classConfirmFolder, "class-confirm-" + classId); - final ClassConfirmation GUI = configFile.exists() ? new ClassConfirmation(playerClass, false) : new ClassConfirmation(playerClass, true); - CLASS_CONFIRM.put(MMOCoreUtils.ymlName(playerClass.getId()), GUI); - GUI.reload(new ConfigFile("/" + classConfirmFolder, GUI.getId()).getConfig()); - } - list.forEach(inv -> { - String folder = "gui" + (inv instanceof ClassConfirmation ? "/class-confirm" : ""); try { - MMOCore.plugin.configManager.loadDefaultFile(folder, inv.getId() + ".yml"); - inv.reload(new ConfigFile("/" + folder, inv.getId()).getConfig()); + MMOCore.plugin.configManager.loadDefaultFile("gui", inv.getId() + ".yml"); + inv.reload(new ConfigFile("/gui", inv.getId()).getConfig()); } catch (Exception exception) { MMOCore.log(Level.WARNING, "Could not load inventory '" + (inv instanceof ClassConfirmation ? "class-confirm/" : "") + inv.getId() + "': " + exception.getMessage()); } }); } + + public enum SpecificInventoryLoader { + CLASS_CONFIRM("class-confirm", + InventoryManager.CLASS_CONFIRM, + MMOCore.plugin.classManager.getAll(). + stream(). + map(playerClass -> playerClass.getId()). + collect(Collectors.toList()), + (id, isDefault) -> new ClassConfirmation(MMOCore.plugin.classManager.get(id), isDefault) + ), + + SPECIFIC_TREE("specific-skill-tree", + InventoryManager.SPECIFIC_TREE_VIEW, + MMOCore.plugin.skillTreeManager.getAll(). + stream(). + map(skillTree -> skillTree.getId()). + collect(Collectors.toList()), + (id, isDefault) -> new SkillTreeViewer(MMOCore.plugin.skillTreeManager.get(id), isDefault)); + + private final String name; + + private final Map inventories; + + private final List ids; + + private final BiFunction provider; + + SpecificInventoryLoader(String name, Map inventories, List ids, + BiFunction provider) { + this.name = name; + this.inventories = inventories; + this.ids = ids; + this.provider = provider; + } + + } } diff --git a/MMOCore-Dist/src/main/resources/config.yml b/MMOCore-Dist/src/main/resources/config.yml index 490ed8b3..c47756f7 100644 --- a/MMOCore-Dist/src/main/resources/config.yml +++ b/MMOCore-Dist/src/main/resources/config.yml @@ -212,6 +212,12 @@ shift-click-player-profile-check: false # whenever a player earns main class exp display-main-class-exp-holograms: true +#This is the GUI opened with /skilltrees. It enables to switch from 1 skill tree to another. +enable-global-skill-tree-gui: true + +#This is the GUI opened with /skilltrees . It only displays this specific skill tree without allowing changes. +enable-specific-skill-tree-gui: true + # Requires a SERVER reload when changed. death-exp-loss: enabled: false @@ -222,7 +228,6 @@ death-exp-loss: # Maximum number of skill slots. This means that you cannot unlock more than X skill slots. max-skill-slots: 8 - # When set to true, passive skills must be bound in order to take effect. # When set to false, unlocked skills will take effect right away. # This is only the default behavior for skills but can be overridden by specifying true/false to diff --git a/MMOCore-Dist/src/main/resources/default/gui/skill-tree.yml b/MMOCore-Dist/src/main/resources/default/gui/skill-tree.yml index e385a325..37f31414 100644 --- a/MMOCore-Dist/src/main/resources/default/gui/skill-tree.yml +++ b/MMOCore-Dist/src/main/resources/default/gui/skill-tree.yml @@ -80,6 +80,7 @@ items: #The lore that will be displayed after the lore of the node. #The {node-lore} placeholder will be replaced by the lore specified in the skill tree node config. #All the placeholders you see here can also be used in the node lore. + #For debugging custom display you can use the {display-type} placeholder. lore: - '&7Current State: &6{current-state}' - '&7Current Level: &6{current-level}' diff --git a/MMOCore-Dist/src/main/resources/default/gui/specific-skill-tree/specific-skill-tree-default.yml b/MMOCore-Dist/src/main/resources/default/gui/specific-skill-tree/specific-skill-tree-default.yml new file mode 100644 index 00000000..bd169b6c --- /dev/null +++ b/MMOCore-Dist/src/main/resources/default/gui/specific-skill-tree/specific-skill-tree-default.yml @@ -0,0 +1,376 @@ +# GUI display name +name: '&7{skill-tree-name} Skill Tree.' + +# Number of slots in your inventory. Must be +# between 9 and 54 and must be a multiple of 9. +slots: 54 + +items: + + up: + function: up + item: PLAYER_HEAD + name: "Up" + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTk5YWFmMjQ1NmE2MTIyZGU4ZjZiNjI2ODNmMmJjMmVlZDlhYmI4MWZkNWJlYTFiNGMyM2E1ODE1NmI2NjkifX19 + slots: [ 50 ] + down: + function: down + item: PLAYER_HEAD + name: "Down" + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzkxMmQ0NWIxYzc4Y2MyMjQ1MjcyM2VlNjZiYTJkMTU3NzdjYzI4ODU2OGQ2YzFiNjJhNTQ1YjI5YzcxODcifX19 + slots: [ 49 ] + right: + function: right + item: PLAYER_HEAD + name: "Right" + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTNmYzUyMjY0ZDhhZDllNjU0ZjQxNWJlZjAxYTIzOTQ3ZWRiY2NjY2Y2NDkzNzMyODliZWE0ZDE0OTU0MWY3MCJ9fX0= + slots: [ 51 ] + left: + function: left + item: PLAYER_HEAD + name: "Left" + texture: eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWYxMzNlOTE5MTlkYjBhY2VmZGMyNzJkNjdmZDg3YjRiZTg4ZGM0NGE5NTg5NTg4MjQ0NzRlMjFlMDZkNTNlNiJ9fX0= + slots: [ 48 ] + + reallocation: + function: reallocation + item: CAULDRON + slots: [ 45 ] + name: '&aReallocate Skill Tree Points' + lore: + - '' + - '&7You have spent a total of &6{point-spent}&7 skill tree points.' + - '&7The maximum points that can be spent is: &6{max-point-spent}' + - '&7Right click to reallocate them.' + - '' + - '&eCosts 1 attribute reallocation point.' + - '&e◆ Skill Tree Reallocation Points: &6{realloc-points}' + + skill-tree-node: + + function: 'skill-tree-node' + slots: [ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44 ] + #The lore that will be displayed after the lore of the node. + #The {node-lore} placeholder will be replaced by the lore specified in the skill tree node config. + #All the placeholders you see here can also be used in the node lore. + #For debugging custom display you can use the {display-type} placeholder. + lore: + - '&7Current State: &6{current-state}' + - '&7Current Level: &6{current-level}' + - '&7Max Level: &6{max-level}' + - '&7Max Children: &6{max-children}' + - '&7Points required: &6{point-consumed}' + - '&7Size: &6{size}' + - '--------------------' + - '&7⧆ &6Requirements: ' + - '&fStrong Parents: ' + - '{strong-parents}' + - '' + - '&fSoft Parents: ' + - '{soft-parents}' + - '--------------------' + - '{node-lore}' + - '--------------------' + - '&e◆Max points for {skill-tree}&e: &6{max-point-spent}' + - '&e◆Points spent for {skill-tree}&e:&6 {point-spent}' + - '&e◆Current {skill-tree} &epoints: &6{skill-tree-points}' + - '&e◆Current &6global&e points: &6{global-points}' + path-lore: + - '' + +#This is the name that will be displayed for each status. +status-names: + unlocked: 'Unlocked' + unlockable: 'Unlockable' + locked: 'Locked' + fully-locked: 'Fully Locked' + + +display: + paths: + unlocked: + up: + item: "WHITE_DYE" + custom-model-data: 0 + up-right: + item: "WHITE_DYE" + custom-model-data: 0 + up-left: + item: "WHITE_DYE" + custom-model-data: 0 + down-right: + item: "WHITE_DYE" + custom-model-data: 0 + down-left: + item: "WHITE_DYE" + custom-model-data: 0 + right: + item: "WHITE_DYE" + custom-model-data: 0 + default: + item: "WHITE_DYE" + custom-model-data: 0 + unlockable: + up: + item: "BLUE_DYE" + custom-model-data: 0 + up-right: + item: "BLUE_DYE" + custom-model-data: 0 + up-left: + item: "BLUE_DYE" + custom-model-data: 0 + down-right: + item: "BLUE_DYE" + custom-model-data: 0 + down-left: + item: "BLUE_DYE" + custom-model-data: 0 + right: + item: "BLUE_DYE" + custom-model-data: 0 + default: + item: "BLUE_DYE" + custom-model-data: 0 + locked: + up: + item: "GRAY_DYE" + custom-model-data: 0 + up-right: + item: "GRAY_DYE" + custom-model-data: 0 + up-left: + item: "GRAY_DYE" + custom-model-data: 0 + down-right: + item: "GRAY_DYE" + custom-model-data: 0 + down-left: + item: "GRAY_DYE" + custom-model-data: 0 + right: + item: "GRAY_DYE" + custom-model-data: 0 + default: + item: "GRAY_DYE" + custom-model-data: 0 + fully-locked: + up: + item: "BLACK_DYE" + custom-model-data: 0 + up-right: + item: "BLACK_DYE" + custom-model-data: 0 + up-left: + item: "BLACK_DYE" + custom-model-data: 0 + down-right: + item: "BLACK_DYE" + custom-model-data: 0 + down-left: + item: "BLACK_DYE" + custom-model-data: 0 + right: + item: "BLACK_DYE" + custom-model-data: 0 + default: + item: "BLACK_DYE" + custom-model-data: 0 + nodes: + unlocked: + up-right-down-left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + up-right-down: + item: "WHITE_CONCRETE" + custom-model-data: 0 + up-right-left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + up-down-left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + down-right-left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + up-right: + item: "WHITE_CONCRETE" + custom-model-data: 0 + up-down: + item: "WHITE_CONCRETE" + custom-model-data: 0 + up-left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + down-right: + item: "WHITE_CONCRETE" + custom-model-data: 0 + down-left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + right-left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + right: + item: "WHITE_CONCRETE" + custom-model-data: 0 + left: + item: "WHITE_CONCRETE" + custom-model-data: 0 + up: + item: "WHITE_CONCRETE" + custom-model-data: 0 + down: + item: "WHITE_CONCRETE" + custom-model-data: 0 + no-path: + item: "WHITE_CONCRETE" + custom-model-data: 0 + locked: + up-right-down-left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + up-right-down: + item: "GRAY_CONCRETE" + custom-model-data: 0 + up-right-left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + up-down-left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + down-right-left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + up-right: + item: "GRAY_CONCRETE" + custom-model-data: 0 + up-down: + item: "GRAY_CONCRETE" + custom-model-data: 0 + up-left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + down-right: + item: "GRAY_CONCRETE" + custom-model-data: 0 + down-left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + right-left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + right: + item: "GRAY_CONCRETE" + custom-model-data: 0 + left: + item: "GRAY_CONCRETE" + custom-model-data: 0 + up: + item: "GRAY_CONCRETE" + custom-model-data: 0 + down: + item: "GRAY_CONCRETE" + custom-model-data: 0 + no-path: + item: "GRAY_CONCRETE" + custom-model-data: 0 + unlockable: + up-right-down-left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + up-right-down: + item: "BLUE_CONCRETE" + custom-model-data: 0 + up-right-left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + up-down-left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + down-right-left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + up-right: + item: "BLUE_CONCRETE" + custom-model-data: 0 + up-down: + item: "BLUE_CONCRETE" + custom-model-data: 0 + up-left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + down-right: + item: "BLUE_CONCRETE" + custom-model-data: 0 + down-left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + right-left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + right: + item: "BLUE_CONCRETE" + custom-model-data: 0 + left: + item: "BLUE_CONCRETE" + custom-model-data: 0 + up: + item: "BLUE_CONCRETE" + custom-model-data: 0 + down: + item: "BLUE_CONCRETE" + custom-model-data: 0 + no-path: + item: "BLUE_CONCRETE" + custom-model-data: 0 + fully-locked: + up-right-down-left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + up-right-down: + item: "BLACK_CONCRETE" + custom-model-data: 0 + up-right-left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + up-down-left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + down-right-left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + up-right: + item: "BLACK_CONCRETE" + custom-model-data: 0 + up-down: + item: "BLACK_CONCRETE" + custom-model-data: 0 + up-left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + down-right: + item: "BLACK_CONCRETE" + custom-model-data: 0 + down-left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + right-left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + right: + item: "BLACK_CONCRETE" + custom-model-data: 0 + left: + item: "BLACK_CONCRETE" + custom-model-data: 0 + up: + item: "BLACK_CONCRETE" + custom-model-data: 0 + down: + item: "BLACK_CONCRETE" + custom-model-data: 0 + no-path: + item: "BLACK_CONCRETE" + custom-model-data: 0 +