初始化项目

This commit is contained in:
yhy
2026-07-11 22:13:14 +08:00
commit 67461082a5
53 changed files with 5568 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
target/
out/
lib/
libs/
.vscode/
.codex/
.idea/
*.iml
*.class
*.log
logs/
*.db
*.sqlite
*.sqlite3
.DS_Store
Thumbs.db

43
pom.xml Normal file
View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AuMyItems</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>public-survive</id>
<url>https://repo.aurora-pixels.com/repository/public-survive/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.momirealms.craftengine.bukkit.plugin</groupId>
<artifactId>CraftEngine</artifactId>
<version>26.5.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,176 @@
package com.io.yaohun.myitems;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.EntityAttributeAPI;
import com.io.yaohun.myitems.builder.ItemTemplateBuilder;
import com.io.yaohun.myitems.commands.AttCommand;
import com.io.yaohun.myitems.commands.MyItemCommand;
import com.io.yaohun.myitems.commands.RepairCommand;
import com.io.yaohun.myitems.config.Config;
import com.io.yaohun.myitems.engine.DamageEngine;
import com.io.yaohun.myitems.gui.PanelGui;
import com.io.yaohun.myitems.listener.*;
import com.io.yaohun.myitems.manage.AttributeManager;
import com.io.yaohun.myitems.manage.CacheManager;
import com.io.yaohun.myitems.manage.EntityAttributeStorage;
import com.io.yaohun.myitems.manage.ItemManager;
import com.io.yaohun.myitems.manage.SuitManager;
import com.io.yaohun.myitems.util.*;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.IronGolem;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList;
import java.util.List;
public class MyItems extends JavaPlugin {
private static MyItems instance;
private CacheManager cacheManager;
private DamageEngine damageEngine;
private AttributeManager attributeManager;
private EntityAttributeStorage entityAttributeStorage;
private SuitManager suitManager;
@Override
public void onEnable() {
instance = this;
Config.reloadConfig(this);
MessageUtil.init(this);
this.cacheManager = new CacheManager();
this.entityAttributeStorage = new EntityAttributeStorage();
this.suitManager = new SuitManager();
this.attributeManager = new AttributeManager(cacheManager, suitManager);
this.damageEngine = new DamageEngine(attributeManager);
getServer().getPluginManager().registerEvents(new PlayerJoinListener(attributeManager,cacheManager), this);
getServer().getPluginManager().registerEvents(new PlayerEquipListener(attributeManager), this);
getServer().getPluginManager().registerEvents(new DamageListener(damageEngine), this);
getServer().getPluginManager().registerEvents(new DurabilityListener(attributeManager), this);
getServer().getPluginManager().registerEvents(new ToolDurabilityListener(attributeManager), this);
getServer().getPluginManager().registerEvents(new SuitListener(suitManager, attributeManager), this);
getServer().getPluginManager().registerEvents(new EntityCleanupListener(entityAttributeStorage), this);
getCommand("att").setExecutor(new AttCommand());
getCommand("repair").setExecutor(new RepairCommand());
getCommand("myitems").setExecutor(new MyItemCommand());
Bukkit.getScheduler().runTask(this, new Runnable() {
@Override
public void run() {
ItemManager.reloadItemManager(MyItems.inst());
suitManager.reloadSuitManager(MyItems.inst());
for (Player player : Bukkit.getOnlinePlayers()){
suitManager.refreshPlayerSuitState(player, false);
attributeManager.refresh(player);
}
}
});
}
@Override
public void onDisable() {
if(cacheManager != null){
cacheManager.clear();
}
}
public static MyItems inst() {
return instance;
}
public CacheManager getCacheManager() {
return cacheManager;
}
public AttributeManager getAttributeManager() {
return attributeManager;
}
public DamageEngine getDamageEngine() {
return damageEngine;
}
public EntityAttributeStorage getEntityAttributeStorage() {
return entityAttributeStorage;
}
public SuitManager getSuitManager() {
return suitManager;
}
public ItemStack gethelmet() {
ItemStack helmet = new ItemStack(Material.DIAMOND_HELMET);
ItemMeta meta = helmet.getItemMeta();
meta.setDisplayName("§b战神头盔");
meta.setUnbreakable(true);
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
helmet.setItemMeta(meta);
ItemAttributeEditor.setAttribute(helmet, AttributeType.PHYSICAL_DEFENSE, 30);
ItemAttributeEditor.setAttribute(helmet, AttributeType.MAX_HEALTH_BONUS, 40);
ItemAttributeEditor.setAttribute(helmet, AttributeType.DODGE_CHANCE, 8);
ItemAttributeEditor.setDurability(helmet, 80, 80);
return helmet;
}
public ItemStack getsword() {
ItemStack sword = new ItemStack(Material.DIAMOND_SWORD);
ItemMeta meta = sword.getItemMeta();
meta.setDisplayName("§6测试神剑");
meta.setUnbreakable(true);
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
sword.setItemMeta(meta);
ItemAttributeEditor.setAttribute(sword, AttributeType.PHYSICAL_ATTACK, 100);
ItemAttributeEditor.setAttribute(sword, AttributeType.CRIT_CHANCE, 25);
ItemAttributeEditor.setAttribute(sword, AttributeType.CRIT_DAMAGE, 50);
ItemAttributeEditor.setAttribute(sword, AttributeType.HIT_CHANCE, 10);
ItemAttributeEditor.setDurability(sword, 20, 120);
return sword;
}
public ItemStack getPickAxe() {
ItemStack stack = new ItemStack(Material.IRON_PICKAXE);
int max = 1024;
int current = RandomUtil.getRandomInt(1,20);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName("§7[§e超稀有§7]§r铁镐");
List<String> lore = new ArrayList<>();
String color;
double percent = current * 1.0 / max;
if(current <= 0){
color = "§c";
}else if(percent > 0.5){
color = "§a";
}else if(percent > 0.2){
color = "§e";
}else {
color = "§c";
}
AttributeType type = AttributeType.EQUIPMENT_DURABILITY;
String prefix = MessageUtil.getMessage(type.name());
String newLore = prefix+" "+color+ AttributeFormatUtil.format(type, current)+"§7/§a"+ max;
lore.add(newLore);
lore.add("§a✔§f从稀有工具箱I获得");
meta.setLore(lore);
meta.setUnbreakable(true);
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
stack.setItemMeta(meta);
ItemAttributeEditor.setDurability(stack, current, max);
return stack;
}
}

View File

@@ -0,0 +1,46 @@
package com.io.yaohun.myitems.api;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
public class AttributeData {
private final EnumMap<AttributeType, Double> values = new EnumMap<>(AttributeType.class);
public void add(AttributeType type, double value) {
values.put(type, get(type) + value);
}
public void set(AttributeType type, double value) {
values.put(type, value);
}
public double get(AttributeType type) {
return values.getOrDefault(type, type.getDefaultValue());
}
public boolean has(AttributeType type) {
return values.containsKey(type);
}
public void merge(AttributeData other) {
if (other == null) return;
for (Map.Entry<AttributeType, Double> entry : other.getAll().entrySet()) {
add(entry.getKey(), entry.getValue());
}
}
public void clear() {
values.clear();
}
public Map<AttributeType, Double> getAll() {
return Collections.unmodifiableMap(values);
}
@Override
public String toString() {
return "AttributeData{" + values + '}';
}
}

View File

@@ -0,0 +1,53 @@
package com.io.yaohun.myitems.api;
public enum AttributeType {
PHYSICAL_ATTACK("物理攻击", 0), // 攻击所有实体时造成的伤害
PHYSICAL_DEFENSE("物理防御", 0), // 物理防御 与 物理攻击 相互抵消
PVE_ATTACK("PVE攻击", 0), // 在攻击非Player实体时额外造成的伤害
PVE_ATTACK_ADDITION("PVE攻击百分比", 0), // 增加PVE攻击百分比
PVE_DEFENSE("PVE防御", 0), // PVE防御 与 PVE攻击 相互抵消
MAX_HEALTH_BONUS("生命增加", 0), // 提升玩家最大生命值
CRIT_CHANCE("暴击概率", 0), // 触发暴击伤害的概率
CRIT_DAMAGE("暴击伤害", 0), // 暴击伤害默认翻倍~ 而这个数值是提高倍率的
HIT_CHANCE("命中概率", 0), // 与闪避几率形成 1:1 相互抵消
DODGE_CHANCE("闪避概率", 0), // 触发闪避后免疫本次伤害
MOVE_SPEED("移动加成", 0), // 增加移动速度
EQUIPMENT_DURABILITY("耐久", 0), // 耐久没有后属性全部失效
/**
* 套装效果属性
* */
SUIT_SKILL_DIAOLING("凋零锐锋",0),
SUIT_SKILL_SHOUHU("守护壁垒",0),
SUIT_SKILL_CANGMU("仓木生息",0),
SUIT_SKILL_BINGSHUANG("冰霜韧战",0),
/**
* 2026年7月新增属性
* */
BLOCK_CHANCE("格挡概率",0), // 触发格挡后减少50%的受到伤害并返还10%的伤害给对方
VAMPIRE_CHANCE("吸血概率",0), // 触发吸血后本次造成伤害的3%转换为生命值
PENETRATION("防御穿透", 0); // 触发护甲穿透后无视防御数值
private final String displayName;
private final double defaultValue;
AttributeType(String displayName, double defaultValue) {
this.displayName = displayName;
this.defaultValue = defaultValue;
}
public String getDisplayName() {
return displayName;
}
public double getDefaultValue() {
return defaultValue;
}
}

View File

@@ -0,0 +1,67 @@
package com.io.yaohun.myitems.api;
public class DamageResult {
private final boolean miss;
private final boolean crit;
private final boolean penetration;
private final boolean block;
private final boolean vampire;
private final double damage;
private final double reflectDamage;
private final double healAmount;
public DamageResult(boolean miss, boolean crit, double damage) {
this(miss, crit, false, false, false, damage, 0.0D, 0.0D);
}
public DamageResult(boolean miss,
boolean crit,
boolean penetration,
boolean block,
boolean vampire,
double damage,
double reflectDamage,
double healAmount) {
this.miss = miss;
this.crit = crit;
this.penetration = penetration;
this.block = block;
this.vampire = vampire;
this.damage = damage;
this.reflectDamage = reflectDamage;
this.healAmount = healAmount;
}
public boolean isMiss() {
return miss;
}
public boolean isCrit() {
return crit;
}
public boolean isPenetration() {
return penetration;
}
public boolean isBlock() {
return block;
}
public boolean isVampire() {
return vampire;
}
public double getDamage() {
return damage;
}
public double getReflectDamage() {
return reflectDamage;
}
public double getHealAmount() {
return healAmount;
}
}

View File

@@ -0,0 +1,116 @@
package com.io.yaohun.myitems.api;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.manage.EntityAttributeStorage;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import java.util.Map;
public final class EntityAttributeAPI {
public static void addEntityAttribute(Entity entity, String source, Map<String, Double> attributeMap) {
if (!(entity instanceof LivingEntity livingEntity)) {
return;
}
if (source == null || source.isEmpty() || attributeMap == null || attributeMap.isEmpty()) {
return;
}
AttributeData data = new AttributeData();
for (Map.Entry<String, Double> entry : attributeMap.entrySet()) {
AttributeType type = matchType(entry.getKey());
if (type == null) {
continue;
}
data.add(type, entry.getValue());
}
EntityAttributeStorage storage = MyItems.inst().getEntityAttributeStorage();
storage.addAttributes(livingEntity.getUniqueId(), source, data);
}
public static void addEntityAttribute(Entity entity, String source, AttributeType type, double value) {
if (!(entity instanceof LivingEntity livingEntity) || type == null) {
return;
}
AttributeData data = new AttributeData();
data.add(type, value);
MyItems.inst()
.getEntityAttributeStorage()
.addAttributes(livingEntity.getUniqueId(), source, data);
}
public static void removeEntityAttribute(Entity entity, String source) {
if (!(entity instanceof LivingEntity livingEntity)) {
return;
}
MyItems.inst()
.getEntityAttributeStorage()
.removeAttributes(livingEntity.getUniqueId(), source);
}
public static AttributeData getFinalEntityAttribute(Entity entity) {
if (!(entity instanceof LivingEntity livingEntity)) {
return new AttributeData();
}
return MyItems.inst()
.getEntityAttributeStorage()
.getFinalAttributes(livingEntity.getUniqueId());
}
public static double getEntityAttribute(Entity entity, AttributeType type) {
if (!(entity instanceof LivingEntity livingEntity)) {
return 0.0D;
}
return MyItems.inst()
.getEntityAttributeStorage()
.getAttribute(livingEntity.getUniqueId(), type);
}
private static AttributeType matchType(String key) {
if (key == null) {
return null;
}
switch (key.toLowerCase()) {
case "physical_attack":
return AttributeType.PHYSICAL_ATTACK;
case "physical_defense":
return AttributeType.PHYSICAL_DEFENSE;
case "pve_attack":
return AttributeType.PVE_ATTACK;
case "pve_defense":
return AttributeType.PVE_DEFENSE;
case "max_health_bonus":
case "health":
case "health_bonus":
return AttributeType.MAX_HEALTH_BONUS;
case "crit_chance":
return AttributeType.CRIT_CHANCE;
case "crit_damage":
return AttributeType.CRIT_DAMAGE;
case "hit_chance":
return AttributeType.HIT_CHANCE;
case "dodge_chance":
return AttributeType.DODGE_CHANCE;
case "block_chance":
case "block":
return AttributeType.BLOCK_CHANCE;
case "vampire_chance":
case "vampire":
return AttributeType.VAMPIRE_CHANCE;
case "penetration":
return AttributeType.PENETRATION;
case "move_speed":
return AttributeType.MOVE_SPEED;
default:
return null;
}
}
}

View File

@@ -0,0 +1,11 @@
package com.io.yaohun.myitems.api;
public enum EquipmentSlotType {
MAIN_HAND,
OFF_HAND,
HELMET,
CHESTPLATE,
LEGGINGS,
BOOTS,
UNIVERSAL
}

View File

@@ -0,0 +1,33 @@
package com.io.yaohun.myitems.api;
import com.io.yaohun.myitems.config.Config;
import com.io.yaohun.myitems.util.MessageUtil;
public enum TierType {
COMMON("普通","§f", MessageUtil.getMessage("COMMON")),
UNCOMMON("稀有","§b",MessageUtil.getMessage("UNCOMMON")),
SUPERUNCOMMON("超稀有","§e",MessageUtil.getMessage("SUPERUNCOMMON")),
EPIC("史诗","§5",MessageUtil.getMessage("EPIC")),
LEGENDARY("传说","§6",MessageUtil.getMessage("LEGENDARY")),
EMINENT("卓越","§d",MessageUtil.getMessage("EMINENT")),
SANCTITY("神圣","§c",MessageUtil.getMessage("SANCTITY"));
private final String displayName;
private final String color;
private final String character;
TierType(String displayName, String color, String character) {
this.displayName = displayName;
this.color = color;
this.character = character;
}
public String getDisplayName() {
return color+displayName;
}
public String getCharacter() {
return character;
}
}

View File

@@ -0,0 +1,84 @@
package com.io.yaohun.myitems.builder;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.TierType;
import com.io.yaohun.myitems.data.ItemTemplate;
import com.io.yaohun.myitems.manage.ItemManager;
import com.io.yaohun.myitems.util.DemonAPI;
import com.io.yaohun.myitems.util.ItemAttributeEditor;
import com.io.yaohun.myitems.util.RandomUtil;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ItemTemplateBuilder {
private static final String EMPTY_LINE_LORE = " ";
public static ItemStack build(String itemId) {
if(itemId == null){
return null;
}
ItemTemplate template = ItemManager.getItemTemplate(itemId);
if(template == null){
return null;
}
ItemStack itemStack = new ItemStack(template.getMaterial());
if(!DemonAPI.itemIsNull(template.getItemStack())){
itemStack = template.getItemStack();
}
TierType tierType = template.getRandomTierType();
ItemMeta meta = itemStack.getItemMeta();
String itemName = template.getDisplayName().replace("{tier_name}", tierType.getDisplayName());
meta.setDisplayName(DemonAPI.convertHexColor(itemName));
int gemLine = 0;
List<String> lore = new ArrayList<>();
if(!template.getLore().isEmpty()){
List<String> lores = new ArrayList<>(template.getLore());
for (int i = 0; i < lores.size(); i++){
String line = DemonAPI.convertHexColor(lores.get(i));
line = line.replace("{tier_name}", tierType.getDisplayName());
line = line.replace("{tier_character}", tierType.getCharacter());
line = line.replace("{item_icon}", template.getItemIcon());
if(line.contains("{GemSlot}")) {
line = line.replace("{GemSlot}", EMPTY_LINE_LORE);
gemLine = i;
}
lores.set(i, line);
}
lore.addAll(lores);
}
meta.setLore(lore);
meta.setUnbreakable(true);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
itemStack.setItemMeta(meta);
if (template.getEquipSlotType() != null) {
ItemAttributeEditor.setEquipSlot(itemStack, template.getEquipSlotType().name());
}
ItemAttributeEditor.setTemplateId(itemStack, template.getItemId());
ItemAttributeEditor.setSuitId(itemStack, template.getSuitId());
Map<AttributeType, ItemTemplate.AttributeRandomValue> attributes = template.getAttributes(tierType);
if(!attributes.isEmpty()) {
for (AttributeType type : attributes.keySet()) {
ItemAttributeEditor.setAttribute(itemStack, type, attributes.get(type).getRandomValue());
}
}
int max = template.getMaxDurability(tierType);
if(max > 0) {
int current = template.getCurrentDurability(tierType);
ItemAttributeEditor.setDurability(itemStack, RandomUtil.getRandomInt(current, max), max);
LoreBuilder.updateDurabilityAttributeLine(itemStack);
}
return itemStack;
}
}

View File

