diff --git a/README.md b/README.md index 998057e..e4ea120 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Objective types can be enabled or disabled individually via the plugin's setting | `/language` | Open the language selection menu | | `/points ` | Modify a player's points | | `/randomteams ` | Creates random teams with a specific size | +| `/recipe ` | Displays the recipe of an item | | `/reset [player]` | Reset the battle or a specific player | | `/result ` | Show detailed results for a player | | `/settings` | Open the plugin's settings menu | diff --git a/src/main/java/net/fameless/forcebattle/ForceBattle.java b/src/main/java/net/fameless/forcebattle/ForceBattle.java index 51b7f79..8835c96 100644 --- a/src/main/java/net/fameless/forcebattle/ForceBattle.java +++ b/src/main/java/net/fameless/forcebattle/ForceBattle.java @@ -15,7 +15,6 @@ import net.fameless.forcebattle.game.Timer; import net.fameless.forcebattle.gui.GUIListener; import net.fameless.forcebattle.scoreboard.ScoreboardManager; -import net.fameless.forcebattle.tablist.TablistManager; import net.fameless.forcebattle.util.BukkitUtil; import net.fameless.forcebattle.util.ResourceUtil; import net.kyori.adventure.text.Component; @@ -93,7 +92,6 @@ private void initCore() { Command.createInstances(); BossbarManager.runTask(); - TablistManager.startUpdating(); ScoreboardManager.startUpdater(); PluginUpdater.checkForUpdate(); } diff --git a/src/main/java/net/fameless/forcebattle/command/RecipeCommand.java b/src/main/java/net/fameless/forcebattle/command/RecipeCommand.java new file mode 100644 index 0000000..ef79c4a --- /dev/null +++ b/src/main/java/net/fameless/forcebattle/command/RecipeCommand.java @@ -0,0 +1,63 @@ +package net.fameless.forcebattle.command; + +import net.fameless.forcebattle.command.framework.CallerType; +import net.fameless.forcebattle.command.framework.Command; +import net.fameless.forcebattle.command.framework.CommandCaller; +import net.fameless.forcebattle.gui.impl.CraftingRecipeGUI; +import net.fameless.forcebattle.player.BattlePlayer; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class RecipeCommand extends Command { + + public RecipeCommand() { + super( + "recipe", + List.of(), + CallerType.PLAYER, + "/recipe ", + "forcebattle.recipe", + "Opens the crafting recipe GUI for the specified item" + ); + } + + @Override + public void executeCommand(@NotNull CommandCaller caller, String[] args) { + if (!(caller instanceof BattlePlayer player)) return; + + if (args.length == 0) { + player.getPlayer().sendMessage("§cUsage: /item "); + return; + } + + String input = args[0].toUpperCase(); + Material material = Material.matchMaterial(input); + + if (material == null) { + player.getPlayer().sendMessage("§cInvalid item: " + args[0]); + return; + } + + ItemStack stack = new ItemStack(material); + new CraftingRecipeGUI(stack).open(player); + } + + @Override + public List tabComplete(CommandCaller caller, String[] args) { + if (args.length == 1) { + String prefix = args[0].toUpperCase(); + List suggestions = new ArrayList<>(); + for (Material mat : Material.values()) { + if (mat.isItem() && mat.name().startsWith(prefix)) { + suggestions.add(mat.name().toLowerCase()); + } + } + return suggestions; + } + return List.of(); + } +} diff --git a/src/main/java/net/fameless/forcebattle/command/framework/Command.java b/src/main/java/net/fameless/forcebattle/command/framework/Command.java index 76118b0..b0dc867 100644 --- a/src/main/java/net/fameless/forcebattle/command/framework/Command.java +++ b/src/main/java/net/fameless/forcebattle/command/framework/Command.java @@ -11,6 +11,7 @@ import net.fameless.forcebattle.command.LanguageCommand; import net.fameless.forcebattle.command.PointsCommand; import net.fameless.forcebattle.command.RandomTeamsCommand; +import net.fameless.forcebattle.command.RecipeCommand; import net.fameless.forcebattle.command.ResetCommand; import net.fameless.forcebattle.command.ResultCommand; import net.fameless.forcebattle.command.SettingsCommand; @@ -91,6 +92,7 @@ public static void createInstances() { new LanguageCommand(); new PointsCommand(); new RandomTeamsCommand(); + new RecipeCommand(); new ResetCommand(); new ResultCommand(); new SettingsCommand(); diff --git a/src/main/java/net/fameless/forcebattle/command/framework/PermissionManager.java b/src/main/java/net/fameless/forcebattle/command/framework/PermissionManager.java index 1d8d70e..2955708 100644 --- a/src/main/java/net/fameless/forcebattle/command/framework/PermissionManager.java +++ b/src/main/java/net/fameless/forcebattle/command/framework/PermissionManager.java @@ -21,6 +21,7 @@ public class PermissionManager { Map.entry("forcebattle.result.animated", PermissionDefault.OP), Map.entry("forcebattle.backpack", PermissionDefault.TRUE), Map.entry("forcebattle.displayresults", PermissionDefault.OP), + Map.entry("forcebattle.recipe", PermissionDefault.TRUE), Map.entry("forcebattle.joker", PermissionDefault.OP), Map.entry("forcebattle.points", PermissionDefault.OP), Map.entry("forcebattle.help", PermissionDefault.TRUE), diff --git a/src/main/java/net/fameless/forcebattle/gui/impl/CraftingRecipeGUI.java b/src/main/java/net/fameless/forcebattle/gui/impl/CraftingRecipeGUI.java new file mode 100644 index 0000000..6674a37 --- /dev/null +++ b/src/main/java/net/fameless/forcebattle/gui/impl/CraftingRecipeGUI.java @@ -0,0 +1,162 @@ +package net.fameless.forcebattle.gui.impl; + +import net.fameless.forcebattle.gui.ForceBattleGUI; +import net.fameless.forcebattle.gui.GUIClickableItem; +import net.fameless.forcebattle.gui.GUIItem; +import net.fameless.forcebattle.player.BattlePlayer; +import net.fameless.forcebattle.util.ItemStackCreator; +import net.fameless.forcebattle.util.Skull; +import net.fameless.forcebattle.util.StringUtility; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; +import java.util.Map; + +public class CraftingRecipeGUI extends ForceBattleGUI { + + private final ItemStack result; + private final List recipes; + private final int page; + private static final int[] GRID_SLOTS = {1, 2, 3, 10, 11, 12, 19, 20, 21}; + + public CraftingRecipeGUI(ItemStack result) { + this(result, 0); + } + + private CraftingRecipeGUI(ItemStack result, int page) { + super("Recipe for " + StringUtility.formatName(result.getType().name()) + " §7(" + (page + 1) + ")", 27); + this.result = result; + this.recipes = Bukkit.getRecipesFor(result); + this.page = page; + } + + @Override + public void setItems(BattlePlayer player) { + fill(ItemStackCreator.createNamedItemStack(Material.GRAY_STAINED_GLASS_PANE, " ")); + + set(new GUIItem(16) { + @Override + public ItemStack getItem(BattlePlayer player) { + return result; + } + }); + + set(new GUIItem(14) { + @Override + public ItemStack getItem(BattlePlayer player) { + return ItemStackCreator.getSkull(Skull.ARROW_RIGHT, " ", 1); + } + }); + + for (int slot : GRID_SLOTS) { + set(new GUIItem(slot) { + @Override + public ItemStack getItem(BattlePlayer player) { + return ItemStackCreator.getStack(" ", Material.AIR, 1); + } + }); + } + + if (recipes.size() > 1) { + if (page > 0) { + set(GUIClickableItem.getGoBackItem(0, new CraftingRecipeGUI(result, page - 1))); + } + if (page < recipes.size() - 1) { + set(GUIClickableItem.getGoForthItem(8, new CraftingRecipeGUI(result, page + 1))); + } + } + + if (recipes.isEmpty()) { + set(new GUIItem(11) { + @Override + public ItemStack getItem(BattlePlayer player) { + ItemStack barrier = new ItemStack(Material.BARRIER); + ItemMeta meta = barrier.getItemMeta(); + if (meta != null) meta.setDisplayName("§cNo recipe found"); + barrier.setItemMeta(meta); + return barrier; + } + }); + return; + } + + Recipe recipe = recipes.get(page); + ItemStack[] grid = new ItemStack[9]; + + if (recipe instanceof ShapedRecipe shaped) { + Map choiceMap = shaped.getChoiceMap(); + String[] shape = shaped.getShape(); + for (int row = 0; row < Math.min(shape.length, 3); row++) { + String line = shape[row]; + for (int col = 0; col < Math.min(line.length(), 3); col++) { + char c = line.charAt(col); + RecipeChoice choice = choiceMap.get(c); + if (choice instanceof RecipeChoice.MaterialChoice matChoice && !matChoice.getChoices().isEmpty()) { + grid[row * 3 + col] = new ItemStack(matChoice.getChoices().getFirst()); + } else if (choice instanceof RecipeChoice.ExactChoice exact && !exact.getChoices().isEmpty()) { + grid[row * 3 + col] = exact.getChoices().getFirst().clone(); + } + } + } + + } else if (recipe instanceof ShapelessRecipe shapeless) { + List ingredients = shapeless.getChoiceList(); + for (int i = 0; i < Math.min(ingredients.size(), 9); i++) { + RecipeChoice choice = ingredients.get(i); + if (choice instanceof RecipeChoice.MaterialChoice matChoice && !matChoice.getChoices().isEmpty()) { + grid[i] = new ItemStack(matChoice.getChoices().getFirst()); + } else if (choice instanceof RecipeChoice.ExactChoice exact && !exact.getChoices().isEmpty()) { + grid[i] = exact.getChoices().getFirst().clone(); + } + } + } + + int minRow = 3, maxRow = -1, minCol = 3, maxCol = -1; + for (int i = 0; i < 9; i++) { + if (grid[i] != null) { + int row = i / 3, col = i % 3; + minRow = Math.min(minRow, row); + maxRow = Math.max(maxRow, row); + minCol = Math.min(minCol, col); + maxCol = Math.max(maxCol, col); + } + } + + int height = (maxRow - minRow + 1); + int width = (maxCol - minCol + 1); + int rowOffset = 0, colOffset = 0; + + if (height == 1) rowOffset = 1 - minRow; + if (width == 1) colOffset = 1 - minCol; + if (height == 1 && width == 1) { + rowOffset = 1 - minRow; + colOffset = 1 - minCol; + } + + for (int i = 0; i < 9; i++) { + ItemStack ingredient = grid[i]; + if (ingredient == null) continue; + + int row = i / 3 + rowOffset; + int col = i % 3 + colOffset; + if (row < 0 || row > 2 || col < 0 || col > 2) continue; + + int guiSlot = GRID_SLOTS[row * 3 + col]; + final ItemStack finalIngredient = ingredient; + set(new GUIItem(guiSlot) { + @Override + public ItemStack getItem(BattlePlayer player) { + return finalIngredient; + } + }); + } + } + + @Override + public boolean allowHotkeying() { + return false; + } +} diff --git a/src/main/java/net/fameless/forcebattle/tablist/TablistManager.java b/src/main/java/net/fameless/forcebattle/tablist/TablistManager.java deleted file mode 100644 index f12ecfe..0000000 --- a/src/main/java/net/fameless/forcebattle/tablist/TablistManager.java +++ /dev/null @@ -1,88 +0,0 @@ -package net.fameless.forcebattle.tablist; - -import net.fameless.forcebattle.ForceBattle; -import net.fameless.forcebattle.player.BattlePlayer; -import net.fameless.forcebattle.util.BattleType; -import net.fameless.forcebattle.util.StringUtility; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -public class TablistManager { - - private static final ForceBattle plugin = ForceBattle.get(); - - public static void startUpdating() { - new BukkitRunnable() { - @Override - public void run() { - updateAllTablists(); - } - }.runTaskTimer(plugin, 0, 20); - } - - public static void updateAllTablists() { - BattlePlayer.getOnlinePlayers().forEach(player -> { - if (player.isInTeam()) { - player.getPlayer().setPlayerListOrder(player.getTeam().getId()); - } else { - player.getPlayer().setPlayerListOrder(999); - } - - updateTablist(player.getPlayer()); - }); - } - - public static void updateTablist(Player player) { - /* - MiniMessage miniMessage = MiniMessage.miniMessage(); - - String header = miniMessage.serialize( - Component.text("⏳ Time Left: " + Format.formatTime(ForceBattle.getTimer().getTime())) - ); - - StringBuilder footerBuilder = new StringBuilder(); - - // Show all teams first - List teams = Team.teams; - for (Team team : teams) { - footerBuilder.append("Team ").append(team.getId()).append(":\n"); - for (BattlePlayer battlePlayer : team.getPlayers()) { - footerBuilder.append(formatPlayerLineString(battlePlayer)).append("\n"); - } - } - - // Show any players not in a team - for (Player p : Bukkit.getOnlinePlayers()) { - BattlePlayer bp = BattlePlayer.adapt(p); - if (!bp.isInTeam()) { - footerBuilder.append("Solo:\n"); - footerBuilder.append(formatPlayerLineString(bp)).append("\n"); - } - } - - player.setPlayerListHeaderFooter(header, footerBuilder.toString()); - */ - } - - private static String formatPlayerLineString(BattlePlayer player) { - String name = player.getName(); - String objective = player.getObjective() != null - ? StringUtility.formatName(player.getObjective().getObjectiveString()) - : "None"; - - String color = "§f"; // white by default - if (player.getObjective() != null) { - BattleType type = player.getObjective().getBattleType(); - color = switch (type) { - case FORCE_ITEM -> "§a"; // green - case FORCE_MOB -> "§c"; // red - case FORCE_BIOME -> "§e"; // yellow - case FORCE_ADVANCEMENT -> "§d"; // light purple - case FORCE_HEIGHT, FORCE_COORDS -> "§7"; // gray - case FORCE_STRUCTURE -> "§b"; // aqua - }; - } - - return color + " - " + name + " | " + objective; - } -} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d6fb9a2..ad8a60c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -18,6 +18,7 @@ commands: aliases: [ lang ] points: randomteams: + recipe: reset: result: settings: