Added the possibility to open specific skill tree GUI and customize the GUI differently for each skill tree through specific-skill-tree/ folder.

This commit is contained in:
Ka0rX 2023-07-09 19:17:25 +01:00
parent 05180a4acc
commit 070c725712
7 changed files with 522 additions and 42 deletions

View File

@ -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 <skilltree_id>");
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 <skilltree-id>");
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 <skilltree-id>");
else
sender.sendMessage(ChatColor.RED + "Usage: /skilltrees");
return false;
}
else {
MMOCore.plugin.configManager.getSimpleMessage("no-skill-tree").send(player);
return true;
}
}
}

View File

@ -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<DisplayInfo, Icon> icons = new HashMap<>();
protected final Map<SkillTreeStatus, String> 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<SkillTree> skillTrees;
@NotNull
private SkillTree skillTree;
private final List<Integer> 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;
}

View File

@ -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;

View File

@ -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<String, ClassConfirmation> 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<String, SkillTreeViewer> SPECIFIC_TREE_VIEW = new HashMap<>();
public static final Map<String, ClassConfirmation> CLASS_CONFIRM = new HashMap<>();
public static final List<EditableInventory> list = new ArrayList(Arrays.asList(PLAYER_STATS, ATTRIBUTE_VIEW, TREE_VIEW, SKILL_LIST, CLASS_SELECT, SUBCLASS_SELECT, QUEST_LIST, WAYPOINTS, FRIEND_LIST, FRIEND_REMOVAL, PARTY_VIEW, PARTY_CREATION, GUILD_VIEW, GUILD_CREATION));
public static 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<String> ids;
private final BiFunction<String, Boolean, ? extends EditableInventory> provider;
SpecificInventoryLoader(String name, Map inventories, List<String> ids,
BiFunction<String, Boolean, ? extends EditableInventory> provider) {
this.name = name;
this.inventories = inventories;
this.ids = ids;
this.provider = provider;
}
}
}

View File

@ -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 <skilltree_id>. 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

View File

@ -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}'

View File

@ -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