@@ -0,0 +1,172 @@
package com.io.yaohun.myitems.builder;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.util.*;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public final class LoreBuilder {
public static void updateDurabilityAttributeLine(ItemStack item){
if(DemonAPI.itemIsNull(item) || DemonAPI.itemIsLore(item)){
return;
}
ItemMeta meta = item.getItemMeta();
List<String> lore = meta.getLore();
int max = ItemAttributeUtil.getMaxDurability(item);
if(max <= 0){
return;
}
String color;
int current = ItemAttributeUtil.getCurrentDurability(item);
double percent = current * 1.0 / max;
if(current <= 0){
color = "§c";
}else if(percent > 0.5){
color = "§a";
}else if(percent > 0.2){
color = "§e";
}else {
color = "§c";
}
AttributeType type = AttributeType.EQUIPMENT_DURABILITY;
if (Math.abs(current) >= 0) {
boolean updated = false;
String prefix = MessageUtil.getMessage(type.name());
String newLore = DemonAPI.convertHexColor(prefix+" "+color+ AttributeFormatUtil.format(type, current)+"§7/§a"+ max);
for (int i = 0; i < lore.size(); i++) {
String line = lore.get(i);
if (line.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) {
lore.set(i, newLore);
updated = true;
break;
}
}
if(!updated){
lore.add(newLore);
}
meta.setLore(lore);
item.setItemMeta(meta);
}
}
public static void updateLore(ItemStack item) {
if(DemonAPI.itemIsNull(item)){
return;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return;
}
List<String> lore = meta.hasLore() ? meta.getLore() : new ArrayList<>();
addAttributeLine(item, lore, AttributeType.PHYSICAL_ATTACK);
addAttributeLine(item, lore, AttributeType.PHYSICAL_DEFENSE);
addAttributeLine(item, lore, AttributeType.PVE_ATTACK);
addAttributeLine(item, lore, AttributeType.PVE_DEFENSE);
addAttributeLine(item, lore, AttributeType.MAX_HEALTH_BONUS);
addAttributeLine(item, lore, AttributeType.CRIT_CHANCE);
addAttributeLine(item, lore, AttributeType.CRIT_DAMAGE);
addAttributeLine(item, lore, AttributeType.HIT_CHANCE);
addAttributeLine(item, lore, AttributeType.DODGE_CHANCE);
addAttributeLine(item, lore, AttributeType.BLOCK_CHANCE);
addAttributeLine(item, lore, AttributeType.VAMPIRE_CHANCE);
addAttributeLine(item, lore, AttributeType.PENETRATION);
addAttributeLine(item, lore, AttributeType.MOVE_SPEED);
meta.setLore(lore);
item.setItemMeta(meta);
}
private static void addAttributeLine(ItemStack item, List<String> lore, AttributeType type) {
double rawValue = readRawAttribute(item, type);
if (Math.abs(rawValue) <= 0.0001D) {
return;
}
String valueText;
if (type == AttributeType.CRIT_DAMAGE) {
valueText = formatCritDamage(rawValue);
} else {
valueText = AttributeFormatUtil.format(type, rawValue);
}
boolean updated = false;
String prefix = MessageUtil.getMessage(type.name());
String newLore = DemonAPI.convertHexColor(prefix+" "+valueText);
for (int i = 0; i < lore.size(); i++){
String line = lore.get(i);
if (line.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) {
lore.set(i, newLore);
updated = true;
break;
}
}
if(!updated){
lore.add(newLore);
}
}
/**
* 注意Lore展示原始属性不受破损屏蔽
* 否则破损后 lore 上所有属性会消失,看起来像装备没词条
*/
private static double readRawAttribute(ItemStack item, AttributeType type) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return 0.0D;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return 0.0D;
}
switch (type) {
case PHYSICAL_ATTACK:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.PHYSICAL_ATTACK, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case PHYSICAL_DEFENSE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.PHYSICAL_DEFENSE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case PVE_ATTACK:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.PVE_ATTACK, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case PVE_DEFENSE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.PVE_DEFENSE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case MAX_HEALTH_BONUS:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.MAX_HEALTH_BONUS, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case CRIT_CHANCE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.CRIT_CHANCE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case CRIT_DAMAGE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.CRIT_DAMAGE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case HIT_CHANCE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.HIT_CHANCE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case DODGE_CHANCE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.DODGE_CHANCE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case BLOCK_CHANCE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.BLOCK_CHANCE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case VAMPIRE_CHANCE:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.VAMPIRE_CHANCE, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case PENETRATION:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.PENETRATION, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
case MOVE_SPEED:
return meta.getPersistentDataContainer().getOrDefault(AttributeKeys.MOVE_SPEED, org.bukkit.persistence.PersistentDataType.DOUBLE, 0.0D);
default:
return 0.0D;
}
}
private static String formatCritDamage(double value) {
double multiplier = value / 100.0;
if (Math.abs(multiplier - Math.round(multiplier)) < 0.001D) {
return (int) multiplier + "x";
}
return String.format("%.2fx", multiplier)
.replace(".00x", "x")
.replace("0x", "x");
}
}

View File

@@ -0,0 +1,189 @@
package com.io.yaohun.myitems.commands;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.EquipmentSlotType;
import com.io.yaohun.myitems.util.ItemAttributeEditor;
import com.io.yaohun.myitems.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class AttCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("§c此命令仅限玩家身份执行");
return true;
}
if(!player.hasPermission("admin.use")){
return true;
}
if (args.length < 1) {
sendHelp(player, label);
return true;
}
ItemStack item = player.getInventory().getItemInMainHand();
if (item == null || item.getType() == Material.AIR) {
MessageUtil.sendMessageKey(player, "MainHand_Empty", Sound.ENTITY_BLAZE_DEATH);
return true;
}
if ("stats".equalsIgnoreCase(args[0])) {
if (args.length != 3) {
MessageUtil.sendDescMessage(player, "/att stats <参数> <value>", Sound.ENTITY_BLAZE_DEATH);
return true;
}
String attTypeName = args[1];
String valueText = args[2];
AttributeType attributeType;
try {
attributeType = AttributeType.valueOf(attTypeName.toUpperCase());
} catch (IllegalArgumentException e) {
MessageUtil.sendDescMessage(player, "/att stats <参数> <value>", Sound.ENTITY_BLAZE_DEATH);
return true;
}
double value;
try {
value = Double.parseDouble(valueText);
} catch (NumberFormatException e) {
MessageUtil.sendDescMessage(player, "/att stats <参数> <value>", Sound.ENTITY_BLAZE_DEATH);
return true;
}
ItemAttributeEditor.setAttribute(item, attributeType, value);
String message = MessageUtil.getMessage("Stats_Success").replace("{stats}",attributeType.name());
message = message.replace("{value}",String.valueOf( value));
MessageUtil.sendMessage(player, message, Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
return true;
}
if ("durability".equalsIgnoreCase(args[0])) {
if(args.length != 3){
MessageUtil.sendDescMessage(player, "/att durability <当前耐久> <最大耐久>", Sound.ENTITY_BLAZE_DEATH);
return true;
}
int current;
int max;
try {
current = Integer.parseInt(args[1]);
max = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
MessageUtil.sendMessageKey(player, "Invalid_Value", Sound.ENTITY_BLAZE_DEATH);
return true;
}
if (current < 0 || max <= 0) {
MessageUtil.sendDescMessage(player, "/att durability 128 512", Sound.ENTITY_BLAZE_DEATH);
return true;
}
if (current > max) {
current = max;
}
ItemAttributeEditor.setDurability(item, current, max);
String message = MessageUtil.getMessage("Durability_Success").replace("{now}",String.valueOf(current));
message = message.replace("{max}",String.valueOf(max));
MessageUtil.sendMessage(player, message, Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
return true;
}
if ("slot".equalsIgnoreCase(args[0])) {
if (args.length != 2) {
MessageUtil.sendDescMessage(player, "/att slot <value>", Sound.ENTITY_BLAZE_DEATH);
return true;
}
EquipmentSlotType slotType;
try {
slotType = EquipmentSlotType.valueOf(args[1].toUpperCase());
} catch (IllegalArgumentException e) {
MessageUtil.sendMessageKey(player, "Invalid_SlotType", Sound.ENTITY_BLAZE_DEATH);
return true;
}
ItemAttributeEditor.setEquipSlot(item, args[1].toUpperCase());
String message = MessageUtil.getMessage("Slot_Success").replace("{value}",slotType.name());
MessageUtil.sendMessage(player, message, Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
return true;
}
if ("clear".equalsIgnoreCase(args[0])) {
if (args.length != 1) {
MessageUtil.sendDescMessage(player, "/att clear", Sound.ENTITY_BLAZE_DEATH);
return true;
}
ItemAttributeEditor.clearAllAttributes(item);
MessageUtil.sendMessageKey(player, "Clear_Success", Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
return true;
}
sendHelp(player, label);
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("admin.use")) {
return Collections.emptyList();
}
// /att <这里>
if (args.length == 1) {
List<String> subCommands = Arrays.asList(
"stats",
"clear",
"slot",
"durability"
);
return subCommands.stream()
.filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
}
// /att stats <这里>
if (args.length == 2 && "stats".equalsIgnoreCase(args[0])) {
return Arrays.stream(AttributeType.values())
.map(Enum::name)
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
.collect(Collectors.toList());
}
// /att stats PHYSICAL_ATTACK <这里>
if (args.length == 3 && "stats".equalsIgnoreCase(args[0])) {
return Arrays.asList(
"10",
"50",
"100",
"500",
"1000"
).stream()
.filter(s -> s.startsWith(args[2]))
.collect(Collectors.toList());
}
// /att slot <这里>
if (args.length == 2 && "slot".equalsIgnoreCase(args[0])) {
return Arrays.stream(EquipmentSlotType.values())
.map(Enum::name)
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
private void sendHelp(Player player, String label) {
player.sendMessage("§r");
player.sendMessage("§e==----- ======= §2属性编辑 §e======= -----==");
player.sendMessage("§2/" + label + " stats §e[stats] §b<value> §f- §2设置属性");
player.sendMessage("§2/" + label + " durability §e[now] §b<max> §f- §2设置耐久");
player.sendMessage("§2/" + label + " slot §e[value] §f- §2设置槽位类型");
player.sendMessage("§2/" + label + " clear §f- §2清理物品属性");
player.sendMessage("§e==----- ======= §2属性编辑 §e======= -----==");
player.sendMessage("§r");
}
}

View File

@@ -0,0 +1,248 @@
package com.io.yaohun.myitems.commands;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.EntityAttributeAPI;
import com.io.yaohun.myitems.api.EquipmentSlotType;
import com.io.yaohun.myitems.builder.ItemTemplateBuilder;
import com.io.yaohun.myitems.config.Config;
import com.io.yaohun.myitems.gui.PanelGui;
import com.io.yaohun.myitems.manage.ItemManager;
import com.io.yaohun.myitems.util.ItemAttributeEditor;
import com.io.yaohun.myitems.util.ItemAttributeUtil;
import com.io.yaohun.myitems.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
import org.bukkit.attribute.Attribute;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.IronGolem;
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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class MyItemCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(args.length == 1){
if("gui".equalsIgnoreCase(args[0]) || "stats".equalsIgnoreCase(args[0])) {
if (!(sender instanceof Player player)) {
sender.sendMessage("§c此命令仅限玩家身份使用");
return true;
}
PanelGui.OpenGui(player);
return true;
}
}
if(!sender.hasPermission("admin.use")){
return true;
}
if(args.length == 1 && "reload".equalsIgnoreCase(args[0])){
Config.reloadConfig(MyItems.inst());
MessageUtil.init(MyItems.inst());
ItemManager.reloadItemManager(MyItems.inst());
MyItems.inst().getSuitManager().reloadSuitManager(MyItems.inst());
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
MyItems.inst().getSuitManager().refreshPlayerSuitState(onlinePlayer, false);
MyItems.inst().getAttributeManager().markDirty(onlinePlayer);
MyItems.inst().getAttributeManager().refresh(onlinePlayer);
}
sender.sendMessage("§f[§6属性§f] §a配置文件已重载.");
if(sender instanceof Player player) {
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 1);
}
return true;
}
if(args.length >= 1 && "give".equalsIgnoreCase(args[0])){
if(args.length == 1){
sender.sendMessage("§f[§6属性§f] §c缺少物品Id参数.");
return true;
}
String playerName = sender.getName();
if(args.length >= 3){
playerName = args[2];
}
Player player = Bukkit.getPlayer(playerName);
if(player == null){
sender.sendMessage("§f[§6属性§f] §c目标玩家不存在.");
return true;
}
String itemId = args[1];
ItemStack itemStack = ItemTemplateBuilder.build(itemId);
if (itemStack == null) {
sender.sendMessage("§f[§6属性§f] §c物品不存在.");
return true;
}
player.getInventory().addItem(itemStack);
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP,1,1);
return true;
}
if (!(sender instanceof Player player)) {
sender.sendMessage("§c此命令仅限玩家身份使用");
return true;
}
if(args.length == 1 && ("nbt".equalsIgnoreCase(args[0])
|| "pdc".equalsIgnoreCase(args[0])
|| "debugnbt".equalsIgnoreCase(args[0]))){
sendItemNbtDebug(player);
return true;
}
if(args.length == 1 && "test2".equalsIgnoreCase(args[0])){
IronGolem ironGolem = player.getWorld().spawn(player.getLocation(), IronGolem.class);
ironGolem.setTarget(player);
ironGolem.getAttribute(Attribute.MAX_HEALTH).setBaseValue(1000.0D);
ironGolem.setHealth(ironGolem.getMaxHealth());
EntityAttributeAPI.addEntityAttribute(ironGolem, "test", AttributeType.PHYSICAL_DEFENSE,500);
return true;
}
MyItems.inst().getAttributeManager().refresh(player);
AttributeData data = MyItems.inst().getAttributeManager().getFinalAttributes(player);
player.sendMessage(ChatColor.GOLD + "====== 当前属性 ======");
for (AttributeType type : AttributeType.values()) {
player.sendMessage(ChatColor.YELLOW + type.getDisplayName()
+ ChatColor.WHITE + ": "
+ ChatColor.GREEN + data.get(type));
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("admin.use")) {
return Collections.emptyList();
}
// /att stats <这里>
if (args.length == 1) {
return Arrays.asList("gui", "stats", "reload", "give", "nbt", "pdc", "debugnbt").stream()
.filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
}
if (args.length == 2 && "give".equalsIgnoreCase(args[0])) {
return ItemManager.getTemplateList().stream()
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
.collect(Collectors.toList());
}
// /att stats PHYSICAL_ATTACK <这里>
if (args.length == 3 && "stats".equalsIgnoreCase(args[0])) {
return Arrays.asList(
"10",
"50",
"100",
"500",
"1000"
).stream()
.filter(s -> s.startsWith(args[2]))
.collect(Collectors.toList());
}
// /att slot <这里>
if (args.length == 2 && "slot".equalsIgnoreCase(args[0])) {
return Arrays.stream(EquipmentSlotType.values())
.map(Enum::name)
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
private void sendHelp(Player player, String label) {
player.sendMessage("§r");
player.sendMessage("§e==----- ======= §2属性编辑 §e======= -----==");
player.sendMessage("§2/" + label + " stats §e[stats] §b<value> §f- §2设置属性");
player.sendMessage("§2/" + label + " durability §e[now] §b<max> §f- §2设置耐久");
player.sendMessage("§2/" + label + " slot §e[value] §f- §2设置槽位类型");
player.sendMessage("§2/" + label + " clear §f- §2清理物品属性");
player.sendMessage("§e==----- ======= §2属性编辑 §e======= -----==");
player.sendMessage("§r");
}
private void sendItemNbtDebug(Player player) {
ItemStack item = player.getInventory().getItemInMainHand();
if (item == null || item.getType() == Material.AIR) {
player.sendMessage(ChatColor.RED + "[MyItems] 主手没有物品。");
return;
}
player.sendMessage(ChatColor.GOLD + "====== MyItems NBT/PDC 调试 ======");
player.sendMessage(ChatColor.YELLOW + "Material: " + ChatColor.WHITE + item.getType().name());
player.sendMessage(ChatColor.YELLOW + "Amount: " + ChatColor.WHITE + item.getAmount());
ItemMeta meta = item.getItemMeta();
if (meta == null) {
player.sendMessage(ChatColor.RED + "ItemMeta: null");
return;
}
player.sendMessage(ChatColor.YELLOW + "DisplayName: " + ChatColor.WHITE + meta.getDisplayName());
player.sendMessage(ChatColor.YELLOW + "LoreLines: " + ChatColor.WHITE + (meta.hasLore() ? meta.getLore().size() : 0));
player.sendMessage(ChatColor.YELLOW + "TemplateId: " + ChatColor.WHITE + ItemAttributeUtil.readTemplateId(item));
player.sendMessage(ChatColor.YELLOW + "SuitId: " + ChatColor.WHITE + ItemAttributeUtil.readSuitId(item));
player.sendMessage(ChatColor.YELLOW + "Durability: " + ChatColor.WHITE
+ ItemAttributeUtil.getCurrentDurability(item) + "/" + ItemAttributeUtil.getMaxDurability(item));
player.sendMessage(ChatColor.YELLOW + "Broken: " + ChatColor.WHITE + ItemAttributeUtil.isBroken(item));
PersistentDataContainer pdc = meta.getPersistentDataContainer();
if (pdc.getKeys().isEmpty()) {
player.sendMessage(ChatColor.GRAY + "PDC: 空");
return;
}
player.sendMessage(ChatColor.AQUA + "--- PDC Keys ---");
for (NamespacedKey key : pdc.getKeys()) {
player.sendMessage(ChatColor.GRAY + key.toString()
+ ChatColor.WHITE + " = "
+ ChatColor.GREEN + readPdcValue(pdc, key));
}
}
private String readPdcValue(PersistentDataContainer pdc, NamespacedKey key) {
if (pdc.has(key, PersistentDataType.STRING)) {
return "STRING:" + pdc.getOrDefault(key, PersistentDataType.STRING, "");
}
if (pdc.has(key, PersistentDataType.DOUBLE)) {
return "DOUBLE:" + pdc.getOrDefault(key, PersistentDataType.DOUBLE, 0.0D);
}
if (pdc.has(key, PersistentDataType.INTEGER)) {
return "INTEGER:" + pdc.getOrDefault(key, PersistentDataType.INTEGER, 0);
}
if (pdc.has(key, PersistentDataType.LONG)) {
return "LONG:" + pdc.getOrDefault(key, PersistentDataType.LONG, 0L);
}
if (pdc.has(key, PersistentDataType.FLOAT)) {
return "FLOAT:" + pdc.getOrDefault(key, PersistentDataType.FLOAT, 0.0F);
}
if (pdc.has(key, PersistentDataType.SHORT)) {
return "SHORT:" + pdc.getOrDefault(key, PersistentDataType.SHORT, (short) 0);
}
if (pdc.has(key, PersistentDataType.BYTE)) {
return "BYTE:" + pdc.getOrDefault(key, PersistentDataType.BYTE, (byte) 0);
}
if (pdc.has(key, PersistentDataType.INTEGER_ARRAY)) {
return "INTEGER_ARRAY:" + Arrays.toString(pdc.getOrDefault(key, PersistentDataType.INTEGER_ARRAY, new int[0]));
}
if (pdc.has(key, PersistentDataType.LONG_ARRAY)) {
return "LONG_ARRAY:" + Arrays.toString(pdc.getOrDefault(key, PersistentDataType.LONG_ARRAY, new long[0]));
}
if (pdc.has(key, PersistentDataType.BYTE_ARRAY)) {
return "BYTE_ARRAY:" + Arrays.toString(pdc.getOrDefault(key, PersistentDataType.BYTE_ARRAY, new byte[0]));
}
return "未知类型";
}
}

View File

