feat: CraftBank Folia 1.21.8 经济与银行核心插件初始实现

实现完整的现金/钱包、银行卡、支票、活期储蓄与定期存款系统,
实现并以 Highest 优先级注册 Vault Economy 接口,接入 PlaceholderAPI。
全程遵循 Folia 调度模型(AsyncScheduler / 实体区域线程),
数据缓存线程安全,支票兑现与定期领取做防刷处理。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Purpur Build
2026-06-29 18:16:06 +08:00
parent 6a5ee9906b
commit eddc706c9a
36 changed files with 3775 additions and 0 deletions
@@ -0,0 +1,122 @@
package com.craftbank.items;
import com.craftbank.CraftBank;
import com.craftbank.model.PlayerAccount;
import com.craftbank.util.Text;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 银行卡实体物品的创建与校验。银行卡通过 PDC 绑定持卡人 UUID 与序列号,
* 序列号用于挂失/补办: 旧卡序列号与账户当前序列号不符即作废。
*/
public class BankCardManager {
private final CraftBank plugin;
private final Keys keys;
public BankCardManager(CraftBank plugin) {
this.plugin = plugin;
this.keys = plugin.getKeys();
}
/**
* 为玩家签发一张新银行卡, 并更新账户的有效序列号 (旧卡随之作废)。
*/
public ItemStack issueCard(Player player, PlayerAccount account) {
long serial = System.currentTimeMillis();
account.setCardSerial(serial);
plugin.persistAsync(account);
return buildCard(player.getName(), player.getUniqueId(), serial);
}
private ItemStack buildCard(String ownerName, UUID owner, long serial) {
Material material = parseMaterial(plugin.getConfig().getString("Items.bank_card.material", "PAPER"), Material.PAPER);
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
String name = plugin.getConfig().getString("Items.bank_card.name", "&b工艺银行 · 储蓄卡");
meta.setDisplayName(Text.color(name.replace("%owner%", ownerName == null ? "?" : ownerName)));
List<String> lore = new ArrayList<>();
for (String line : plugin.getConfig().getStringList("Items.bank_card.lore")) {
lore.add(Text.color(line.replace("%owner%", ownerName == null ? "?" : ownerName)));
}
meta.setLore(lore);
int cmd = plugin.getConfig().getInt("Items.bank_card.custom_model_data", 0);
if (cmd > 0) {
meta.setCustomModelData(cmd);
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
pdc.set(keys.cardOwner, PersistentDataType.STRING, owner.toString());
pdc.set(keys.cardSerial, PersistentDataType.LONG, serial);
item.setItemMeta(meta);
return item;
}
public boolean isCard(ItemStack item) {
if (item == null) {
return false;
}
ItemMeta meta = item.getItemMeta();
return meta != null && meta.getPersistentDataContainer().has(keys.cardOwner, PersistentDataType.STRING);
}
public UUID getOwner(ItemStack item) {
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return null;
}
String raw = meta.getPersistentDataContainer().get(keys.cardOwner, PersistentDataType.STRING);
if (raw == null) {
return null;
}
try {
return UUID.fromString(raw);
} catch (IllegalArgumentException ex) {
return null;
}
}
public long getSerial(ItemStack item) {
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return 0;
}
Long serial = meta.getPersistentDataContainer().get(keys.cardSerial, PersistentDataType.LONG);
return serial == null ? 0 : serial;
}
/** 银行卡是否仍然有效 (持卡人匹配且序列号未被挂失作废)。 */
public boolean isValid(ItemStack item, PlayerAccount account) {
if (account == null || !isCard(item)) {
return false;
}
UUID owner = getOwner(item);
return account.getUuid().equals(owner)
&& account.hasValidCard()
&& account.getCardSerial() == getSerial(item);
}
private Material parseMaterial(String name, Material def) {
if (name == null) {
return def;
}
Material m = Material.matchMaterial(name.toUpperCase());
return m == null ? def : m;
}
}