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}
+
+
+
+ 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