@@ -0,0 +1,123 @@
package com.io.yaohun.myitems.commands;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.util.MessageUtil;
import com.io.yaohun.myitems.util.RepairUtil;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class RepairCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("§c此命令仅限玩家身份执行");
return true;
}
if(!player.hasPermission("admin.use")){
return true;
}
if (args.length < 1) {
sendHelp(player, label);
return true;
}
if ("repair".equalsIgnoreCase(args[0])) {
Player target = getTargetPlayer(player, args);
if (target == null) {
MessageUtil.sendMessage(player, "§c目标玩家不在线", Sound.ENTITY_BLAZE_DEATH);
return true;
}
ItemStack item = target.getInventory().getItemInMainHand();
if (item == null || item.getType() == Material.AIR) {
MessageUtil.sendMessageKey(player, "MainHand_Empty", Sound.ENTITY_BLAZE_DEATH);
return true;
}
boolean success = RepairUtil.repairToFull(item);
if (!success) {
MessageUtil.sendMessageKey(player, "Durability_Not_Exist", Sound.ENTITY_BLAZE_DEATH);
return true;
}
refreshTarget(target);
MessageUtil.sendMessageKey(target, "Repair_Success", Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
return true;
}
if ("repairall".equalsIgnoreCase(args[0])) {
Player target = getTargetPlayer(player, args);
if (target == null) {
MessageUtil.sendMessage(player, "§c目标玩家不在线", Sound.ENTITY_BLAZE_DEATH);
return true;
}
int count = RepairUtil.repairAll(target);
if (count <= 0) {
MessageUtil.sendMessageKey(player, "Durability_Not_Amount", Sound.ENTITY_BLAZE_DEATH);
return true;
}
refreshTarget(target);
MessageUtil.sendMessageKey(target, "Repair_All_Success", Sound.ENTITY_EXPERIENCE_ORB_PICKUP);
return true;
}
sendHelp(player, label);
return true;
}
private Player getTargetPlayer(Player player, String[] args) {
String playerName = player.getName();
if(args.length == 2){
playerName = args[1];
}
return Bukkit.getPlayer(playerName);
}
private void refreshTarget(Player target) {
MyItems.inst().getSuitManager().refreshPlayerSuitState(target, true);
MyItems.inst().getAttributeManager().markDirty(target);
MyItems.inst().getAttributeManager().refresh(target);
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("admin.use")) {
return Collections.emptyList();
}
if (args.length == 1) {
List<String> subCommands = Arrays.asList(
"repair",
"repairall"
);
return subCommands.stream()
.filter(s -> s.toLowerCase().startsWith(args[0].toLowerCase()))
.collect(Collectors.toList());
}
if (args.length == 2 && ("repair".equalsIgnoreCase(args[0]) || "repairall".equalsIgnoreCase(args[0]))) {
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(s -> s.toLowerCase().startsWith(args[1].toLowerCase()))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
private void sendHelp(Player player, String label) {
player.sendMessage("§r");
player.sendMessage("§e==----- ======= §2耐久修复 §e======= -----==");
player.sendMessage("§2/" + label + " repair §e[player] §f- §2修复手持物品");
player.sendMessage("§2/" + label + " repairall §e[player] §f- §2修复所有物品");
player.sendMessage("§e==----- ======= §2耐久修复 §e======= -----==");
player.sendMessage("§r");
}
}

View File

@@ -0,0 +1,15 @@
package com.io.yaohun.myitems.config;
import com.io.yaohun.myitems.MyItems;
import org.bukkit.configuration.file.FileConfiguration;
public class Config {
public static void reloadConfig(MyItems plugin){
plugin.reloadConfig();
plugin.saveConfig();
FileConfiguration config = plugin.getConfig();
}
}

View File

@@ -0,0 +1,50 @@
package com.io.yaohun.myitems.data;
import com.io.yaohun.myitems.util.DemonAPI;
import net.momirealms.craftengine.bukkit.api.CraftEngineItems;
import net.momirealms.craftengine.bukkit.item.BukkitItemDefinition;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
public class CEngineData {
private String Namespace;
private String Value;
private ItemStack itemStack;
public CEngineData(String Namespace, String Value) {
this.Namespace = Namespace;
this.Value = Value;
}
public boolean isItemStack() {
if (!DemonAPI.itemIsNull(itemStack)) {
return true;
}
Key key = new Key(Namespace, Value);
BukkitItemDefinition definition = CraftEngineItems.byId(key);
// 防止空指针
if (definition == null) {
Bukkit.getLogger().warning("[MyItems] 未找到 CraftEngine 物品: " + Namespace + ":" + Value);
return false;
}
ItemStack items = definition.buildBukkitItem();
if (!DemonAPI.itemIsNull(items)) {
itemStack = items;
return true;
}
return false;
}
public ItemStack getItemStack() {
return itemStack;
}
}

View File

@@ -0,0 +1,146 @@
package com.io.yaohun.myitems.data;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.EquipmentSlotType;
import com.io.yaohun.myitems.api.TierType;
import com.io.yaohun.myitems.util.RandomBuilder;
import com.io.yaohun.myitems.util.RandomUtil;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ItemTemplate {
private String itemId;
private ItemStack itemStack;
private Material material;
private String displayName;
private List<String> lore;
private String itemIcon;
private EquipmentSlotType equipSlotType;
private Map<TierType, Double> tierTypes = new HashMap<>();
private Map<TierType, TierAttribute> tierAttributes = new HashMap<>();
private String suitId;
public ItemTemplate(String itemId, String itemIcon,ItemStack itemStack,
Material material, String displayName,
List<String> lore, EquipmentSlotType equipSlotType,
Map<TierType, Double> tierTypes, Map<TierType, TierAttribute> tierAttributes,
String suitId) {
this.itemId = itemId;
this.itemIcon = itemIcon;
this.itemStack = itemStack;
this.material = material;
this.displayName = displayName;
this.lore = lore;
this.equipSlotType = equipSlotType;
this.tierTypes = tierTypes;
this.tierAttributes = tierAttributes;
this.suitId = suitId;
}
public String getItemId() {
return itemId;
}
public String getItemIcon() {
return itemIcon;
}
public Material getMaterial() {
return material;
}
public String getDisplayName() {
return displayName;
}
public List<String> getLore() {
return lore;
}
public ItemStack getItemStack() {
return itemStack;
}
public EquipmentSlotType getEquipSlotType() {
return equipSlotType;
}
public Map<String,Double> getTierTypes() {
Map<String,Double> chanceMap = new HashMap<>();
for (Map.Entry<TierType, Double> entry : tierTypes.entrySet()){
chanceMap.put(entry.getKey().name(),entry.getValue());
}
return chanceMap;
}
public Map<TierType, TierAttribute> getTierAttributes() {
return tierAttributes;
}
public String getSuitId() {
return suitId;
}
public TierType getRandomTierType() {
Map<String,Double> chanceMap = getTierTypes();
RandomBuilder<String> builder = new RandomBuilder<>(chanceMap);
return TierType.valueOf(builder.random());
}
public TierAttribute getTierAttribute(TierType tierType){
return tierAttributes.get(tierType);
}
public int getCurrentDurability(TierType tierType){
return getTierAttribute(tierType).currentDurability;
}
public int getMaxDurability(TierType tierType){
return getTierAttribute(tierType).maxDurability;
}
public Map<AttributeType, AttributeRandomValue> getAttributes(TierType tierType){
return getTierAttribute(tierType).getAttributes();
}
public static class TierAttribute {
private int currentDurability;
private int maxDurability;
private Map<AttributeType, AttributeRandomValue> attributes = new HashMap<>();
public TierAttribute(int currentDurability, int maxDurability, Map<AttributeType, AttributeRandomValue> attributes) {
this.currentDurability = currentDurability;
this.maxDurability = maxDurability;
this.attributes = attributes;
}
public Map<AttributeType, AttributeRandomValue> getAttributes() {
return attributes;
}
}
public static class AttributeRandomValue {
private double min;
private double max;
public AttributeRandomValue(double min, double max) {
this.min = min;
this.max = max;
}
public double getRandomValue(){
return RandomUtil.getRandomDouble(min, max, 2);
}
}
}

View File

@@ -0,0 +1,50 @@
package com.io.yaohun.myitems.data;
import com.io.yaohun.myitems.api.AttributeType;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class SuitTemplate {
private final String suitId;
private final String displayName;
private final int requireAmount;
private final Set<String> itemIds;
private final Map<AttributeType, Double> attributes;
public SuitTemplate(String suitId, String displayName, int requireAmount,
Set<String> itemIds, Map<AttributeType, Double> attributes) {
this.suitId = suitId;
this.displayName = displayName;
this.requireAmount = requireAmount;
this.itemIds = new HashSet<>(itemIds);
this.attributes = attributes;
}
public String getSuitId() {
return suitId;
}
public String getDisplayName() {
return displayName;
}
public int getRequireAmount() {
return requireAmount;
}
public Set<String> getItemIds() {
return Collections.unmodifiableSet(itemIds);
}
public Map<AttributeType, Double> getAttributes() {
return Collections.unmodifiableMap(attributes);
}
public boolean containsItem(String itemId) {
return itemIds.contains(itemId);
}
}

View File

@@ -0,0 +1,127 @@
package com.io.yaohun.myitems.engine;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.DamageResult;
import com.io.yaohun.myitems.api.EntityAttributeAPI;
import com.io.yaohun.myitems.manage.AttributeManager;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import java.util.concurrent.ThreadLocalRandom;
public class DamageEngine {
private static final double PVE_DEFENSE_CONSTANT = 1000.0;
private static final double PVP_DEFENSE_CONSTANT = 1000.0;
private static final double BLOCK_DAMAGE_RATE = 0.5D;
private static final double BLOCK_REFLECT_RATE = 0.1D;
private static final double VAMPIRE_HEAL_RATE = 0.03D;
private final AttributeManager attributeManager;
public DamageEngine(AttributeManager attributeManager) {
this.attributeManager = attributeManager;
}
public DamageResult calculate(LivingEntity attacker, LivingEntity target) {
AttributeData attackerData = getEntityAttributes(attacker);
AttributeData targetData = getEntityAttributes(target);
boolean miss = isMiss(attackerData, targetData);
if (miss) {
return new DamageResult(true, false, 0);
}
boolean penetration = isTriggered(attackerData.get(AttributeType.PENETRATION));
double baseDamage = calculateBaseDamage(attacker, target, attackerData, targetData, penetration);
boolean crit = isCritical(attackerData);
double finalDamage = baseDamage;
if (crit) {
double critDamage = attackerData.get(AttributeType.CRIT_DAMAGE);
finalDamage = finalDamage * (1.0 + (critDamage / 100.0));
}
boolean block = isTriggered(targetData.get(AttributeType.BLOCK_CHANCE));
double reflectDamage = 0.0D;
if (block) {
finalDamage = finalDamage * BLOCK_DAMAGE_RATE;
reflectDamage = finalDamage * BLOCK_REFLECT_RATE;
}
if (finalDamage < 1.0) {
finalDamage = 1.0;
}
boolean vampire = isTriggered(attackerData.get(AttributeType.VAMPIRE_CHANCE));
double healAmount = vampire ? finalDamage * VAMPIRE_HEAL_RATE : 0.0D;
return new DamageResult(false, crit, penetration, block, vampire, finalDamage, reflectDamage, healAmount);
}
private boolean isMiss(AttributeData attackerData, AttributeData targetData) {
double hitChance = attackerData.get(AttributeType.HIT_CHANCE);
double dodgeChance = targetData.get(AttributeType.DODGE_CHANCE);
double finalHitRate = 90.0 + hitChance - dodgeChance;
finalHitRate = clamp(finalHitRate, 20.0, 100.0);
double roll = ThreadLocalRandom.current().nextDouble(100.0);
return roll >= finalHitRate;
}
private boolean isCritical(AttributeData attackerData) {
return isTriggered(attackerData.get(AttributeType.CRIT_CHANCE));
}
private double calculateBaseDamage(LivingEntity attacker, LivingEntity target,
AttributeData attackerData, AttributeData targetData,
boolean penetration) {
double attack = attackerData.get(AttributeType.PHYSICAL_ATTACK);
double defense = targetData.get(AttributeType.PHYSICAL_DEFENSE);
boolean isPve = isPveBattle(attacker, target);
boolean isPvp = isPvpBattle(attacker, target);
if (isPve) {
attack += attackerData.get(AttributeType.PVE_ATTACK);
defense += targetData.get(AttributeType.PVE_DEFENSE);
}
double constant = isPvp ? PVP_DEFENSE_CONSTANT : PVE_DEFENSE_CONSTANT;
double effectiveDefense = penetration ? 0.0D : Math.max(0.0, defense);
double damage = attack * constant / (constant + effectiveDefense);
return Math.max(1.0, damage);
}
private boolean isPveBattle(LivingEntity attacker, LivingEntity target) {
return (attacker instanceof Player && !(target instanceof Player))
|| (!(attacker instanceof Player) && target instanceof Player);
}
private boolean isPvpBattle(LivingEntity attacker, LivingEntity target) {
return attacker instanceof Player
&& target instanceof Player;
}
private AttributeData getEntityAttributes(LivingEntity entity) {
if (entity instanceof Player player) {
return attributeManager.getFinalAttributes(player);
}
AttributeData data = EntityAttributeAPI.getFinalEntityAttribute(entity);
return data == null ? new AttributeData() : data;
}
private boolean isTriggered(double chance) {
double fixedChance = clamp(chance, 0.0D, 100.0D);
return ThreadLocalRandom.current().nextDouble(100.0D) < fixedChance;
}
private double clamp(double value, double min, double max) {
return Math.max(min, Math.min(max, value));
}
}

View File

@@ -0,0 +1,136 @@
package com.io.yaohun.myitems.gui;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.util.DemonAPI;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
public class PanelGui {
public static String invTitle = "§9§l个人属性菜单";
public static void OpenGui(Player player){
AttributeData data = MyItems.inst().getAttributeManager().getFinalAttributes(player);
Inventory inv = Bukkit.createInventory(null,54,invTitle);
for (int slot = 0; slot < 54; slot++){
inv.setItem(slot, blackGlass());
}
for (int slot : new int[]{0,8,9,17,18,26,27,35,36,44,45,53}){
inv.setItem(slot, whiteGlass());
}
PlayerInventory pInv = player.getInventory();
int helmetSlot = 11;
if(!DemonAPI.itemIsNull(pInv.getHelmet())){
inv.setItem(helmetSlot, pInv.getHelmet());
} else {
inv.setItem(helmetSlot, slotRedGlass("§c头盔槽"));
}
int chestplateSlot = 20;
if(!DemonAPI.itemIsNull(pInv.getChestplate())){
inv.setItem(chestplateSlot, pInv.getChestplate());
} else {
inv.setItem(chestplateSlot, slotRedGlass("§c胸甲槽"));
}
int leggingsSlot = 29;
if(!DemonAPI.itemIsNull(pInv.getLeggings())){
inv.setItem(leggingsSlot, pInv.getLeggings());
} else {
inv.setItem(leggingsSlot, slotRedGlass("§c护腿槽"));
}
int bootsSlot = 38;
if(!DemonAPI.itemIsNull(pInv.getBoots())){
inv.setItem(bootsSlot, pInv.getBoots());
} else {
inv.setItem(bootsSlot, slotRedGlass("§c战靴槽"));
}
int mainHandSlot = 13;
if(!DemonAPI.itemIsNull(pInv.getItemInMainHand())){
inv.setItem(mainHandSlot, pInv.getItemInMainHand());
} else {
inv.setItem(mainHandSlot, slotRedGlass("§c主手槽"));
}
int offHandSlot = 22;
if(!DemonAPI.itemIsNull(pInv.getItemInOffHand())){
inv.setItem(offHandSlot, pInv.getItemInOffHand());
} else {
inv.setItem(offHandSlot, slotRedGlass("§c副手槽"));
}
inv.setItem(15, attackAttribute(data));
inv.setItem(24, defenseAttribute(data));
player.openInventory(inv);
}
private static ItemStack attackAttribute(AttributeData data){
ItemStack stack = new ItemStack(Material.IRON_SWORD);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName("§8[§e§l攻击属性§8]");
List<String> lore = new ArrayList<>();
lore.add("§8§m------------------");
lore.add(" ");
lore.add("§C§l➨ §2物理攻击: §7"+data.get(AttributeType.PHYSICAL_ATTACK));
lore.add("§C§l➨ §2PVE攻击: §7"+data.get(AttributeType.PVE_ATTACK));
lore.add("§C§l➨ §2暴击概率: §7"+data.get(AttributeType.CRIT_CHANCE)+"%");
lore.add("§C§l➨ §2暴击伤害: §7"+String.format("%.1f", data.get(AttributeType.CRIT_DAMAGE) / 100.0)+"x");
lore.add("§C§l➨ §2命中概率: §7"+data.get(AttributeType.HIT_CHANCE)+"%");
lore.add("§C§l➨ §2护甲穿透: §7"+data.get(AttributeType.PENETRATION)+"%");
lore.add("§C§l➨ §2吸血概率: §7"+data.get(AttributeType.VAMPIRE_CHANCE)+"%");
meta.setLore(lore);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
stack.setItemMeta(meta);
return stack;
}
private static ItemStack defenseAttribute(AttributeData data){
ItemStack stack = new ItemStack(Material.IRON_CHESTPLATE);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName("§8[§e§l防御属性§8]");
List<String> lore = new ArrayList<>();
lore.add("§8§m------------------");
lore.add(" ");
lore.add("§C§l➨ §2物理防御: §7"+data.get(AttributeType.PHYSICAL_DEFENSE));
lore.add("§C§l➨ §2PVE防御: §7"+data.get(AttributeType.PVE_DEFENSE));
lore.add("§C§l➨ §2生命提升: §7"+data.get(AttributeType.MAX_HEALTH_BONUS));
lore.add("§C§l➨ §2闪避概率: §7"+data.get(AttributeType.DODGE_CHANCE)+"%");
lore.add("§C§l➨ §2格挡概率: §7"+data.get(AttributeType.BLOCK_CHANCE)+"%");
lore.add("§C§l➨ §2移速加成: §7"+data.get(AttributeType.MOVE_SPEED)+"%");
meta.setLore(lore);
meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
stack.setItemMeta(meta);
return stack;
}
private static ItemStack whiteGlass(){
ItemStack stack = new ItemStack(Material.WHITE_STAINED_GLASS_PANE);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName("§7§l[§e§l!§7§l]");
stack.setItemMeta(meta);
return stack;
}
private static ItemStack blackGlass(){
ItemStack stack = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName("§7§l[§e§l!§7§l]");
stack.setItemMeta(meta);
return stack;
}
private static ItemStack slotRedGlass(String name){
ItemStack stack = new ItemStack(Material.RED_STAINED_GLASS_PANE);
ItemMeta meta = stack.getItemMeta();
meta.setDisplayName("§8["+name+"§8]");
stack.setItemMeta(meta);
return stack;
}
}

View File

