From 17097ec27acc5328176fa15b24d30073c70146a5 Mon Sep 17 00:00:00 2001 From: wling Date: Wed, 15 Jan 2025 23:18:25 +0800 Subject: [PATCH] init --- .gitignore | 113 +++++++++ pom.xml | 72 ++++++ .../example/calcStack/CalcStackPlugin.java | 42 ++++ .../example/calcStack/CalculateCommand.java | 221 ++++++++++++++++++ src/main/resources/config.yml | 7 + src/main/resources/lang.yml | 21 ++ src/main/resources/plugin.yml | 12 + 7 files changed, 488 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/example/calcStack/CalcStackPlugin.java create mode 100644 src/main/java/com/example/calcStack/CalculateCommand.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/lang.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ab926e0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.example.calcStack + CalcStack + 1.0 + jar + + CalcStack + + + 21 + UTF-8 + + + + clean package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + + + org.spigotmc + spigot-api + 1.21-R0.1-SNAPSHOT + provided + + + diff --git a/src/main/java/com/example/calcStack/CalcStackPlugin.java b/src/main/java/com/example/calcStack/CalcStackPlugin.java new file mode 100644 index 0000000..6ac2108 --- /dev/null +++ b/src/main/java/com/example/calcStack/CalcStackPlugin.java @@ -0,0 +1,42 @@ +package com.example.calcStack; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.util.Objects; + +public class CalcStackPlugin extends JavaPlugin { + + private FileConfiguration langConfig; + + @Override + public void onEnable() { + saveDefaultConfig(); // 生成默认 config.yml + + // 初始化语言文件 + createLangFile(); + + // 注册指令 + Objects.requireNonNull(getCommand("calculate")).setExecutor(new CalculateCommand(this)); + } + + @Override + public void onDisable() { + // 插件关闭逻辑 + } + + public FileConfiguration getLangConfig() { + return langConfig; + } + + private void createLangFile() { + File langFile = new File(getDataFolder(), "lang.yml"); + if (!langFile.exists()) { + saveResource("lang.yml", false); + } + langConfig = YamlConfiguration.loadConfiguration(langFile); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/calcStack/CalculateCommand.java b/src/main/java/com/example/calcStack/CalculateCommand.java new file mode 100644 index 0000000..1746467 --- /dev/null +++ b/src/main/java/com/example/calcStack/CalculateCommand.java @@ -0,0 +1,221 @@ +package com.example.calcStack; + +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; +import org.bukkit.Bukkit; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CalculateCommand implements TabExecutor { + + private final CalcStackPlugin plugin; + + public CalculateCommand(@Nonnull CalcStackPlugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@Nullable CommandSender sender, @Nullable Command command, @Nullable String label, @Nonnull String[] args) { + // 确保 sender 不为 null + if (sender == null) { + return false; + } + + if (!(sender instanceof Player player)) { + sender.sendMessage(getLangMessage("error.not_player")); + return true; + } + + Material material; + int quantity; + + try { + if (args.length == 1) { + // 使用玩家手上的物品 + ItemStack itemInHand = player.getInventory().getItemInMainHand(); + if (itemInHand.getType() == Material.AIR) { + sender.sendMessage(getLangMessage("error.no_item_in_hand")); + return true; + } + material = itemInHand.getType(); + quantity = Integer.parseInt(args[0]); + } else if (args.length == 2) { + // 指定物品 ID 和数量 + material = Material.matchMaterial(args[0]); + if (material == null) { + sender.sendMessage(getLangMessage("error.invalid_item").replace("{item}", args[0])); + return true; + } + quantity = Integer.parseInt(args[1]); + } else { + sender.sendMessage(getLangMessage("usage")); + return true; + } + } catch (NumberFormatException e) { + sender.sendMessage(getLangMessage("error.invalid_number")); + return true; + } + + // 异步处理合成配方和计算 + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + // 获取配置的箱子大小和最大堆叠数量 + int maxStackSize = material.getMaxStackSize(); + if (plugin.getConfig().getBoolean("custom-stack-size.enabled")) { + maxStackSize = plugin.getConfig().getInt("custom-stack-size.value", maxStackSize); + } + int chestSize = plugin.getConfig().getInt("chest-size", 27); + + // 计算组和余数 + int totalStacks = quantity / maxStackSize; + int remainingItems = quantity % maxStackSize; + + // 计算需要的箱子数量 + int chests = (totalStacks + (remainingItems > 0 ? 1 : 0) + chestSize - 1) / chestSize; + + // 获取物品的合成原材料数量 + List materialDetails = getCraftingMaterials(material, quantity); + + // 回到主线程发送消息 + int finalMaxStackSize = maxStackSize; + Bukkit.getScheduler().runTask(plugin, () -> { + sender.sendMessage(getLangMessage("result.header")); + sender.sendMessage(getLangMessage("result.item").replace("{item}", material.name().toLowerCase())); + sender.sendMessage(getLangMessage("result.quantity").replace("{quantity}", String.valueOf(quantity))); + sender.sendMessage(getLangMessage("result.stacks").replace("{stacks}", String.valueOf(totalStacks)) + .replace("{stack_size}", String.valueOf(finalMaxStackSize))); + sender.sendMessage(getLangMessage("result.remaining").replace("{remaining}", String.valueOf(remainingItems))); + sender.sendMessage(getLangMessage("result.chests").replace("{chests}", String.valueOf(chests)) + .replace("{chest_size}", String.valueOf(chestSize))); + + if (!materialDetails.isEmpty()) { + sender.sendMessage(getLangMessage("result.ingredients.header")); + for (String detail : materialDetails) { + sender.sendMessage(detail); + } + } + + sender.sendMessage(getLangMessage("result.footer")); + }); + }); + + return true; + } + + /** + * 获取物品的合成原材料及数量,数量将根据目标物品数量进行加倍 + * + * @param material 物品 + * @param quantity 物品数量 + * @return 返回物品的所有原材料及数量 + */ + private List getCraftingMaterials(Material material, int quantity) { + Map materialMap = new HashMap<>(); // 用于合并重复的原材料 + List materialDetails = new ArrayList<>(); + ItemStack result = new ItemStack(material); + + // 获取该物品的所有配方 + List recipes = getRecipesFor(result); + + if (recipes.isEmpty()) { + return materialDetails; // 如果没有配方,返回空列表 + } + + // 遍历所有的合成配方 + for (Recipe recipe : recipes) { + if (recipe instanceof ShapedRecipe shapedRecipe) { + Map ingredients = shapedRecipe.getIngredientMap(); + for (ItemStack ingredient : ingredients.values()) { + // 添加null检查 + if (ingredient != null && ingredient.getType() != Material.AIR) { + // 计算该原材料的数量 + materialMap.put(ingredient.getType(), materialMap.getOrDefault(ingredient.getType(), 0) + ingredient.getAmount() * quantity); + } + } + } else if (recipe instanceof ShapelessRecipe shapelessRecipe) { + for (ItemStack ingredient : shapelessRecipe.getIngredientList()) { + // 添加null检查 + if (ingredient != null && ingredient.getType() != Material.AIR) { + // 计算该原材料的数量 + materialMap.put(ingredient.getType(), materialMap.getOrDefault(ingredient.getType(), 0) + ingredient.getAmount() * quantity); + } + } + } + } + + // 格式化输出原材料数量,并计算组、余数、箱 + for (Map.Entry entry : materialMap.entrySet()) { + Material ingredientType = entry.getKey(); + int totalQuantity = entry.getValue(); + + // 计算堆叠数量、组、余数、箱 + int maxStackSize = ingredientType.getMaxStackSize(); + int totalStacks = totalQuantity / maxStackSize; + int remainingItems = totalQuantity % maxStackSize; + int chestSize = plugin.getConfig().getInt("chest-size", 27); + int chests = (totalStacks + (remainingItems > 0 ? 1 : 0) + chestSize - 1) / chestSize; + + // 根据新的语言文件路径输出原材料信息 + String detail = getLangMessage("result.ingredients.item") + .replace("{ingredient}", ingredientType.name().toLowerCase()) + .replace("{quantity}", String.valueOf(totalQuantity)); + + // 输出组、余数、箱的信息 + materialDetails.add(detail); + materialDetails.add(getLangMessage("result.ingredients.stacks") + .replace("{stacks}", String.valueOf(totalStacks)) + .replace("{stack_size}", String.valueOf(maxStackSize))); + materialDetails.add(getLangMessage("result.ingredients.remaining") + .replace("{remaining}", String.valueOf(remainingItems))); + materialDetails.add(getLangMessage("result.ingredients.chests") + .replace("{chests}", String.valueOf(chests)) + .replace("{chest_size}", String.valueOf(chestSize))); + } + + return materialDetails; + } + + /** + * 使用提供的物品获取所有相关配方 + * + * @param result 物品 + * @return 返回所有相关配方 + */ + private List getRecipesFor(ItemStack result) { + // 获取所有配方 + return Bukkit.getRecipesFor(result); + } + + @Override + @Nonnull + public List onTabComplete(@Nonnull CommandSender sender, @Nonnull Command command, @Nonnull String alias, @Nonnull String[] args) { + if (args.length == 1) { + // 提供所有 Material 名称作为补全选项 + List completions = new ArrayList<>(); + for (Material material : Material.values()) { + if (material.name().toLowerCase().startsWith(args[0].toLowerCase())) { + completions.add(material.name().toLowerCase()); + } + } + return completions; + } + return new ArrayList<>(); + } + + @Nonnull + private String getLangMessage(@Nonnull String path) { + String message = plugin.getLangConfig().getString(path); + return message != null ? message : "§c配置错误: 未找到语言文件内容 (" + path + ")!"; + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..2f093d3 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,7 @@ +# 最大堆叠数量设置 +custom-stack-size: + enabled: false + value: 64 + +# 每箱的大小(格子数量) +chest-size: 27 diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml new file mode 100644 index 0000000..134550b --- /dev/null +++ b/src/main/resources/lang.yml @@ -0,0 +1,21 @@ +usage: "§e用法: /calculate <数量> 或 /calculate <物品ID> <数量>" +error: + not_player: "§c该指令只能由玩家使用!" + no_item_in_hand: "§c请手持一个有效的物品,或在指令中指定物品 ID!" + invalid_item: "§c无效的物品 ID: {item}" + invalid_number: "§c请输入有效的数量!" + +result: + header: "§a===== 计算结果 =====" + item: "§b物品: {item}" + quantity: "§b数量: {quantity}" + stacks: "§b堆叠: {stacks} 组 ({stack_size} 个/组)" + remaining: "§b剩余: {remaining} 个" + chests: "§b需要箱子: {chests} 个 (每个箱子 {chest_size} 格)" + footer: "§a====================" + ingredients: + header: "§a===== 原材料 =====" + item: "§b- {ingredient}: {quantity} 个" + stacks: " §7- {stacks} 组 ({stack_size} 个/组)" + remaining: " §7- 剩余 {remaining} 个" + chests: " §7- 需要箱子: {chests} 个 (每个箱子 {chest_size} 格)" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..887516c --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: CalcStack +version: '1.0' +main: com.example.calcStack.CalcStackPlugin +api-version: '1.13' +load: STARTUP + +commands: + calculate: + description: 计算物品堆叠和箱子数量 + usage: / <数量> 或 / <物品ID> <数量> + aliases: + - calc \ No newline at end of file