@@ -0,0 +1,126 @@
package com.io.yaohun.myitems.listener;
import com.io.yaohun.myitems.api.DamageResult;
import com.io.yaohun.myitems.engine.DamageEngine;
import com.io.yaohun.myitems.util.EquipmentCheckUtil;
import org.bukkit.ChatColor;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
public class DamageListener implements Listener {
private final DamageEngine damageEngine;
public DamageListener(DamageEngine damageEngine) {
this.damageEngine = damageEngine;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onDamage(EntityDamageByEntityEvent event) {
Entity rawDamager = event.getDamager();
Entity rawTarget = event.getEntity();
LivingEntity attacker = getRealDamager(rawDamager);
if (!(rawTarget instanceof LivingEntity target)) {
return;
}
if (attacker == null) {
return;
}
if (attacker instanceof Player player) {
if (EquipmentCheckUtil.isBrokenCustomWeapon(player)) {
player.sendActionBar(ChatColor.RED + "武器已破损,无法发挥属性效果");
return;
}
}
DamageResult result = damageEngine.calculate(attacker, target);
if (result.isMiss()) {
event.setDamage(0);
event.setCancelled(true);
if (attacker instanceof Player player) {
player.sendActionBar(ChatColor.GRAY + "Miss");
}
if (target instanceof Player player) {
player.sendActionBar(ChatColor.YELLOW + "闪避");
}
return;
}
event.setDamage(result.getDamage());
applyVampire(attacker, result);
applyReflect(attacker, result);
sendBattleMessage(attacker, target, result);
}
private void applyVampire(LivingEntity attacker, DamageResult result) {
if (!result.isVampire() || result.getHealAmount() <= 0) {
return;
}
AttributeInstance maxHealth = attacker.getAttribute(Attribute.MAX_HEALTH);
if (maxHealth == null) {
return;
}
double newHealth = Math.min(maxHealth.getValue(), attacker.getHealth() + result.getHealAmount());
attacker.setHealth(newHealth);
}
private void applyReflect(LivingEntity attacker, DamageResult result) {
if (!result.isBlock() || result.getReflectDamage() <= 0 || attacker.isDead()) {
return;
}
attacker.damage(result.getReflectDamage());
}
private void sendBattleMessage(LivingEntity attacker, LivingEntity target, DamageResult result) {
if (attacker instanceof Player player) {
StringBuilder message = new StringBuilder();
if (result.isCrit()) {
message.append(ChatColor.RED).append("暴击! ");
} else {
message.append(ChatColor.WHITE).append("伤害 ");
}
message.append(ChatColor.RED).append(String.format("%.1f", result.getDamage()));
if (result.isPenetration()) {
message.append(ChatColor.GRAY).append(" 穿透");
}
if (result.isVampire()) {
message.append(ChatColor.DARK_RED).append(" 吸血");
}
player.sendActionBar(message.toString());
}
if (target instanceof Player player && result.isBlock()) {
player.sendActionBar(ChatColor.YELLOW + "格挡 -" + ChatColor.RED + String.format("%.1f", result.getDamage()));
}
}
private LivingEntity getRealDamager(Entity entity) {
if (entity instanceof LivingEntity livingEntity) {
return livingEntity;
}
if (entity instanceof Arrow arrow) {
if (arrow.getShooter() instanceof LivingEntity shooter) {
return shooter;
}
}
return null;
}
}

View File

@@ -0,0 +1,153 @@
package com.io.yaohun.myitems.listener;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.manage.AttributeManager;
import com.io.yaohun.myitems.util.ItemAttributeUtil;
import com.io.yaohun.myitems.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Sound;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.inventory.ItemStack;
public class DurabilityListener implements Listener {
private final AttributeManager attributeManager;
public DurabilityListener(AttributeManager attributeManager) {
this.attributeManager = attributeManager;
}
/**
* 玩家攻击时:主手武器掉耐久
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerAttack(EntityDamageByEntityEvent event) {
LivingEntity damager = getRealDamager(event.getDamager());
if (!(damager instanceof Player player)) {
return;
}
if (!(event.getEntity() instanceof LivingEntity)) {
return;
}
boolean changed = false;
ItemStack weapon = player.getInventory().getItemInMainHand();
if (weapon != null && ItemAttributeUtil.hasDurability(weapon)) {
boolean wasBroken = ItemAttributeUtil.isBroken(weapon);
ItemAttributeUtil.damageDurability(weapon, 1);
changed = true;
boolean isBroken = ItemAttributeUtil.isBroken(weapon);
if (!wasBroken && isBroken) {
sendBrokenMessage(player, "Broken_MainHand");
}
}
if (changed) {
refreshLater(player);
}
}
/**
* 玩家受击时:副手 + 四件护甲掉耐久
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerDamaged(EntityDamageByEntityEvent event) {
if (!(event.getEntity() instanceof Player player)) {
return;
}
boolean changed = false;
changed |= damageOffhand(player);
changed |= damageArmorPiece(player, player.getInventory().getHelmet(), "Broken_Helmet");
changed |= damageArmorPiece(player, player.getInventory().getChestplate(), "Broken_Chestplate");
changed |= damageArmorPiece(player, player.getInventory().getLeggings(), "Broken_Leggings");
changed |= damageArmorPiece(player, player.getInventory().getBoots(), "Broken_Boots");
if (changed) {
refreshLater(player);
}
}
private boolean damageOffhand(Player player) {
ItemStack offhand = player.getInventory().getItemInOffHand();
if (offhand == null) {
return false;
}
if (!ItemAttributeUtil.hasDurability(offhand)) {
return false;
}
boolean wasBroken = ItemAttributeUtil.isBroken(offhand);
ItemAttributeUtil.damageDurability(offhand, 1);
boolean isBroken = ItemAttributeUtil.isBroken(offhand);
if (!wasBroken && isBroken) {
sendBrokenMessage(player, "Broken_Offhand");
}
return true;
}
private boolean damageArmorPiece(Player player, ItemStack armor, String key) {
if (armor == null) {
return false;
}
if (!ItemAttributeUtil.hasDurability(armor)) {
return false;
}
boolean wasBroken = ItemAttributeUtil.isBroken(armor);
ItemAttributeUtil.damageDurability(armor, 1);
boolean isBroken = ItemAttributeUtil.isBroken(armor);
if (!wasBroken && isBroken) {
sendBrokenMessage(player, key);
}
return true;
}
private void sendBrokenMessage(Player player, String key) {
MessageUtil.sendMessageKey(player, key, Sound.ITEM_SHIELD_BREAK);
}
private LivingEntity getRealDamager(Entity entity) {
if (entity instanceof LivingEntity livingEntity) {
return livingEntity;
}
if (entity instanceof Arrow arrow) {
if (arrow.getShooter() instanceof LivingEntity shooter) {
return shooter;
}
}
return null;
}
private void refreshLater(Player player) {
attributeManager.markDirty(player);
Bukkit.getScheduler().runTaskLater(MyItems.inst(), () -> {
if (player.isOnline()) {
attributeManager.refresh(player);
}
}, 1L);
}
}

View File

@@ -0,0 +1,33 @@
package com.io.yaohun.myitems.listener;
import com.io.yaohun.myitems.manage.EntityAttributeStorage;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.world.EntitiesUnloadEvent;
public class EntityCleanupListener implements Listener {
private final EntityAttributeStorage storage;
public EntityCleanupListener(EntityAttributeStorage storage) {
this.storage = storage;
}
@EventHandler
public void onDeath(EntityDeathEvent event) {
if (event.getEntity() instanceof LivingEntity livingEntity) {
storage.clearEntity(livingEntity.getUniqueId());
}
}
@EventHandler
public void onEntitiesUnload(EntitiesUnloadEvent event) {
event.getEntities().forEach(entity -> {
if (entity instanceof LivingEntity livingEntity) {
storage.clearEntity(livingEntity.getUniqueId());
}
});
}
}

View File

@@ -0,0 +1,153 @@
package com.io.yaohun.myitems.listener;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.manage.AttributeManager;
import com.io.yaohun.myitems.util.DemonAPI;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.*;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
public class PlayerEquipListener implements Listener {
private final AttributeManager attributeManager;
public PlayerEquipListener(AttributeManager attributeManager) {
this.attributeManager = attributeManager;
}
private boolean isArmor(ItemStack item) {
String name = item.getType().name();
return name.endsWith("_HELMET")
|| name.endsWith("_CHESTPLATE")
|| name.endsWith("_LEGGINGS")
|| name.endsWith("_BOOTS");
}
@EventHandler(priority = EventPriority.MONITOR)
public void onJoin(PlayerJoinEvent event) {
scheduleRefresh(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
if (shouldRefresh(event)) {
scheduleRefresh(player);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
scheduleRefresh(player);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSwap(PlayerSwapHandItemsEvent event) {
scheduleRefresh(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onHeldChange(PlayerItemHeldEvent event) {
scheduleRefresh(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onRespawn(PlayerRespawnEvent event) {
scheduleRefresh(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onDeath(PlayerDeathEvent event) {
scheduleRefresh(event.getEntity());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onBreak(PlayerItemBreakEvent event) {
scheduleRefresh(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onWorldChange(PlayerChangedWorldEvent event) {
scheduleRefresh(event.getPlayer());
}
private boolean shouldRefresh(InventoryClickEvent event) {
InventoryType.SlotType slotType = event.getSlotType();
ClickType click = event.getClick();
InventoryAction action = event.getAction();
// 只要碰到护甲槽 / 快捷栏 / 副手相关,优先刷新
if (slotType == InventoryType.SlotType.ARMOR) {
return true;
}
if (slotType == InventoryType.SlotType.QUICKBAR) {
return true;
}
if (slotType == InventoryType.SlotType.CONTAINER) {
// 普通容器也有可能通过 shift 或热键把装备切上去
}
// 数字键切换
if (click == ClickType.NUMBER_KEY) {
return true;
}
// Shift 快速移动
if (click == ClickType.SHIFT_LEFT || click == ClickType.SHIFT_RIGHT) {
return true;
}
// 常见影响装备栏的动作
switch (action) {
case HOTBAR_SWAP:
case MOVE_TO_OTHER_INVENTORY:
case SWAP_WITH_CURSOR:
case PLACE_ALL:
case PLACE_ONE:
case PLACE_SOME:
case PICKUP_ALL:
case PICKUP_HALF:
case PICKUP_ONE:
case PICKUP_SOME:
case COLLECT_TO_CURSOR:
return true;
default:
return false;
}
}
private void scheduleRefresh(Player player) {
if (player == null) {
return;
}
attributeManager.markDirty(player);
Bukkit.getScheduler().runTaskLater(MyItems.inst(), () -> {
if (!player.isOnline()) {
return;
}
attributeManager.refresh(player);
}, 2L);
}
}

View File

@@ -0,0 +1,52 @@
package com.io.yaohun.myitems.listener;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.gui.PanelGui;
import com.io.yaohun.myitems.manage.AttributeManager;
import com.io.yaohun.myitems.manage.CacheManager;
import com.io.yaohun.myitems.util.SpeedUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
public class PlayerJoinListener implements Listener {
private final AttributeManager attributeManager;
private final CacheManager cacheManager;
public PlayerJoinListener(AttributeManager attributeManager, CacheManager cacheManager) {
this.attributeManager = attributeManager;
this.cacheManager = cacheManager;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
Bukkit.getScheduler().runTaskLater(MyItems.inst(), () -> {
if (!player.isOnline()) {
return;
}
attributeManager.markDirty(player);
attributeManager.refresh(player);
}, 20L);
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
SpeedUtil.reset(player);
cacheManager.remove(player.getUniqueId());
}
@EventHandler
public void onClick(InventoryClickEvent e){
if(e.getView().getTitle().equalsIgnoreCase(PanelGui.invTitle)){
e.setCancelled(true);
}
}
}

View File

@@ -0,0 +1,143 @@
package com.io.yaohun.myitems.listener;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.manage.AttributeManager;
import com.io.yaohun.myitems.manage.SuitManager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerItemBreakEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
public class SuitListener implements Listener {
private final SuitManager suitManager;
private final AttributeManager attributeManager;
public SuitListener(SuitManager suitManager, AttributeManager attributeManager) {
this.suitManager = suitManager;
this.attributeManager = attributeManager;
}
@EventHandler(priority = EventPriority.MONITOR)
public void onJoin(PlayerJoinEvent event) {
scheduleCheck(event.getPlayer(), false, 2L);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onQuit(PlayerQuitEvent event) {
suitManager.clearPlayerState(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) {
return;
}
if (shouldCheck(event)) {
scheduleCheck(player, true, 2L);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onInventoryDrag(InventoryDragEvent event) {
if (event.getWhoClicked() instanceof Player player) {
scheduleCheck(player, true, 2L);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSwap(PlayerSwapHandItemsEvent event) {
scheduleCheck(event.getPlayer(), true, 2L);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onHeldChange(PlayerItemHeldEvent event) {
scheduleCheck(event.getPlayer(), true, 2L);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onRespawn(PlayerRespawnEvent event) {
scheduleCheck(event.getPlayer(), true, 2L);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onDeath(PlayerDeathEvent event) {
scheduleCheck(event.getEntity(), true, 2L);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onBreak(PlayerItemBreakEvent event) {
scheduleCheck(event.getPlayer(), true, 1L);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onWorldChange(PlayerChangedWorldEvent event) {
scheduleCheck(event.getPlayer(), true, 2L);
}
private boolean shouldCheck(InventoryClickEvent event) {
InventoryType.SlotType slotType = event.getSlotType();
ClickType click = event.getClick();
InventoryAction action = event.getAction();
if (slotType == InventoryType.SlotType.ARMOR || slotType == InventoryType.SlotType.QUICKBAR) {
return true;
}
if (click == ClickType.NUMBER_KEY || click == ClickType.SHIFT_LEFT || click == ClickType.SHIFT_RIGHT) {
return true;
}
switch (action) {
case HOTBAR_SWAP:
case MOVE_TO_OTHER_INVENTORY:
case SWAP_WITH_CURSOR:
case PLACE_ALL:
case PLACE_ONE:
case PLACE_SOME:
case PICKUP_ALL:
case PICKUP_HALF:
case PICKUP_ONE:
case PICKUP_SOME:
case COLLECT_TO_CURSOR:
return true;
default:
return false;
}
}
private void scheduleCheck(Player player, boolean sendMessage, long delay) {
if (player == null) {
return;
}
Bukkit.getScheduler().runTaskLater(MyItems.inst(), () -> {
if (!player.isOnline()) {
return;
}
boolean changed = suitManager.refreshPlayerSuitState(player, sendMessage);
if (!changed) {
return;
}
attributeManager.markDirty(player);
attributeManager.refresh(player);
}, delay);
}
}

View File

@@ -0,0 +1,77 @@
package com.io.yaohun.myitems.listener;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.manage.AttributeManager;
import com.io.yaohun.myitems.util.ItemAttributeUtil;
import com.io.yaohun.myitems.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemStack;
public class ToolDurabilityListener implements Listener {
private final AttributeManager attributeManager;
public ToolDurabilityListener(AttributeManager attributeManager) {
this.attributeManager = attributeManager;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBreak(BlockBreakEvent event) {
Player player = event.getPlayer();
ItemStack item = player.getInventory().getItemInMainHand();
if (item == null) {
return;
}
if (!isTool(item.getType())) {
return;
}
if (!ItemAttributeUtil.hasDurability(item)) {
return;
}
boolean wasBroken = ItemAttributeUtil.isBroken(item);
if (wasBroken) {
event.setCancelled(true);
return;
}
ItemAttributeUtil.damageDurability(item, 1);
boolean isBroken = ItemAttributeUtil.isBroken(item);
if (!wasBroken && isBroken) {
MessageUtil.sendMessageKey(player,
"Broken_MainHand",
Sound.ITEM_SHIELD_BREAK);
}
refreshLater(player);
}
private boolean isTool(Material type) {
String name = type.name();
return name.endsWith("_PICKAXE")
|| name.endsWith("_AXE");
}
private void refreshLater(Player player) {
attributeManager.markDirty(player);
Bukkit.getScheduler().runTaskLater(MyItems.inst(), () -> {
if (player.isOnline()) {
attributeManager.refresh(player);
}
}, 1L);
}
}

View File

@@ -0,0 +1,269 @@
package com.io.yaohun.myitems.manage;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.EquipmentSlotType;
import com.io.yaohun.myitems.model.PlayerAttributeProfile;
import com.io.yaohun.myitems.util.EquipmentTypeUtil;
import com.io.yaohun.myitems.util.HealthUtil;
import com.io.yaohun.myitems.util.ItemAttributeUtil;
import com.io.yaohun.myitems.util.SpeedUtil;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class AttributeManager {
private final CacheManager cacheManager;
private final SuitManager suitManager;
public AttributeManager(CacheManager cacheManager, SuitManager suitManager) {
this.cacheManager = cacheManager;
this.suitManager = suitManager;
}
/**
* 计算玩家最终属性
*/
public AttributeData calculateFinalAttributes(Player player) {
AttributeData result = new AttributeData();
AttributeData base = getBaseAttributes(player);
AttributeData equipment = getEquipmentAttributes(player);
AttributeData suit = getSuitAttributes(player);
AttributeData gems = getGemAttributes(player);
AttributeData buffs = getBuffAttributes(player);
AttributeData temporary = getTemporaryAttributes(player);
result.merge(base);
result.merge(equipment);
result.merge(suit);
result.merge(gems);
result.merge(buffs);
result.merge(temporary);
applyPveAttackAddition(result);
return result;
}
private void applyPveAttackAddition(AttributeData data) {
double pveAttack = data.get(AttributeType.PVE_ATTACK);
double addition = data.get(AttributeType.PVE_ATTACK_ADDITION);
if (Math.abs(pveAttack) <= 0.000001D || Math.abs(addition) <= 0.000001D) {
return;
}
data.set(AttributeType.PVE_ATTACK, pveAttack * (1.0D + addition / 100.0D));
}
/**
* 主动刷新玩家属性缓存
*/
public void refresh(Player player) {
// MyItems.inst().getLogger().info("已刷新玩家属性: " + player.getName());
if (player == null || !player.isOnline()) {
return;
}
AttributeData finalData = calculateFinalAttributes(player);
PlayerAttributeProfile profile = cacheManager.getOrCreate(player.getUniqueId());
profile.setFinalAttributes(finalData);
profile.setDirty(false);
HealthUtil.applyMaxHealth(player, finalData);
SpeedUtil.applyMoveSpeed(player, finalData);
//MyItems.inst().getLogger().info("已刷新玩家属性: " + player.getName() + " | main=" + player.getInventory().getItemInMainHand().getType().name());
}
/**
* 标记该玩家属性缓存已过期
*/
public void markDirty(Player player) {
if (player == null) {
return;
}
PlayerAttributeProfile profile = cacheManager.getOrCreate(player.getUniqueId());
profile.setDirty(true);
}
/**
* 获取玩家最终属性
* 如果缓存是脏的,则先即时刷新一次再返回
*/
public AttributeData getFinalAttributes(Player player) {
if (player == null) {
return new AttributeData();
}
PlayerAttributeProfile profile = cacheManager.getOrCreate(player.getUniqueId());
if (profile.isDirty()) {
refresh(player);
profile = cacheManager.getOrCreate(player.getUniqueId());
}
return profile.getFinalAttributes();
}
/**
* 玩家基础属性
*/
private AttributeData getBaseAttributes(Player player) {
AttributeData data = new AttributeData();
// 这里后续可以接:
// 1. 职业基础属性
// 2. 等级成长属性
// 3. 默认初始属性
return data;
}
/**
* 装备属性
*/
private AttributeData getEquipmentAttributes(Player player) {
AttributeData data = new AttributeData();
if (player == null || player.getInventory() == null) {
return data;
}
mergeIfMatches(data, player.getInventory().getItemInMainHand(), EquipmentSlotType.MAIN_HAND);
mergeIfMatches(data, player.getInventory().getItemInOffHand(), EquipmentSlotType.OFF_HAND);
mergeIfMatches(data, player.getInventory().getHelmet(), EquipmentSlotType.HELMET);
mergeIfMatches(data, player.getInventory().getChestplate(), EquipmentSlotType.CHESTPLATE);
mergeIfMatches(data, player.getInventory().getLeggings(), EquipmentSlotType.LEGGINGS);
mergeIfMatches(data, player.getInventory().getBoots(), EquipmentSlotType.BOOTS);
return data;
}
private void mergeIfMatches(AttributeData target, ItemStack item, EquipmentSlotType slotType) {
if (EquipmentTypeUtil.matchesSlot(item, slotType)) {
target.merge(ItemAttributeUtil.readAttributes(item));
}
}
private AttributeData getSuitAttributes(Player player) {
if (suitManager == null) {
return new AttributeData();
}
return suitManager.getSuitAttributes(player);
}
/**
* 宝石属性
*/
private AttributeData getGemAttributes(Player player) {
AttributeData data = new AttributeData();
// 后续接宝石系统
return data;
}
/**
* Buff属性
*/
private AttributeData getBuffAttributes(Player player) {
AttributeData data = new AttributeData();
// 后续接药水、称号、区域效果、临时buff
return data;
}
/**
* 临时状态属性
*/
private AttributeData getTemporaryAttributes(Player player) {
AttributeData data = new AttributeData();
// 后续接技能触发、短时增伤、短时减伤等
return data;
}
private AttributeType matchAttributeType(String input) {
if (input == null || input.isEmpty()) {
return null;
}
switch (input.toLowerCase()) {
case "physical_attack":
case "攻击":
case "物理攻击":
return AttributeType.PHYSICAL_ATTACK;
case "physical_defense":
case "防御":
case "物理防御":
return AttributeType.PHYSICAL_DEFENSE;
case "pve_attack":
case "pve攻击":
return AttributeType.PVE_ATTACK;
case "pve_defense":
case "pve防御":
return AttributeType.PVE_DEFENSE;
case "max_health_bonus":
case "health":
case "生命":
case "生命增加":
return AttributeType.MAX_HEALTH_BONUS;
case "crit_chance":
case "暴击":
case "暴击率":
case "暴击概率":
return AttributeType.CRIT_CHANCE;
case "crit_damage":
case "暴击伤害":
return AttributeType.CRIT_DAMAGE;
case "hit_chance":
case "命中":
case "命中率":
case "命中几率":
return AttributeType.HIT_CHANCE;
case "dodge_chance":
case "闪避":
case "闪避率":
case "闪避几率":
return AttributeType.DODGE_CHANCE;
case "block_chance":
case "block":
case "格挡":
case "格挡概率":
return AttributeType.BLOCK_CHANCE;
case "vampire_chance":
case "vampire":
case "吸血":
case "吸血概率":
return AttributeType.VAMPIRE_CHANCE;
case "penetration":
case "护甲穿透":
case "穿透":
return AttributeType.PENETRATION;
case "move_speed":
case "speed":
case "移速":
case "移动速度":
return AttributeType.MOVE_SPEED;
default:
return null;
}
}
}

View File

@@ -0,0 +1,28 @@
package com.io.yaohun.myitems.manage;
import com.io.yaohun.myitems.model.PlayerAttributeProfile;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class CacheManager {
private final Map<UUID, PlayerAttributeProfile> cacheMap = new ConcurrentHashMap<>();
public PlayerAttributeProfile getOrCreate(UUID uuid) {
return cacheMap.computeIfAbsent(uuid, PlayerAttributeProfile::new);
}
public PlayerAttributeProfile get(UUID uuid) {
return cacheMap.get(uuid);
}
public void remove(UUID uuid) {
cacheMap.remove(uuid);
}
public void clear() {
cacheMap.clear();
}
}

View File

@@ -0,0 +1,78 @@
package com.io.yaohun.myitems.manage;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class EntityAttributeStorage {
/**
* entityUUID -> (source -> AttributeData)
*/
private final Map<UUID, Map<String, AttributeData>> entityAttributes = new ConcurrentHashMap<>();
public void addAttributes(UUID uuid, String source, AttributeData data) {
if (uuid == null || source == null || source.isEmpty() || data == null) {
return;
}
entityAttributes
.computeIfAbsent(uuid, k -> new ConcurrentHashMap<>())
.put(source, data);
}
public void removeAttributes(UUID uuid, String source) {
if (uuid == null || source == null || source.isEmpty()) {
return;
}
Map<String, AttributeData> sourceMap = entityAttributes.get(uuid);
if (sourceMap == null) {
return;
}
sourceMap.remove(source);
if (sourceMap.isEmpty()) {
entityAttributes.remove(uuid);
}
}
public void clearEntity(UUID uuid) {
if (uuid == null) {
return;
}
entityAttributes.remove(uuid);
}
public AttributeData getFinalAttributes(UUID uuid) {
AttributeData result = new AttributeData();
if (uuid == null) {
return result;
}
Map<String, AttributeData> sourceMap = entityAttributes.get(uuid);
if (sourceMap == null || sourceMap.isEmpty()) {
return result;
}
for (AttributeData data : sourceMap.values()) {
result.merge(data);
}
return result;
}
public double getAttribute(UUID uuid, AttributeType type) {
return getFinalAttributes(uuid).get(type);
}
public boolean hasAnyAttribute(UUID uuid) {
Map<String, AttributeData> sourceMap = entityAttributes.get(uuid);
return sourceMap != null && !sourceMap.isEmpty();
}
}

View File

@@ -0,0 +1,4 @@
package com.io.yaohun.myitems.manage;
public class EquipmentAttributeManager {
}

View File

@@ -0,0 +1,192 @@
package com.io.yaohun.myitems.manage;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.EquipmentSlotType;
import com.io.yaohun.myitems.api.TierType;
import com.io.yaohun.myitems.data.CEngineData;
import com.io.yaohun.myitems.data.ItemTemplate;
import com.io.yaohun.myitems.util.DemonAPI;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ItemManager {
private static final Map<String, ItemTemplate> templateMap = new HashMap<>();
public static ItemTemplate getItemTemplate(String itemId){
return templateMap.get(itemId);
}
public static List<String> getTemplateList() {
return new ArrayList<>(templateMap.keySet());
}
public static void reloadItemManager(MyItems plugin){
String folderPath = plugin.getDataFolder()+"/Item_Generator";
File folder = new File(folderPath);
if (!folder.exists() || !folder.isDirectory()) {
templateMap.clear();
return;
}
List<File> ymlFiles = new ArrayList<>();
DemonAPI.collectYmlFiles(folder, ymlFiles);
if(ymlFiles.isEmpty()){
templateMap.clear();
return;
}
Map<String, ItemTemplate> loadedTemplates = new HashMap<>();
for (File file : ymlFiles){
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
for (String itemId : config.getKeys(false)){
try {
ItemTemplate template = loadTemplate(config, itemId);
loadedTemplates.put(itemId,template);
} catch (RuntimeException e) {
Bukkit.getLogger().warning("[MyItems] 物品模板加载失败: " + file.getName() + " -> " + itemId + ",原因: " + e.getMessage());
}
}
}
templateMap.clear();
templateMap.putAll(loadedTemplates);
Bukkit.getConsoleSender().sendMessage("§6[Myitems] §7载入物品: §f"+templateMap.size()+"");
}
private static ItemTemplate loadTemplate(FileConfiguration config, String itemId) {
ItemStack itemStack = null;
Material material = parseMaterial(config, itemId);
String displayName = config.getString(itemId + ".Display_Name", itemId);
List<String> lore = config.getStringList(itemId + ".Lore");
String itemIcon = config.getString(itemId+".Item_Icon","");
String ceNamespace = config.getString(itemId+".CraftEngine.Namespace", "error");
if(!"error".equalsIgnoreCase(ceNamespace)) {
String ceValue = config.getString(itemId + ".CraftEngine.Value", "default");
CEngineData engineData = new CEngineData(ceNamespace, ceValue);
if (engineData.isItemStack()) {
itemStack = engineData.getItemStack();
}
}
EquipmentSlotType equipSlotType = parseEquipSlot(config, itemId);
Map<TierType, Double> tierTypes = new HashMap<>();
Map<TierType, ItemTemplate.TierAttribute> tierAttributes = new HashMap<>();
ConfigurationSection section = config.getConfigurationSection(itemId+".Tier_Types");
if(section == null) {
throw new IllegalArgumentException("缺少 Tier_Types 节点");
}
for (String tier : section.getKeys(false)) {
TierType tierType = parseTierType(tier);
double chance = section.getDouble(tier+".Chance");
if (chance <= 0) {
throw new IllegalArgumentException("品质 " + tier + " 的 Chance 必须大于 0");
}
tierTypes.put(tierType,chance);
int currentDurability = section.getInt(tier+".Current_Durability",0);
int maxDurability = section.getInt(tier+".Max_Durability",0);
if (currentDurability < 0 || maxDurability < 0) {
throw new IllegalArgumentException("品质 " + tier + " 的耐久不能为负数");
}
Map<AttributeType, ItemTemplate.AttributeRandomValue> attributes = new HashMap<>();
ConfigurationSection attSection = section.getConfigurationSection(tier+".Attributes");
if(attSection != null) {
for (String att : attSection.getKeys(false)){
AttributeType attributeType = parseAttributeType(att);
attributes.put(attributeType, parseAttributeValue(attSection.getString(att), tier, att));
}
}
ItemTemplate.TierAttribute tierAttribute = new ItemTemplate.TierAttribute(currentDurability, maxDurability, attributes);
tierAttributes.put(tierType,tierAttribute);
}
if (tierTypes.isEmpty()) {
throw new IllegalArgumentException("至少需要一个有效品质");
}
String suitId = config.getString(itemId + ".Suit_Id", "default");
return new ItemTemplate(itemId,itemIcon,itemStack,
material,displayName,
lore,equipSlotType,
tierTypes,tierAttributes,suitId);
}
private static Material parseMaterial(FileConfiguration config, String itemId) {
String materialName = config.getString(itemId + ".Material");
if (materialName == null || materialName.isEmpty()) {
throw new IllegalArgumentException("缺少 Material");
}
try {
return Material.valueOf(materialName.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("无效 Material: " + materialName);
}
}
private static EquipmentSlotType parseEquipSlot(FileConfiguration config, String itemId) {
String slotName = config.getString(itemId+".Equip_Slot");
if (slotName == null || slotName.isEmpty()) {
throw new IllegalArgumentException("缺少 Equip_Slot");
}
try {
return EquipmentSlotType.valueOf(slotName.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("无效 Equip_Slot: " + slotName);
}
}
private static TierType parseTierType(String tier) {
try {
return TierType.valueOf(tier.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("无效品质类型: " + tier);
}
}
private static AttributeType parseAttributeType(String attribute) {
try {
return AttributeType.valueOf(attribute.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("无效属性类型: " + attribute);
}
}
private static ItemTemplate.AttributeRandomValue parseAttributeValue(String rawValue, String tier, String attribute) {
if (rawValue == null || rawValue.isEmpty()) {
throw new IllegalArgumentException("品质 " + tier + " 的属性 " + attribute + " 缺少数值");
}
String[] values = rawValue.split("~");
if (values.length > 2) {
throw new IllegalArgumentException("品质 " + tier + " 的属性 " + attribute + " 数值格式错误");
}
try {
double first = Double.parseDouble(values[0].trim());
double second = values.length == 1 ? first : Double.parseDouble(values[1].trim());
return new ItemTemplate.AttributeRandomValue(Math.min(first, second), Math.max(first, second));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("品质 " + tier + " 的属性 " + attribute + " 数值不是数字");
}
}
}

View File

@@ -0,0 +1,269 @@
package com.io.yaohun.myitems.manage;
import com.io.yaohun.myitems.MyItems;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.api.EquipmentSlotType;
import com.io.yaohun.myitems.data.SuitTemplate;
import com.io.yaohun.myitems.util.DemonAPI;
import com.io.yaohun.myitems.util.EquipmentTypeUtil;
import com.io.yaohun.myitems.util.ItemAttributeUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class SuitManager {
private final Map<String, SuitTemplate> suitTemplateMap = new HashMap<>();
private final Map<String, String> itemSuitMap = new HashMap<>();
private final Map<UUID, Set<String>> playerActiveSuitMap = new HashMap<>();
public void reloadSuitManager(MyItems plugin) {
String folderPath = plugin.getDataFolder() + "/Suit_Generator";
File folder = new File(folderPath);
if (!folder.exists() && !folder.mkdirs()) {
Bukkit.getLogger().warning("[MyItems] 套装配置目录创建失败: " + folderPath);
return;
}
List<File> ymlFiles = new ArrayList<>();
DemonAPI.collectYmlFiles(folder, ymlFiles);
if (ymlFiles.isEmpty()) {
suitTemplateMap.clear();
itemSuitMap.clear();
playerActiveSuitMap.clear();
return;
}
Map<String, SuitTemplate> loadedSuits = new HashMap<>();
Map<String, String> loadedItemSuitMap = new HashMap<>();
for (File file : ymlFiles) {
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
for (String suitId : config.getKeys(false)) {
try {
SuitTemplate template = loadTemplate(config, suitId, loadedItemSuitMap);
loadedSuits.put(suitId, template);
} catch (RuntimeException e) {
Bukkit.getLogger().warning("[MyItems] 套装模板加载失败: " + file.getName() + " -> " + suitId + ",原因: " + e.getMessage());
}
}
}
suitTemplateMap.clear();
suitTemplateMap.putAll(loadedSuits);
itemSuitMap.clear();
itemSuitMap.putAll(loadedItemSuitMap);
playerActiveSuitMap.clear();
Bukkit.getConsoleSender().sendMessage("§6[Myitems] §7载入套装: §f" + suitTemplateMap.size() + "");
}
public AttributeData getSuitAttributes(Player player) {
AttributeData data = new AttributeData();
if (player == null || player.getInventory() == null || suitTemplateMap.isEmpty()) {
return data;
}
for (String suitId : getActiveSuitIds(player)) {
SuitTemplate template = suitTemplateMap.get(suitId);
if (template == null) {
continue;
}
for (Map.Entry<AttributeType, Double> attribute : template.getAttributes().entrySet()) {
data.add(attribute.getKey(), attribute.getValue());
}
}
return data;
}
public boolean refreshPlayerSuitState(Player player, boolean sendMessage) {
if (player == null) {
return false;
}
Set<String> oldSuits = playerActiveSuitMap.getOrDefault(player.getUniqueId(), Collections.emptySet());
Set<String> newSuits = getActiveSuitIds(player);
if (oldSuits.equals(newSuits)) {
return false;
}
Set<String> enabled = new HashSet<>(newSuits);
enabled.removeAll(oldSuits);
Set<String> disabled = new HashSet<>(oldSuits);
disabled.removeAll(newSuits);
playerActiveSuitMap.put(player.getUniqueId(), new HashSet<>(newSuits));
if (sendMessage) {
for (String suitId : enabled) {
player.sendMessage(ChatColor.GREEN + "[MyItems] 套装属性已生效: " + ChatColor.WHITE + getDisplayName(suitId));
}
for (String suitId : disabled) {
player.sendMessage(ChatColor.RED + "[MyItems] 套装属性已失效: " + ChatColor.WHITE + getDisplayName(suitId));
}
}
return true;
}
public Set<String> getActiveSuitIds(Player player) {
if (player == null || player.getInventory() == null || suitTemplateMap.isEmpty()) {
return Collections.emptySet();
}
Map<String, Integer> suitCounts = getSuitCounts(player);
Set<String> activeSuits = new HashSet<>();
for (Map.Entry<String, Integer> entry : suitCounts.entrySet()) {
SuitTemplate template = suitTemplateMap.get(entry.getKey());
if (template != null && entry.getValue() >= template.getRequireAmount()) {
activeSuits.add(entry.getKey());
}
}
return activeSuits;
}
public void clearPlayerState(Player player) {
if (player != null) {
playerActiveSuitMap.remove(player.getUniqueId());
}
}
private String getDisplayName(String suitId) {
SuitTemplate template = suitTemplateMap.get(suitId);
return template == null ? suitId : template.getDisplayName();
}
private Map<String, Integer> getSuitCounts(Player player) {
Map<String, Integer> suitCounts = new HashMap<>();
Set<String> countedItems = new HashSet<>();
countIfMatches(suitCounts, countedItems, player.getInventory().getItemInMainHand(), EquipmentSlotType.MAIN_HAND);
countIfMatches(suitCounts, countedItems, player.getInventory().getItemInOffHand(), EquipmentSlotType.OFF_HAND);
countIfMatches(suitCounts, countedItems, player.getInventory().getHelmet(), EquipmentSlotType.HELMET);
countIfMatches(suitCounts, countedItems, player.getInventory().getChestplate(), EquipmentSlotType.CHESTPLATE);
countIfMatches(suitCounts, countedItems, player.getInventory().getLeggings(), EquipmentSlotType.LEGGINGS);
countIfMatches(suitCounts, countedItems, player.getInventory().getBoots(), EquipmentSlotType.BOOTS);
return suitCounts;
}
private void countIfMatches(Map<String, Integer> suitCounts, Set<String> countedItems,
ItemStack item, EquipmentSlotType slotType) {
if (!EquipmentTypeUtil.matchesSlot(item, slotType) || ItemAttributeUtil.isBroken(item)) {
return;
}
String itemId = ItemAttributeUtil.readTemplateId(item);
if (itemId == null || itemId.isEmpty()) {
return;
}
String suitId = ItemAttributeUtil.readSuitId(item);
if (suitId == null || suitId.isEmpty()) {
suitId = itemSuitMap.get(itemId);
}
SuitTemplate template = suitTemplateMap.get(suitId);
if (template == null || !template.containsItem(itemId)) {
return;
}
String countedKey = suitId + ":" + itemId;
if (!countedItems.add(countedKey)) {
return;
}
suitCounts.put(suitId, suitCounts.getOrDefault(suitId, 0) + 1);
}
private SuitTemplate loadTemplate(FileConfiguration config, String suitId, Map<String, String> loadedItemSuitMap) {
String displayName = config.getString(suitId + ".Display_Name", suitId);
int requireAmount = config.getInt(suitId + ".Require", config.getInt(suitId + ".需求件数", 4));
if (requireAmount <= 0) {
throw new IllegalArgumentException("Require 必须大于 0");
}
List<String> itemList = config.getStringList(suitId + ".Items");
if (itemList.isEmpty()) {
itemList = config.getStringList(suitId + ".物品");
}
if (itemList.isEmpty()) {
throw new IllegalArgumentException("缺少 Items 或 物品");
}
Set<String> itemIds = new HashSet<>(itemList);
for (String itemId : itemIds) {
String oldSuitId = loadedItemSuitMap.get(itemId);
if (oldSuitId != null && !oldSuitId.equals(suitId)) {
throw new IllegalArgumentException("物品 " + itemId + " 同时属于 " + oldSuitId + "" + suitId);
}
}
for (String itemId : itemIds) {
loadedItemSuitMap.put(itemId, suitId);
}
Map<AttributeType, Double> attributes = loadAttributes(config, suitId);
if (attributes.isEmpty()) {
throw new IllegalArgumentException("缺少 Attributes 或 属性");
}
return new SuitTemplate(suitId, displayName, requireAmount, itemIds, attributes);
}
private Map<AttributeType, Double> loadAttributes(FileConfiguration config, String suitId) {
ConfigurationSection section = config.getConfigurationSection(suitId + ".Attributes");
if (section == null) {
section = config.getConfigurationSection(suitId + ".属性");
}
Map<AttributeType, Double> attributes = new HashMap<>();
if (section == null) {
return attributes;
}
for (String key : section.getKeys(false)) {
AttributeType type = parseAttributeType(key);
attributes.put(type, parseAttributeValue(section.getString(key), key));
}
return attributes;
}
private AttributeType parseAttributeType(String attribute) {
try {
return AttributeType.valueOf(attribute.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("无效属性类型: " + attribute);
}
}
private double parseAttributeValue(String rawValue, String attribute) {
if (rawValue == null || rawValue.isEmpty()) {
throw new IllegalArgumentException("属性 " + attribute + " 缺少数值");
}
if (rawValue.contains("~")) {
throw new IllegalArgumentException("属性 " + attribute + " 必须使用固定数值");
}
try {
return Double.parseDouble(rawValue.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("属性 " + attribute + " 数值不是数字");
}
}
}

View File

@@ -0,0 +1,45 @@
package com.io.yaohun.myitems.model;
import com.io.yaohun.myitems.api.AttributeData;
import java.util.UUID;
public class PlayerAttributeProfile {
private final UUID uuid;
private AttributeData finalAttributes;
private long lastUpdateTime;
private boolean dirty;
public PlayerAttributeProfile(UUID uuid) {
this.uuid = uuid;
this.finalAttributes = new AttributeData();
this.lastUpdateTime = System.currentTimeMillis();
this.dirty = true;
}
public UUID getUuid() {
return uuid;
}
public AttributeData getFinalAttributes() {
return finalAttributes;
}
public void setFinalAttributes(AttributeData finalAttributes) {
this.finalAttributes = finalAttributes;
this.lastUpdateTime = System.currentTimeMillis();
}
public long getLastUpdateTime() {
return lastUpdateTime;
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
}

View File

@@ -0,0 +1,54 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.api.AttributeType;
import java.text.DecimalFormat;
public final class AttributeFormatUtil {
private static final DecimalFormat INT_FORMAT = new DecimalFormat("0");
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("0.##");
public static String format(AttributeType type, double value) {
switch (type) {
case PHYSICAL_ATTACK:
case PHYSICAL_DEFENSE:
case PVE_ATTACK:
case PVE_DEFENSE:
case MAX_HEALTH_BONUS:
case EQUIPMENT_DURABILITY:
return INT_FORMAT.format(value);
case CRIT_CHANCE:
case CRIT_DAMAGE:
case HIT_CHANCE:
case DODGE_CHANCE:
case BLOCK_CHANCE:
case VAMPIRE_CHANCE:
case PENETRATION:
case MOVE_SPEED:
case PVE_ATTACK_ADDITION:
return DOUBLE_FORMAT.format(value) + "%";
default:
return DOUBLE_FORMAT.format(value);
}
}
public static boolean isPercent(AttributeType type) {
switch (type) {
case CRIT_CHANCE:
case CRIT_DAMAGE:
case HIT_CHANCE:
case DODGE_CHANCE:
case BLOCK_CHANCE:
case VAMPIRE_CHANCE:
case PENETRATION:
case MOVE_SPEED:
case PVE_ATTACK_ADDITION:
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,54 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.MyItems;
import org.bukkit.NamespacedKey;
public final class AttributeKeys {
public static final NamespacedKey EQUIP_SLOT =
new NamespacedKey(MyItems.inst(), "equip_slot");
public static final NamespacedKey TEMPLATE_ID =
new NamespacedKey(MyItems.inst(), "template_id");
public static final NamespacedKey SUIT_ID =
new NamespacedKey(MyItems.inst(), "suit_id");
public static final NamespacedKey PHYSICAL_ATTACK =
new NamespacedKey(MyItems.inst(), "physical_attack");
public static final NamespacedKey PHYSICAL_DEFENSE =
new NamespacedKey(MyItems.inst(), "physical_defense");
public static final NamespacedKey PVE_ATTACK =
new NamespacedKey(MyItems.inst(), "pve_attack");
public static final NamespacedKey PVE_ATTACK_ADDITION =
new NamespacedKey(MyItems.inst(), "pve_attack_addition");
public static final NamespacedKey PVE_DEFENSE =
new NamespacedKey(MyItems.inst(), "pve_defense");
public static final NamespacedKey MAX_HEALTH_BONUS =
new NamespacedKey(MyItems.inst(), "max_health_bonus");
public static final NamespacedKey CRIT_CHANCE =
new NamespacedKey(MyItems.inst(), "crit_chance");
public static final NamespacedKey CRIT_DAMAGE =
new NamespacedKey(MyItems.inst(), "crit_damage");
public static final NamespacedKey HIT_CHANCE =
new NamespacedKey(MyItems.inst(), "hit_chance");
public static final NamespacedKey DODGE_CHANCE =
new NamespacedKey(MyItems.inst(), "dodge_chance");
public static final NamespacedKey BLOCK_CHANCE =
new NamespacedKey(MyItems.inst(), "block_chance");
public static final NamespacedKey VAMPIRE_CHANCE =
new NamespacedKey(MyItems.inst(), "vampire_chance");
public static final NamespacedKey CURRENT_DURABILITY =
new NamespacedKey(MyItems.inst(), "current_durability");
public static final NamespacedKey MAX_DURABILITY =
new NamespacedKey(MyItems.inst(), "max_durability");
public static final NamespacedKey PENETRATION =
new NamespacedKey(MyItems.inst(), "penetration");
public static final NamespacedKey MOVE_SPEED =
new NamespacedKey(MyItems.inst(), "move_speed");
}

View File

@@ -0,0 +1,58 @@
package com.io.yaohun.myitems.util;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DemonAPI {
public static boolean itemIsNull(ItemStack item){
return item == null || item.getType() == Material.AIR;
}
public static boolean itemIsLore(ItemStack item) {
if (item == null || !item.hasItemMeta()) {
return true;
}
List<String> lore = item.getItemMeta().getLore();
return lore == null || lore.isEmpty();
}
public static void collectYmlFiles(File dir, List<File> result) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
collectYmlFiles(file, result); // 递归子目录
} else if (file.isFile() && file.getName().endsWith(".yml")) {
result.add(file);
}
}
}
public static String convertHexColor(String text) {
Pattern pattern = Pattern.compile("#[a-fA-F0-9]{6}");
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String hexCode = text.substring(matcher.start(), matcher.end());
String replaceSharp = hexCode.replace('#', 'x');
char[] ch = replaceSharp.toCharArray();
StringBuilder builder = new StringBuilder();
for (char c : ch) {
builder.append("&").append(c);
}
text = text.replace(hexCode, builder.toString());
matcher = pattern.matcher(text);
}
return ChatColor.translateAlternateColorCodes('&', text);
}
}

View File

@@ -0,0 +1,46 @@
package com.io.yaohun.myitems.util;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class EquipmentCheckUtil {
public static boolean isValidItem(ItemStack item) {
return item != null && item.getType() != Material.AIR;
}
public static boolean isBrokenWeapon(Player player) {
if (player == null) {
return false;
}
ItemStack item = player.getInventory().getItemInMainHand();
return isBrokenEquipment(item);
}
public static boolean isBrokenEquipment(ItemStack item) {
if (!isValidItem(item)) {
return false;
}
return ItemAttributeUtil.hasDurability(item) && ItemAttributeUtil.isBroken(item);
}
public static boolean hasCustomAttributes(ItemStack item) {
if (!isValidItem(item)) {
return false;
}
return ItemAttributeUtil.hasAnyAttribute(item);
}
public static boolean isBrokenCustomWeapon(Player player) {
if (player == null) {
return false;
}
ItemStack item = player.getInventory().getItemInMainHand();
return hasCustomAttributes(item) && isBrokenEquipment(item);
}
}

View File

@@ -0,0 +1,80 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.api.EquipmentSlotType;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
public class EquipmentTypeUtil {
public static boolean matchesSlot(ItemStack item, EquipmentSlotType slotType) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return false;
}
ItemMeta meta = item.getItemMeta();
if (meta != null) {
String customSlot = meta.getPersistentDataContainer().get(
AttributeKeys.EQUIP_SLOT,
PersistentDataType.STRING
);
if (customSlot != null && !customSlot.isEmpty()) {
if ("UNIVERSAL".equalsIgnoreCase(customSlot)) {
return true;
}
return customSlot.equalsIgnoreCase(slotType.name());
}
}
return matchesVanillaMaterial(item, slotType);
}
public static boolean matchesVanillaMaterial(ItemStack item, EquipmentSlotType slotType) {
if (item == null || item.getType() == Material.AIR) {
return false;
}
Material type = item.getType();
String name = type.name();
switch (slotType) {
case MAIN_HAND, OFF_HAND:
return isWeapon(item);
case HELMET:
return name.endsWith("_HELMET")
|| name.equals("TURTLE_HELMET");
case CHESTPLATE:
return name.endsWith("_CHESTPLATE");
case LEGGINGS:
return name.endsWith("_LEGGINGS");
case BOOTS:
return name.endsWith("_BOOTS");
default:
return false;
}
}
public static boolean isWeapon(ItemStack item) {
if (item == null || item.getType() == Material.AIR) {
return false;
}
String name = item.getType().name();
return name.endsWith("_SWORD")
|| name.endsWith("_AXE")
|| name.equals("BOW")
|| name.equals("CROSSBOW")
|| name.equals("TRIDENT");
}
}

View File

@@ -0,0 +1,33 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Player;
public final class HealthUtil {
private HealthUtil() {
}
public static void applyMaxHealth(Player player, AttributeData data) {
if (player == null || data == null) return;
AttributeInstance instance = player.getAttribute(Attribute.MAX_HEALTH);
if (instance == null) return;
double bonusHealth = data.get(AttributeType.MAX_HEALTH_BONUS);
double finalHealth = 20.0 + bonusHealth;
if (finalHealth < 1.0) {
finalHealth = 1.0;
}
instance.setBaseValue(finalHealth);
if (player.getHealth() > finalHealth) {
player.setHealth(finalHealth);
}
}
}

View File

@@ -0,0 +1,241 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.builder.LoreBuilder;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import java.util.Map;
public class ItemAttributeEditor {
public static ItemStack setAttribute(ItemStack item, AttributeType type, double value) {
if (item == null || item.getType() == Material.AIR) {
return item;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
NamespacedKey key = getKey(type);
if (key == null) {
return item;
}
if (Math.abs(value) <= 0.000001D) {
pdc.remove(key);
} else {
pdc.set(key, PersistentDataType.DOUBLE, value);
}
item.setItemMeta(meta);
LoreBuilder.updateLore(item);
return item;
}
public static ItemStack addAttribute(ItemStack item, AttributeType type, double addValue) {
double oldValue = ItemAttributeUtil.readSingleAttribute(item, type);
return setAttribute(item, type, oldValue + addValue);
}
public static ItemStack removeAttribute(ItemStack item, AttributeType type) {
return setAttribute(item, type, 0.0D);
}
public static ItemStack setAttributes(ItemStack item, Map<AttributeType, Double> attributeMap) {
if (item == null || attributeMap == null || attributeMap.isEmpty()) {
return item;
}
for (Map.Entry<AttributeType, Double> entry : attributeMap.entrySet()) {
setAttribute(item, entry.getKey(), entry.getValue());
}
LoreBuilder.updateLore(item);
return item;
}
public static ItemStack clearAllAttributes(ItemStack item) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return item;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
removeIfExists(pdc, AttributeKeys.PHYSICAL_ATTACK);
removeIfExists(pdc, AttributeKeys.PHYSICAL_DEFENSE);
removeIfExists(pdc, AttributeKeys.PVE_ATTACK);
removeIfExists(pdc, AttributeKeys.PVE_ATTACK_ADDITION);
removeIfExists(pdc, AttributeKeys.PVE_DEFENSE);
removeIfExists(pdc, AttributeKeys.MAX_HEALTH_BONUS);
removeIfExists(pdc, AttributeKeys.CRIT_CHANCE);
removeIfExists(pdc, AttributeKeys.CRIT_DAMAGE);
removeIfExists(pdc, AttributeKeys.HIT_CHANCE);
removeIfExists(pdc, AttributeKeys.DODGE_CHANCE);
removeIfExists(pdc, AttributeKeys.BLOCK_CHANCE);
removeIfExists(pdc, AttributeKeys.VAMPIRE_CHANCE);
removeIfExists(pdc, AttributeKeys.PENETRATION);
removeIfExists(pdc, AttributeKeys.MOVE_SPEED);
item.setItemMeta(meta);
LoreBuilder.updateLore(item);
return item;
}
public static ItemStack setDurability(ItemStack item, int current, int max) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return item;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
if (max <= 0) {
pdc.remove(AttributeKeys.CURRENT_DURABILITY);
pdc.remove(AttributeKeys.MAX_DURABILITY);
} else {
int fixedMax = Math.max(1, max);
int fixedCurrent = Math.max(0, Math.min(current, fixedMax));
pdc.set(AttributeKeys.CURRENT_DURABILITY, PersistentDataType.INTEGER, fixedCurrent);
pdc.set(AttributeKeys.MAX_DURABILITY, PersistentDataType.INTEGER, fixedMax);
}
item.setItemMeta(meta);
// LoreBuilder.updateLore(item);
return item;
}
public static ItemStack damageDurability(ItemStack item, int damage) {
if (item == null || damage <= 0) {
return item;
}
int max = ItemAttributeUtil.getMaxDurability(item);
if (max <= 0) {
return item;
}
int current = ItemAttributeUtil.getCurrentDurability(item);
return setDurability(item, current - damage, max);
}
public static ItemStack repairDurability(ItemStack item, int amount) {
if (item == null || amount <= 0) {
return item;
}
int max = ItemAttributeUtil.getMaxDurability(item);
if (max <= 0) {
return item;
}
int current = ItemAttributeUtil.getCurrentDurability(item);
return setDurability(item, current + amount, max);
}
public static ItemStack setEquipSlot(ItemStack item, String slotName) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return item;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
meta.getPersistentDataContainer().set(
AttributeKeys.EQUIP_SLOT,
PersistentDataType.STRING,
slotName
);
item.setItemMeta(meta);
return item;
}
public static ItemStack setTemplateId(ItemStack item, String templateId) {
return setStringData(item, AttributeKeys.TEMPLATE_ID, templateId);
}
public static ItemStack setSuitId(ItemStack item, String suitId) {
return setStringData(item, AttributeKeys.SUIT_ID, suitId);
}
private static NamespacedKey getKey(AttributeType type) {
switch (type) {
case PHYSICAL_ATTACK:
return AttributeKeys.PHYSICAL_ATTACK;
case PHYSICAL_DEFENSE:
return AttributeKeys.PHYSICAL_DEFENSE;
case PVE_ATTACK:
return AttributeKeys.PVE_ATTACK;
case PVE_ATTACK_ADDITION:
return AttributeKeys.PVE_ATTACK_ADDITION;
case PVE_DEFENSE:
return AttributeKeys.PVE_DEFENSE;
case MAX_HEALTH_BONUS:
return AttributeKeys.MAX_HEALTH_BONUS;
case CRIT_CHANCE:
return AttributeKeys.CRIT_CHANCE;
case CRIT_DAMAGE:
return AttributeKeys.CRIT_DAMAGE;
case HIT_CHANCE:
return AttributeKeys.HIT_CHANCE;
case DODGE_CHANCE:
return AttributeKeys.DODGE_CHANCE;
case BLOCK_CHANCE:
return AttributeKeys.BLOCK_CHANCE;
case VAMPIRE_CHANCE:
return AttributeKeys.VAMPIRE_CHANCE;
case PENETRATION:
return AttributeKeys.PENETRATION;
case MOVE_SPEED:
return AttributeKeys.MOVE_SPEED;
default:
return null;
}
}
private static void removeIfExists(PersistentDataContainer pdc, NamespacedKey key) {
pdc.remove(key);
}
private static ItemStack setStringData(ItemStack item, NamespacedKey key, String value) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return item;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return item;
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
if (value == null || value.isEmpty()) {
pdc.remove(key);
} else {
pdc.set(key, PersistentDataType.STRING, value);
}
item.setItemMeta(meta);
return item;
}
}

View File

@@ -0,0 +1,298 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import com.io.yaohun.myitems.builder.LoreBuilder;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
public final class ItemAttributeUtil {
/**
* 读取整件装备属性
* 若装备已破损,则不提供任何属性
*/
public static AttributeData readAttributes(ItemStack item) {
AttributeData data = new AttributeData();
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return data;
}
// 破损装备属性失效
if (isBroken(item)) {
return data;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return data;
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
data.add(AttributeType.PHYSICAL_ATTACK, getDouble(pdc, AttributeKeys.PHYSICAL_ATTACK));
data.add(AttributeType.PHYSICAL_DEFENSE, getDouble(pdc, AttributeKeys.PHYSICAL_DEFENSE));
data.add(AttributeType.PVE_ATTACK, getDouble(pdc, AttributeKeys.PVE_ATTACK));
data.add(AttributeType.PVE_ATTACK_ADDITION, getDouble(pdc, AttributeKeys.PVE_ATTACK_ADDITION));
data.add(AttributeType.PVE_DEFENSE, getDouble(pdc, AttributeKeys.PVE_DEFENSE));
data.add(AttributeType.MAX_HEALTH_BONUS, getDouble(pdc, AttributeKeys.MAX_HEALTH_BONUS));
data.add(AttributeType.CRIT_CHANCE, getDouble(pdc, AttributeKeys.CRIT_CHANCE));
data.add(AttributeType.CRIT_DAMAGE, getDouble(pdc, AttributeKeys.CRIT_DAMAGE));
data.add(AttributeType.HIT_CHANCE, getDouble(pdc, AttributeKeys.HIT_CHANCE));
data.add(AttributeType.DODGE_CHANCE, getDouble(pdc, AttributeKeys.DODGE_CHANCE));
data.add(AttributeType.BLOCK_CHANCE, getDouble(pdc, AttributeKeys.BLOCK_CHANCE));
data.add(AttributeType.VAMPIRE_CHANCE, getDouble(pdc, AttributeKeys.VAMPIRE_CHANCE));
data.add(AttributeType.PENETRATION, getDouble(pdc, AttributeKeys.PENETRATION));
data.add(AttributeType.MOVE_SPEED, getDouble(pdc, AttributeKeys.MOVE_SPEED));
return data;
}
/**
* 读取单个属性
* 若装备已破损,则属性视为 0
*/
public static double readSingleAttribute(ItemStack item, AttributeType type) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return 0.0D;
}
if (isBroken(item)) {
return 0.0D;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return 0.0D;
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
switch (type) {
case PHYSICAL_ATTACK:
return getDouble(pdc, AttributeKeys.PHYSICAL_ATTACK);
case PHYSICAL_DEFENSE:
return getDouble(pdc, AttributeKeys.PHYSICAL_DEFENSE);
case PVE_ATTACK:
return getDouble(pdc, AttributeKeys.PVE_ATTACK);
case PVE_ATTACK_ADDITION:
return getDouble(pdc, AttributeKeys.PVE_ATTACK_ADDITION);
case PVE_DEFENSE:
return getDouble(pdc, AttributeKeys.PVE_DEFENSE);
case MAX_HEALTH_BONUS:
return getDouble(pdc, AttributeKeys.MAX_HEALTH_BONUS);
case CRIT_CHANCE:
return getDouble(pdc, AttributeKeys.CRIT_CHANCE);
case CRIT_DAMAGE:
return getDouble(pdc, AttributeKeys.CRIT_DAMAGE);
case HIT_CHANCE:
return getDouble(pdc, AttributeKeys.HIT_CHANCE);
case DODGE_CHANCE:
return getDouble(pdc, AttributeKeys.DODGE_CHANCE);
case BLOCK_CHANCE:
return getDouble(pdc, AttributeKeys.BLOCK_CHANCE);
case VAMPIRE_CHANCE:
return getDouble(pdc, AttributeKeys.VAMPIRE_CHANCE);
case PENETRATION:
return getDouble(pdc, AttributeKeys.PENETRATION);
case MOVE_SPEED:
return getDouble(pdc, AttributeKeys.MOVE_SPEED);
case EQUIPMENT_DURABILITY:
return getMaxDurability(item);
default:
return 0.0D;
}
}
public static boolean hasDurability(ItemStack item) {
return getMaxDurability(item) > 0;
}
public static int getCurrentDurability(ItemStack item) {
if (item == null || !item.hasItemMeta()) return 0;
ItemMeta meta = item.getItemMeta();
if (meta == null) return 0;
return meta.getPersistentDataContainer().getOrDefault(
AttributeKeys.CURRENT_DURABILITY,
PersistentDataType.INTEGER,
0
);
}
public static int getMaxDurability(ItemStack item) {
if (item == null || !item.hasItemMeta()) return 0;
ItemMeta meta = item.getItemMeta();
if (meta == null) return 0;
return meta.getPersistentDataContainer().getOrDefault(
AttributeKeys.MAX_DURABILITY,
PersistentDataType.INTEGER,
0
);
}
public static void setCurrentDurability(ItemStack item, int value) {
if (item == null || !item.hasItemMeta()) return;
ItemMeta meta = item.getItemMeta();
if (meta == null) return;
meta.getPersistentDataContainer().set(
AttributeKeys.CURRENT_DURABILITY,
PersistentDataType.INTEGER,
Math.max(0, value)
);
item.setItemMeta(meta);
}
public static void setMaxDurability(ItemStack item, int value) {
if (item == null || !item.hasItemMeta()) return;
ItemMeta meta = item.getItemMeta();
if (meta == null) return;
meta.getPersistentDataContainer().set(
AttributeKeys.MAX_DURABILITY,
PersistentDataType.INTEGER,
Math.max(0, value)
);
item.setItemMeta(meta);
}
public static void setDurability(ItemStack item, int current, int max) {
if(DemonAPI.itemIsNull(item) || DemonAPI.itemIsLore(item)){
return;
}
ItemMeta meta = item.getItemMeta();
PersistentDataContainer pdc = meta.getPersistentDataContainer();
if (max <= 0) {
pdc.remove(AttributeKeys.CURRENT_DURABILITY);
pdc.remove(AttributeKeys.MAX_DURABILITY);
} else {
int fixedMax = Math.max(1, max);
int fixedCurrent = Math.max(0, Math.min(current, fixedMax));
pdc.set(AttributeKeys.CURRENT_DURABILITY, PersistentDataType.INTEGER, fixedCurrent);
pdc.set(AttributeKeys.MAX_DURABILITY, PersistentDataType.INTEGER, fixedMax);
}
item.setItemMeta(meta);
LoreBuilder.updateDurabilityAttributeLine(item);
}
public static void damageDurability(ItemStack item, int damage) {
if (item == null || damage <= 0) {
return;
}
int max = getMaxDurability(item);
if (max <= 0) {
return;
}
int current = getCurrentDurability(item);
setDurability(item, current - damage, max);
}
public static void repairDurability(ItemStack item, int amount) {
if (item == null || amount <= 0) {
return;
}
int max = getMaxDurability(item);
if (max <= 0) {
return;
}
int current = getCurrentDurability(item);
setDurability(item, current + amount, max);
}
public static boolean isBroken(ItemStack item) {
int max = getMaxDurability(item);
if (max <= 0) {
return false;
}
int current = getCurrentDurability(item);
return current <= 0;
}
public static String readTemplateId(ItemStack item) {
return readString(item, AttributeKeys.TEMPLATE_ID);
}
public static String readSuitId(ItemStack item) {
return readString(item, AttributeKeys.SUIT_ID);
}
public static boolean hasAnyAttribute(ItemStack item) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return false;
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return false;
}
PersistentDataContainer pdc = meta.getPersistentDataContainer();
return has(pdc, AttributeKeys.PHYSICAL_ATTACK)
|| has(pdc, AttributeKeys.PHYSICAL_DEFENSE)
|| has(pdc, AttributeKeys.PVE_ATTACK)
|| has(pdc, AttributeKeys.PVE_ATTACK_ADDITION)
|| has(pdc, AttributeKeys.PVE_DEFENSE)
|| has(pdc, AttributeKeys.MAX_HEALTH_BONUS)
|| has(pdc, AttributeKeys.CRIT_CHANCE)
|| has(pdc, AttributeKeys.CRIT_DAMAGE)
|| has(pdc, AttributeKeys.HIT_CHANCE)
|| has(pdc, AttributeKeys.DODGE_CHANCE)
|| has(pdc, AttributeKeys.BLOCK_CHANCE)
|| has(pdc, AttributeKeys.VAMPIRE_CHANCE)
|| has(pdc, AttributeKeys.PENETRATION)
|| has(pdc, AttributeKeys.MOVE_SPEED);
}
private static boolean has(PersistentDataContainer pdc, NamespacedKey key) {
return pdc.has(key, PersistentDataType.DOUBLE) || pdc.has(key, PersistentDataType.INTEGER);
}
private static double getDouble(PersistentDataContainer pdc, NamespacedKey key) {
if (pdc.has(key, PersistentDataType.DOUBLE)) {
return pdc.getOrDefault(key, PersistentDataType.DOUBLE, 0.0D);
}
if (pdc.has(key, PersistentDataType.INTEGER)) {
return pdc.getOrDefault(key, PersistentDataType.INTEGER, 0);
}
return 0.0D;
}
private static String readString(ItemStack item, NamespacedKey key) {
if (item == null || item.getType() == Material.AIR || !item.hasItemMeta()) {
return "";
}
ItemMeta meta = item.getItemMeta();
if (meta == null) {
return "";
}
return meta.getPersistentDataContainer().getOrDefault(
key,
PersistentDataType.STRING,
""
);
}
}

View File

@@ -0,0 +1,71 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.MyItems;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import java.io.File;
import java.util.HashMap;
public class MessageUtil {
private static HashMap<String,String> messageMap = new HashMap<>();
public static void init(MyItems plugin) {
messageMap.clear();
File file = new File(plugin.getDataFolder(),"lang.yml");
// 如果没有这个文件就新建一个~
if(!file.exists()){
plugin.saveResource("lang.yml",false);
}
FileConfiguration yml = YamlConfiguration.loadConfiguration(file);
for (String ymlKey : yml.getKeys(false)){
ConfigurationSection section = yml.getConfigurationSection(ymlKey);
for (String key : section.getKeys(false)){
String message = DemonAPI.convertHexColor(section.getString(key));
messageMap.put(key,message.replace("&","§"));
}
}
}
public static String getMessage(String key){
if(messageMap.containsKey(key)){
return messageMap.get(key);
}
return "§c未找到节点.§7#"+key;
}
public static void broadcast(String msg) {
Bukkit.getServer().broadcastMessage( msg.replace("&","§"));
}
public static void sendMessage(CommandSender sender, String message){
sender.sendMessage(message);
}
public static void sendMessageKey(CommandSender sender, String key, Sound sound){
sender.sendMessage(getMessage(key));
if(sender instanceof Player player){
player.playSound(player.getLocation(),sound,1,1);
}
}
public static void sendMessage(CommandSender sender, String message, Sound sound){
sender.sendMessage(message);
if(sender instanceof Player player){
player.playSound(player.getLocation(),sound,1,1);
}
}
public static void sendDescMessage(CommandSender sender, String message, Sound sound){
sender.sendMessage("§f[§c消息§f] §a正确用法: §e"+message);
if(sender instanceof Player player){
player.playSound(player.getLocation(),sound,1,1);
}
}
}

View File

@@ -0,0 +1,83 @@
package com.io.yaohun.myitems.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class RandomBuilder<T> {
private Map<T, Double> valueMap;
private List<Element> elementList = new ArrayList<>();
private double maxElement = 0;
public RandomBuilder(Map<T, Double> map) {
valueMap = map;
double minElement = 0;
for (Map.Entry<T, Double> entry : map.entrySet()) {
minElement = maxElement;
maxElement += entry.getValue();
elementList.add(new Element(entry.getKey(), minElement, maxElement));
}
}
public T random() {
return random(new Random());
}
public T random(Random random) {
double d = random.nextDouble() * maxElement;
if (d == 0) {
d = 0.1 * maxElement;
}
int size = valueMap.size();
for (int i = 0; i < elementList.size(); i++) {
Element element = elementList.get(i);
if (element.isContainKey(d)) {
return element.getValue();
}
}
return null;
}
public double getMaxElement() {
return maxElement;
}
public List<Element> getElementList() {
return elementList;
}
public Map<T, Double> getValueMap() {
return valueMap;
}
private class Element {
private T value;
private final double minElement;
private final double maxElement;
public Element(T value, double element0, double element1) {
this.value = value;
this.minElement = Math.min(element0, element1);
this.maxElement = Math.max(element0, element1);
}
public T getValue() {
return value;
}
public boolean isContainKey(double element) {
return element > minElement && element <= maxElement;
}
}
}

View File

@@ -0,0 +1,74 @@
package com.io.yaohun.myitems.util;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
public class RandomUtil {
public static double round(double d, int scale) {
return new BigDecimal(Double.toString(d)).setScale(scale, RoundingMode.HALF_UP).doubleValue();
}
public static boolean random(double d) {
return d >= new Random().nextFloat();
}
public static int getRandomInt(int min, int max) {
if (min == max) {
return max;
}
Random r = new Random();
int i = min < max ? min : max;
int a = min < max ? max : min;
return r.nextInt(a - i + 1) + i;
}
public static double getRandomDouble(double min, double max, int scl) {
int pow = (int) Math.pow(10, scl);
return Math.floor((Math.random() * (max - min) + min) * pow) / pow;
}
public static Location getRandomLocation(World world, double minX, double maxX, double minY, double maxY, double minZ, double maxZ) {
double rx = getRandomDouble(minX, maxX, 2);
double ry = getRandomDouble(minY, maxY, 2);
double rz = getRandomDouble(minZ, maxZ, 2);
return new Location(world, rx, ry, rz);
}
public static Location getRandomLocation(Player player, double minX, double maxX, double minY, double maxY, double minZ, double maxZ) {
World world = player.getWorld();
Location playerLocation = player.getLocation();
Location location = playerLocation.add(getRandomInt(-3,3),0,getRandomInt(-3,3));
for (int i = 0; i < 10;i++) {
Location loc = new Location(world, getRandomDouble(minX, maxX, 3), getRandomDouble(minY, maxY, 3), getRandomDouble(minZ, maxZ, 3));
if (loc.distance(playerLocation) <= 50) {
location = loc;
break;
}
}
return location;
}
public static Location getPlayerRandomLocation(Player player) {
Location location = player.getLocation().clone();
double rx = RandomUtil.getRandomDouble(-3, 3, 3);
double ry = RandomUtil.getRandomDouble(0, 2, 3);
double rz = RandomUtil.getRandomDouble(-3, 3, 3);
location.add(rx, ry, rz);
return location;
}
public static Location getPlayerRandomLocation(Player player,int range) {
Location location = player.getLocation().clone();
double rx = RandomUtil.getRandomDouble(-range, range, 3);
double ry = RandomUtil.getRandomDouble(0, 2, 3);
double rz = RandomUtil.getRandomDouble(-range, range, 3);
location.add(rx, ry, rz);
return location;
}
}

View File

@@ -0,0 +1,37 @@
package com.io.yaohun.myitems.util;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class RepairUtil {
public static boolean repairToFull(ItemStack item) {
if (item == null) {
return false;
}
int max = ItemAttributeUtil.getMaxDurability(item);
if (max <= 0) {
return false;
}
ItemAttributeUtil.setDurability(item, max, max);
return true;
}
public static int repairAll(Player player) {
if (player == null) {
return 0;
}
int repaired = 0;
if (repairToFull(player.getInventory().getItemInMainHand())) repaired++;
if (repairToFull(player.getInventory().getItemInOffHand())) repaired++;
if (repairToFull(player.getInventory().getHelmet())) repaired++;
if (repairToFull(player.getInventory().getChestplate())) repaired++;
if (repairToFull(player.getInventory().getLeggings())) repaired++;
if (repairToFull(player.getInventory().getBoots())) repaired++;
return repaired;
}
}

View File

@@ -0,0 +1,37 @@
package com.io.yaohun.myitems.util;
import com.io.yaohun.myitems.api.AttributeData;
import com.io.yaohun.myitems.api.AttributeType;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Player;
public class SpeedUtil {
public static void applyMoveSpeed(Player player, AttributeData data) {
if (player == null || data == null) return;
AttributeInstance instance = player.getAttribute(Attribute.MOVEMENT_SPEED);
if (instance == null) return;
double bonusPercent = data.get(AttributeType.MOVE_SPEED);
// 原版默认移动速度大约是 0.1
double base = 0.1D;
double finalSpeed = base * (1.0D + bonusPercent / 100.0D);
// 防止异常速度
finalSpeed = Math.max(0.01D, Math.min(finalSpeed, 0.5D));
instance.setBaseValue(finalSpeed);
}
public static void reset(Player player) {
if (player == null) return;
AttributeInstance instance = player.getAttribute(Attribute.MOVEMENT_SPEED);
if (instance == null) return;
instance.setBaseValue(0.1D);
}
}

View File

@@ -0,0 +1,15 @@
xygjx:
ItemStack: default
Material: DIAMOND_SWORD
Display_Name: "§7[{tier_prefix}{tier_name}§7] §f铁镐"
Lore:
- "§7耐久§f§l: §c{now}§7/§a{max}"
- "§a✔§f从稀有工具箱I获得"
Equip_Slot: MAIN_HAND
Tier_Types:
UNCOMMON:
Chance: 10.0
Current_Durability: 40
Max_Durability: 40
Attributes:
PHYSICAL_ATTACK: 100

View File

@@ -0,0 +1,7 @@
hub-spawn:
world: "world"
x: 8.5
y: 61.0
z: 8.5
yaw: 0.0
pitch: 0.0

View File

@@ -0,0 +1,40 @@
Stats:
PHYSICAL_ATTACK: " #CF0000ꖋ 攻击伤害 +"
PHYSICAL_DEFENSE: " #FF9F00ꖋ 物理防御 +"
PVE_ATTACK: " #8ba2baꖋ PvE伤害 +"
PVE_DEFENSE: " #8ba2baꖋ PvE护甲 +"
MAX_HEALTH_BONUS: " #00B836ꖋ 生命增加 +"
CRIT_CHANCE: " #8ba2baꖋ 暴击概率 +"
CRIT_DAMAGE: " #8ba2baꖋ 暴击伤害 +"
HIT_CHANCE: " #8ba2baꖋ 命中概率 +"
DODGE_CHANCE: " #8ba2baꖋ 闪避概率 +"
BLOCK_CHANCE: " #8ba2baꖋ 格挡概率 +"
VAMPIRE_CHANCE: " #CF0000ꖋ 吸血概率 +"
PENETRATION: " #8ba2baꖋ 护甲穿透 +"
MOVE_SPEED: " #00B836ꖋ 移速加成 +"
EQUIPMENT_DURABILITY: " #A3A3A3ꖋ 耐久"
Att_Int_Format: "0"
Att_Double_Format: "0.##"
MyItems:
Stats_Success: "§f[§6属性§f] §7属性&c{stats}&7已被修改为: &e{value}"
Slot_Success: "§f[§6属性§f] §7手持物品的槽位已经修改为: &e{value}"
Durability_Success: "§f[§6属性§f] §7当前物品耐久已设置为: &c{now}&7/&e{max}"
Clear_Success: "§f[§6属性§f] §a成功删除手中物品所有属性."
Repair_Success: "§f[§6战争领域§f] §a你手中物品耐久已修复."
Repair_All_Success: "§f[§6战争领域§f] §a背包中所有物品耐久已修复."
Item:
MainHand_Empty: "§f[§6属性§f] §c你的主手没有物品."
Broken_MainHand: "&7[&6战争领域&7] &f你手上的物品耐久值已用尽,请到主城修复,否则无法使用!"
Broken_Offhand: "&7[&6战争领域&7] &f你副手的物品耐久值已用尽,请到主城修复,否则无法使用!"
Broken_Helmet: "&7[&6战争领域&7] &f你的帽子已经穿烂了!"
Broken_Chestplate: "&7[&6战争领域&7] &f你的衣服已经穿烂了!"
Broken_Leggings: "&7[&6战争领域&7] &f你的裤子已经穿烂了!"
Broken_Boots: "&7[&6战争领域&7] &f你的鞋子已经穿烂了!"
Durability_Not_Exist: "§f[§6战争领域§f] §c该物品不支持耐久修复."
Durability_Not_Amount: "§f[§6战争领域§f] §c背包中没有物品可修复."
Argument:
Invalid_Value: "§f[§6属性§f] §c无效的数值."
Invalid_SlotType: "§f[§6属性§f] §c无效的槽位类型."

View File

@@ -0,0 +1,12 @@
name: AuMyItems
main: com.io.yaohun.myitems.MyItems
version: 1.1.3
api-version: 1.21
authors: [YaoHunYa]
depend:
- CraftEngine
commands:
att:
repair:
myitems:
aliases: [mi]

347
使用案例.md Normal file
View File

@@ -0,0 +1,347 @@
# AuQuestEngine
AuQuestEngine 是一个面向 Paper / Spigot 服务端的任务系统插件,用于通过配置文件定义任务、任务目标、接取条件、完成提示和奖励命令。
当前插件版本:`1.0.6`
## 1. 功能概览
- 支持每日、每周、每月、长期任务。
- 支持 YAML、SQLite、MySQL 三种玩家任务数据存储方式。
- 支持任务接取条件:玩家等级、前置已完成任务、指定世界。
- 支持多种 Bukkit 事件任务目标,例如破坏方块、放置方块、合成、拾取、击杀、钓鱼、村民交易、打开菜单等。
- 支持 PlaceholderAPI 变量。
- 支持 Citizens、MythicMobs、CustomFishing、CustomCrops 等前置插件的扩展任务监听。
- 支持任务完成后执行控制台奖励命令。
## 2. 运行环境
```text
Java: 21
服务端: Paper 1.21.11
构建工具: Maven
```
前置插件:
```yml
softdepend:
- Citizens
- MythicMobs
- PlaceholderAPI
- CustomFishing
- CustomCrops
```
这些前置不是全部必装。未安装对应插件时,相关任务监听器不会注册。
## 3. 安装方式
构建插件:
```bash
mvn package
```
构建完成后,将生成的插件包放入服务端:
```text
plugins/AuQuestEngine-1.0-SNAPSHOT.jar
```
启动服务端后,插件会生成基础配置文件。任务文件需要放在:
```text
plugins/AuQuestEngine/Quests/
```
## 4. 配置文件
`config.yml` 用于配置存储方式和部分任务类型识别列表。
```yml
Storage:
Type: yaml # yaml / sqlite / mysql
SQLite:
File: PlayerData.db
SaveInterval: 10
MySQL:
Host: localhost
Port: 3306
Database: quest
Username: root
Password: root
TablePrefix: AuQuest_
Pool:
MaximumPoolSize: 10
MinimumIdle: 2
ConnectionTimeout: 10000
IdleTimeout: 600000
MaxLifetime: 1800000
```
存储说明:
- `yaml`:玩家数据保存为单独 YAML 文件,适合小型服务器。
- `sqlite`:玩家数据保存到本地 SQLite 数据库。
- `mysql`:玩家数据保存到 MySQL适合多实例或长期运营环境。
插件包已包含 SQLite JDBC、MySQL Connector/J 和 HikariCP。
## 5. 任务文件格式
任务文件可以放在 `plugins/AuQuestEngine/Quests/` 目录下,文件名不限,后缀必须是 `.yml`
示例:
```yml
quest_1:
display_name: "&a[初级] 新手成长之路"
description:
- "&7完成基础生存挑战任务"
- "&7砍伐指定木材熟悉资源收集"
type: DAILY
messages:
receive:
- "&a你接受了任务&f新手成长之路"
complete:
- "&a任务完成&f新手成长之路"
- "&7奖励已发放请继续成长。"
conditions:
level: 0
completed_quests: []
worlds: []
tasks:
id_1:
type: BREAK_BLOCK
display_name: "砍伐云杉木 * 128"
amount: 128
targets:
- SPRUCE_LOG
- SPRUCE_WOOD
- STRIPPED_SPRUCE_LOG
- STRIPPED_SPRUCE_WOOD
rewards:
commands:
- "dlevel give %player% 12000"
- "points give %player% 5"
messages:
- "&a{name} 任务已完成"
```
字段说明:
- `display_name`:任务显示名称。
- `description`:任务描述。
- `type`:任务类型,可用 `DAILY``WEEKLY``MONTHLY``FIXED`
- `messages.receive`:接取任务后发送给玩家的消息。
- `messages.complete`:完成任务后发送给玩家的消息。
- `conditions.level`:最低等级要求。
- `conditions.completed_quests`:前置已完成任务 ID 列表。
- `conditions.worlds`:允许推进任务的世界列表,留空表示不限制。
- `tasks`:任务目标列表。
- `rewards.commands`:完成后由控制台执行的命令,使用 `%player%` 替换玩家名。
- `rewards.messages`:奖励提示,支持 `{name}` 替换任务名称。
## 6. 任务类型
常用任务类型:
```text
BREAK_BLOCK 破坏方块
PLACE_BLOCK 放置方块
CRAFT_ITEM 合成物品
PICKUP_ITEM 拾取物品
FISH_ITEM 钓鱼获得物品
FISH_STAR 钓起鱼的星级
PLACE_BUCKET 倒水、倒岩浆、放置粉雪
FILL_BUCKET 装水、装岩浆、装牛奶
HARVEST_CROP 收获农作物
EXP_GAIN 获取经验
BREED 繁殖动物
COLLECT_ENTITY 剪羊毛、取蘑菇煲
COLLECT_BLOCK 收集蜂蜜
TAME_ENTITY 驯服实体
VILLAGER_TRADE_ITEM 村民交易物品
VILLAGER_TRADE_JOB 村民职业交易
BREW_POTION 酿造药水
BREW_POTION_COUNT 酿造药水次数
USE_ANVIL 使用铁砧
ENCHANT_ITEM 附魔物品
FEED_ANIMAL 喂动物
FEED_PLAYER 玩家食用物品
SMELT_ORE 烧制矿物
SMELT_FOOD 烧制食物
INTERACT_NPC 交互 Citizens NPC
INTERACT_ENTITY 交互实体
INTERACT_ITEM 交互手持物品
SEND_COMMAND 发送命令
SEND_CHAT 发送聊天
OPEN_GUI_TITLE 打开指定标题菜单
OPEN_GUI_TYPE 打开指定类型菜单
KILL_TYPE 击杀原版实体
KILL_MYTHIC 击杀 MythicMobs 实体
```
预留类型存在于枚举中,但未必已有完整监听实现。配置任务前应确认对应监听器已实现。
## 7. 玩家命令
查看任务信息:
```text
/aquest info
```
管理员查看指定在线玩家任务信息:
```text
/aquest info <玩家名>
```
给玩家发放任务:
```text
/aquest js <任务ID> <玩家名>
```
OP 玩家直接完成自身任务:
```text
/aquest direc <任务ID>
```
## 8. 管理命令
管理命令需要权限:
```text
admin.use
```
重载配置、语言和任务:
```text
/aquestadmin reload
```
开关调试输出:
```text
/aquestadmin debug
```
清空指定玩家任务数据:
```text
/aquestadmin clear <玩家名>
```
SQLite 数据导出为 YAML
```text
/aquestadmin outsqlite
```
## 9. PlaceholderAPI 变量
插件标识:
```text
aquest
```
任务名称:
```text
%aquest_quest_<任务ID>_name%
```
任务是否完成:
```text
%aquest_quest_<任务ID>_completed%
```
任务总进度:
```text
%aquest_quest_<任务ID>_progress%
```
指定任务目标进度:
```text
%aquest_task_<任务ID>_<任务目标ID>%
```
示例:
```text
%aquest_quest_quest_1_name%
%aquest_quest_quest_1_progress%
%aquest_task_quest_1_id_1%
```
## 10. API 调用
其他插件可以通过 `AuQuestAPI` 查询或操作玩家任务。
```java
UUID uuid = player.getUniqueId();
boolean started = AuQuestAPI.startQuest(uuid, "quest_1");
boolean completed = AuQuestAPI.hasCompletedQuest(uuid, "quest_1");
boolean active = AuQuestAPI.hasActiveQuest(uuid, "quest_1");
List<String> activeQuests = AuQuestAPI.getActiveQuests(uuid);
List<String> completedQuests = AuQuestAPI.getCompletedQuests(uuid);
AuQuestAPI.directCompleteQuest(uuid, "quest_1");
AuQuestAPI.abandonQuest(uuid, "quest_1");
```
常用方法:
- `getQuestDisplayName(String questId)`
- `getQuestDescription(String questId)`
- `getQuestType(String questId)`
- `hasCompletedQuest(UUID uuid, String questId)`
- `hasActiveQuest(UUID uuid, String questId)`
- `getActiveQuests(UUID uuid)`
- `getCompletedQuests(UUID uuid)`
- `startQuest(UUID uuid, String questId)`
- `abandonQuest(UUID uuid, String questId)`
- `directCompleteQuest(UUID uuid, String questId)`
## 11. 自动刷新规则
任务刷新按任务类型处理:
- `DAILY`:每日 00:00 后刷新。
- `WEEKLY`:每周一 00:00 后刷新。
- `MONTHLY`:每月 1 日 00:00 后刷新。
- `FIXED`:长期任务,不参与周期刷新。
刷新时会清理对应类型任务的历史进度,并同步在线玩家缓存。
## 12. 使用流程
1. 将插件 jar 放入服务端 `plugins` 目录。
2. 启动服务端生成配置文件。
3. 根据需要选择 `yaml``sqlite``mysql` 存储。
4.`plugins/AuQuestEngine/Quests/` 下创建任务配置文件。
5. 执行 `/aquestadmin reload` 重载任务。
6. 使用 `/aquest js <任务ID> <玩家名>` 给玩家发放任务。
7. 玩家完成目标后自动累计进度。
8. 任务完成后自动发送完成消息并执行奖励命令。
## 13. 注意事项
- 修改任务文件后需要执行 `/aquestadmin reload`
- 数据库模式切换前应先备份旧数据。
- MySQL 模式需要确认数据库、账号和权限正确。
- CustomFishing、CustomCrops、Citizens、MythicMobs 未安装时,对应任务类型不会生效。
- 任务目标 `targets` 必须与监听器传入的目标字符串一致,原版方块和物品通常使用 Bukkit 枚举名。
- 任务奖励命令由控制台执行,配置前应确认命令不会被重复领取滥用。
- 发布新版本前应确认 `plugin.yml` 中版本号已递增。

View File

@@ -0,0 +1,458 @@
## AuMyItems 投入使用测试流程
生成时间2026-07-08 05:05:00
### 1. 测试目标
确认插件满足以下条件后,才允许投入使用:
- 插件可完成 Maven 构建。
- 服务端可正常启动并加载插件。
- CraftEngine 依赖可被服务端识别。
- 物品模板可加载。
- 属性写入、读取、展示、生效链路正常。
- 穿透、格挡、吸血语义符合当前设计。
- 耐久扣减、破损失效、修复逻辑正常。
- reload 不产生异常或旧数据残留。
### 2. 测试环境要求
- Java21
- 服务端Paper 1.21.11
- 必装依赖CraftEngine 26.5.1
- 插件版本1.0.7
- 测试账号:至少 2 个玩家账号
- 推荐准备:
- 一个测试平坦世界
- 一个普通怪物生成区
- 一个 PVP 测试区域
- 控制台日志实时可见
### 3. 构建验证
#### 3.1 执行构建
命令:
```bash
mvn -q -DskipTests package
```
通过标准:
- 命令退出码为 0。
- 生成插件 jar。
- 控制台无编译错误。
失败处理:
- 如果 CraftEngine 依赖解析失败,先检查 Maven 仓库配置。
- 如果 Maven 插件下载失败,先处理本机网络或 Maven 缓存。
- 构建失败不得进入服务端测试。
#### 3.2 检查插件描述文件
检查 jar 内 `plugin.yml`
- `version` 必须是 `1.0.7`
- `depend` 必须包含 `CraftEngine`
- `main` 必须指向主类。
通过标准:
- jar 内版本号与发布版本一致。
- 没有连续构建沿用旧版本号。
#### 3.3 检查依赖打包
检查 jar 内容:
- 不得包含 `org/sqlite/**`
- 不得包含 `com/zaxxer/hikari/**`
- 不得包含 `net/momirealms/craftengine/**`
通过标准:
- 上述类路径均不存在。
### 4. 服务端启动验证
#### 4.1 安装插件
将以下插件放入服务端插件目录:
- CraftEngine
- AuMyItems
启动服务端。
通过标准:
- 控制台无红色报错。
- AuMyItems 正常启用。
- CraftEngine 在 AuMyItems 前完成加载。
- 控制台出现物品模板加载数量日志。
失败处理:
- 若提示缺少 CraftEngine检查依赖插件文件名、版本和加载顺序。
- 若模板加载报错,记录具体物品 ID 和原因。
### 5. 配置加载验证
#### 5.1 默认配置生成
删除测试服中的旧插件数据目录后启动一次。
通过标准:
- 默认配置文件生成。
- 默认语言文件生成。
- 默认物品模板生成。
- 无启动异常。
#### 5.2 旧配置兼容
使用旧版本配置启动。
通过标准:
- 缺失新配置不会导致插件无法启动。
- 模板错误只跳过单个物品,不影响其他模板加载。
- 控制台能明确提示错误物品和原因。
### 6. 命令验证
#### 6.1 权限验证
无权限玩家执行:
```text
/att
/repair repair
/myitems reload
```
通过标准:
- 无权限玩家不能执行管理功能。
- 控制台无异常。
#### 6.2 属性写入验证
管理员手持普通物品,依次执行:
```text
/att stats PHYSICAL_ATTACK 100
/att stats PHYSICAL_DEFENSE 50
/att stats PVE_ATTACK 30
/att stats PVE_DEFENSE 20
/att stats MAX_HEALTH_BONUS 40
/att stats CRIT_CHANCE 100
/att stats CRIT_DAMAGE 100
/att stats HIT_CHANCE 100
/att stats DODGE_CHANCE 100
/att stats PENETRATION 100
/att stats BLOCK_CHANCE 100
/att stats VAMPIRE_CHANCE 100
/att stats MOVE_SPEED 50
```
通过标准:
- 每条命令返回成功提示。
- 物品 Lore 显示对应属性。
- 不出现“未找到节点”文案。
- 控制台无异常。
#### 6.3 槽位写入验证
执行:
```text
/att slot MAIN_HAND
/att slot OFF_HAND
/att slot HELMET
/att slot CHESTPLATE
/att slot LEGGINGS
/att slot BOOTS
```
通过标准:
- 每个合法槽位都能写入。
- 物品放入对应槽位时属性生效。
- 放入不匹配槽位时属性不生效。
#### 6.4 耐久写入验证
执行:
```text
/att durability 10 10
```
通过标准:
- Lore 显示耐久。
- 当前耐久和最大耐久一致。
- 无异常。
### 7. 物品模板验证
#### 7.1 重载模板
执行:
```text
/myitems reload
```
通过标准:
- 控制台显示模板加载数量。
- 坏模板只跳过自身。
- 好模板仍可生成。
#### 7.2 生成模板物品
执行:
```text
/myitems give xygjx
```
通过标准:
- 玩家获得物品。
- 名称、Lore、品质、属性、耐久均正确。
- 单值属性配置可正常解析。
- 区间属性配置可正常随机。
### 8. 属性面板验证
执行:
```text
/myitems gui
```
通过标准:
- GUI 可打开。
- 攻击属性显示:
- 物理攻击
- PVE 攻击
- 暴击概率
- 暴击伤害
- 命中几率
- 护甲穿透
- 吸血几率
- 防御属性显示:
- 物理防御
- PVE 防御
- 生命提升
- 闪避几率
- 格挡几率
- 移速加成
- 点击 GUI 不会取出展示物品。
### 9. 战斗属性验证
#### 9.1 基础伤害
准备:
- 攻击者主手武器设置 `PHYSICAL_ATTACK 100`
- 目标不穿任何装备
操作:
- 攻击目标。
通过标准:
- 伤害明显高于无属性武器。
- ActionBar 显示伤害数字。
#### 9.2 防御减伤
准备:
- 目标装备设置 `PHYSICAL_DEFENSE 1000`
- 攻击者武器设置 `PHYSICAL_ATTACK 100`
通过标准:
- 目标受到伤害低于无防御状态。
#### 9.3 穿透触发
准备:
- 攻击者武器设置 `PENETRATION 100`
- 目标装备设置高额 `PHYSICAL_DEFENSE`
操作:
- 攻击目标多次。
通过标准:
- 每次都应触发穿透。
- ActionBar 出现穿透提示。
- 伤害应接近无防御状态,而不是按防御减伤。
#### 9.4 格挡触发
准备:
- 目标装备设置 `BLOCK_CHANCE 100`
- 攻击者武器设置稳定攻击属性。
操作:
- 攻击目标。
通过标准:
- 每次都应触发格挡。
- 目标 ActionBar 出现格挡提示。
- 目标受到最终伤害降低约 50%。
- 攻击者受到反伤。
- 反伤不会造成无限递归触发。
#### 9.5 吸血触发
准备:
- 攻击者武器设置 `VAMPIRE_CHANCE 100`
- 攻击者先损失生命值。
操作:
- 攻击目标。
通过标准:
- 每次都应触发吸血。
- ActionBar 出现吸血提示。
- 攻击者生命恢复。
- 恢复量不超过最大生命值。
#### 9.6 暴击触发
准备:
- 攻击者武器设置 `CRIT_CHANCE 100`
- 攻击者武器设置 `CRIT_DAMAGE 100`
通过标准:
- 每次攻击都显示暴击。
- 最终伤害高于非暴击伤害。
#### 9.7 闪避触发
准备:
- 目标装备设置 `DODGE_CHANCE 100`
- 攻击者不设置额外命中。
通过标准:
- 目标应频繁或稳定闪避,具体取决于命中公式钳制。
- 闪避时事件取消,伤害为 0。
#### 9.8 PVE 属性
准备:
- 攻击者武器设置 `PVE_ATTACK 100`
- 怪物装备或实体属性设置 `PVE_DEFENSE`
通过标准:
- 玩家攻击怪物时 PVE 攻击参与计算。
- 玩家攻击玩家时不应使用 PVE 攻击加成。
### 10. 耐久系统验证
#### 10.1 攻击扣耐久
准备:
- 主手武器设置耐久 `3/3`
操作:
- 攻击实体 3 次。
通过标准:
- 每次攻击后耐久减少。
- 耐久归零后发送破损提示。
- 破损后该装备属性失效。
#### 10.2 受击扣护甲耐久
准备:
- 护甲设置耐久 `3/3`
操作:
- 让玩家受到伤害 3 次。
通过标准:
- 护甲耐久减少。
- 耐久归零后发送对应部位破损提示。
- 破损后该装备属性失效。
#### 10.3 工具挖掘扣耐久
准备:
- 镐或斧设置耐久 `3/3`
操作:
- 挖掘方块 3 次。
通过标准:
- 工具耐久减少。
- 耐久归零后阻止继续使用。
#### 10.4 修复命令
执行:
```text
/repair repair
/repair repairall
/repair repair <玩家名>
/repair repairall <玩家名>
```
通过标准:
- 目标玩家为空时给出提示,不报错。
- 指定玩家时修复目标玩家物品。
- 修复后属性刷新。
### 11. 重载与缓存验证
操作:
1. 穿戴属性装备。
2. 打开属性面板记录属性。
3. 执行 `/myitems reload`
4. 切换装备或重新打开面板。
通过标准:
- reload 不报错。
- 已穿戴装备属性不丢失。
- 新模板配置生效。
- 玩家属性缓存能正确刷新。
### 12. 下线与资源释放验证
操作:
1. 玩家穿戴属性装备。
2. 进入战斗。
3. 玩家退出服务器。
4. 玩家重新进入。
通过标准:
- 退出时缓存释放。
- 重新进入后属性重新计算。
- 移速和生命值不残留异常。
### 13. 实体属性 API 验证
使用插件内部或临时测试命令给实体写入:
- `physical_defense`
- `pve_defense`
- `block_chance`
- `vampire_chance`
- `penetration`
- `move_speed`
通过标准:
- 已支持的字符串能映射到正确属性。
- 实体死亡或区块卸载后属性缓存释放。
### 14. 回归检查清单
- [ ] 构建通过。
- [ ] plugin.yml 版本正确。
- [ ] CraftEngine 已安装且先于本插件加载。
- [ ] 物品模板加载数量符合预期。
- [ ] `/att stats` 支持所有 AttributeType。
- [ ] Lore 不出现缺失节点。
- [ ] GUI 属性完整显示。
- [ ] 穿透按概率触发并无视防御。
- [ ] 格挡按概率触发并减伤反伤。
- [ ] 吸血按概率触发并回血。
- [ ] 破损装备属性失效。
- [ ] 修复命令修复目标正确。
- [ ] reload 后无异常。
- [ ] 玩家退出后无属性残留。
- [ ] jar 未打入 SQLite、HikariCP、CraftEngine 类。
### 15. 投入使用判定
允许投入使用必须同时满足:
- 构建验证通过。
- 服务端启动验证通过。
- 命令验证通过。
- 属性面板验证通过。
- 战斗属性验证通过。
- 耐久系统验证通过。
- 重载与缓存验证通过。
- 无高风险控制台异常。
若任一核心战斗属性未通过,结论为不允许投入使用。