CatAttrib.java v2.3
支持自定义倍率、属性标签、提示语言配置,穿戴生效优化,可热重载
命令列表
- SmileCat检测你当前装备/手持的全部猫属性
- reloadattrib重载所有猫属性配置文件
package com.scriptirc.attribchecker;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.CommandExecutor;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.*;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.entity.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.projectiles.ProjectileSource;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.*;
/**
* @pluginName CatAttrib
* @author ScriptIrc Engine
* @version 2.3
* @description 支持自定义倍率、属性标签、提示语言配置,穿戴生效优化,可热重载
* [command]SmileCat|检测你当前装备/手持的全部猫属性[/command]
* [command]reloadattrib|重载所有猫属性配置文件[/command]
* [Permission]catattrib.chatdmg|显示猫属性伤害和回血提示[/Permission]
*/
public class CatAttrib extends JavaPlugin implements CommandExecutor, Listener {
private Map<String, List<String>> attrSynonyms = new HashMap<>();
private File tagsConfigFile;
private File mainConfigFile;
private File langConfigFile;
private Map<String, String> langMap = new HashMap<>();
private Map<String, Double> rateMap = new HashMap<>();
private static final Map<String, List<String>> DEFAULT_ATTRS = new LinkedHashMap<>();
static {
DEFAULT_ATTRS.put("生命值", Arrays.asList("生命值", "HP", "Health"));
DEFAULT_ATTRS.put("防御", Arrays.asList("防御", "DEF", "防御力", "Defence"));
DEFAULT_ATTRS.put("伤害", Arrays.asList("伤害", "攻击力", "DMG", "Damage"));
DEFAULT_ATTRS.put("暴击", Arrays.asList("暴击", "暴击率", "Crit", "Cr"));
DEFAULT_ATTRS.put("暴击伤害", Arrays.asList("暴击伤害", "暴伤", "CritDmg", "暴击倍数"));
DEFAULT_ATTRS.put("移动速度", Arrays.asList("移动速度", "速度", "Speed", "MoveSpeed"));
DEFAULT_ATTRS.put("攻击速度", Arrays.asList("攻击速度", "攻速", "AttackSpeed", "AS"));
DEFAULT_ATTRS.put("吸血", Arrays.asList("吸血", "吸血率", "Lifesteal", "生命偷取", "ls"));
}
private static final Map<Material, Double> WEAPON_BASE_DAMAGE = new HashMap<>();
static {
WEAPON_BASE_DAMAGE.put(Material.WOODEN_SWORD, 4.0);
WEAPON_BASE_DAMAGE.put(Material.STONE_SWORD, 5.0);
WEAPON_BASE_DAMAGE.put(Material.IRON_SWORD, 6.0);
WEAPON_BASE_DAMAGE.put(Material.GOLDEN_SWORD, 4.0);
WEAPON_BASE_DAMAGE.put(Material.DIAMOND_SWORD, 7.0);
WEAPON_BASE_DAMAGE.put(Material.NETHERITE_SWORD, 8.0);
WEAPON_BASE_DAMAGE.put(Material.WOODEN_AXE, 7.0);
WEAPON_BASE_DAMAGE.put(Material.STONE_AXE, 9.0);
WEAPON_BASE_DAMAGE.put(Material.IRON_AXE, 9.0);
WEAPON_BASE_DAMAGE.put(Material.GOLDEN_AXE, 7.0);
WEAPON_BASE_DAMAGE.put(Material.DIAMOND_AXE, 9.0);
WEAPON_BASE_DAMAGE.put(Material.NETHERITE_AXE, 10.0);
WEAPON_BASE_DAMAGE.put(Material.BOW, 2.0);
WEAPON_BASE_DAMAGE.put(Material.CROSSBOW, 2.0);
WEAPON_BASE_DAMAGE.put(Material.TRIDENT, 8.0);
WEAPON_BASE_DAMAGE.put(Material.AIR, 1.0);
}
private static final Map<Material, Double> WEAPON_BASE_ATTACK_SPEED = new HashMap<>();
static {
WEAPON_BASE_ATTACK_SPEED.put(Material.WOODEN_SWORD, 1.6);
WEAPON_BASE_ATTACK_SPEED.put(Material.STONE_SWORD, 1.6);
WEAPON_BASE_ATTACK_SPEED.put(Material.IRON_SWORD, 1.6);
WEAPON_BASE_ATTACK_SPEED.put(Material.GOLDEN_SWORD, 1.6);
WEAPON_BASE_ATTACK_SPEED.put(Material.DIAMOND_SWORD, 1.6);
WEAPON_BASE_ATTACK_SPEED.put(Material.NETHERITE_SWORD, 1.6);
WEAPON_BASE_ATTACK_SPEED.put(Material.WOODEN_AXE, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.STONE_AXE, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.IRON_AXE, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.GOLDEN_AXE, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.DIAMOND_AXE, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.NETHERITE_AXE, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.BOW, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.CROSSBOW, 1.0);
WEAPON_BASE_ATTACK_SPEED.put(Material.TRIDENT, 1.1);
WEAPON_BASE_ATTACK_SPEED.put(Material.AIR, 4.0);
}
private final Map<UUID, Map<String, Double>> playerAttrib = new HashMap<>();
private final Random random = new Random();
private final Map<UUID, Map<String, Double>> arrowAttribMap = new HashMap<>();
private final Map<UUID, Material> arrowWeaponMap = new HashMap<>();
private final Map<UUID, ItemStack> arrowWeaponStackMap = new HashMap<>();
private static final Set<String> PASSIVE_KEYS = new HashSet<>(Arrays.asList("生命值","防御","移动速度"));
private static final Set<String> ATTACK_KEYS = new HashSet<>(Arrays.asList("伤害","暴击","暴击伤害","吸血","攻击速度"));
@Override
public void onEnable() {
Bukkit.getPluginManager().registerEvents(this, this);
getCommand("SmileCat").setExecutor(this);
try { getCommand("reloadattrib").setExecutor(this); } catch (Throwable ignore) {}
initMainConfig();
initAttributeTagsConfig();
initLangConfig();
for (Player player : Bukkit.getOnlinePlayers()) refreshPlayerAttrib(player);
getLogger().info("【CatAttrib】已加载全部倍率、标签与语言配置,随时可定制!");
}
@Override
public void onDisable() {
for (Player player : Bukkit.getOnlinePlayers()) {
resetPlayerHealthSpeed(player);
}
arrowAttribMap.clear(); playerAttrib.clear();
arrowWeaponMap.clear(); arrowWeaponStackMap.clear();
}
private void initMainConfig() {
mainConfigFile = new File(getDataFolder(), "catattrib_config.yml");
if (!getDataFolder().exists()) getDataFolder().mkdirs();
boolean writeDefault = false;
if (!mainConfigFile.exists()) writeDefault = true;
YamlConfiguration yml = YamlConfiguration.loadConfiguration(mainConfigFile);
// 默认倍率配置
if (writeDefault) {
yml.set("critical_base", 1.0); // 暴击基础倍率
yml.set("critical_per10", 0.5); // 每10点暴击伤害倍率加成
yml.set("lifesteal_rate", 1.0); // 吸血伤害百分比
yml.set("damage_bonus_rate", 1.0); // lore伤害加成倍率
yml.set("move_speed_base", 0.2); // 基础移速
yml.set("move_speed_per", 0.01); // lore移动速度每点加成
yml.set("attack_speed_base", 1.0); // 基础攻速
try { yml.save(mainConfigFile); } catch (IOException ignore) {}
}
rateMap.clear();
for(String k:yml.getKeys(false)){
rateMap.put(k, yml.getDouble(k));
}
}
private void initAttributeTagsConfig() {
tagsConfigFile = new File(getDataFolder(), "attribute_tags.yml");
boolean writeDefault = false;
if (!tagsConfigFile.exists()) writeDefault = true;
YamlConfiguration yml = YamlConfiguration.loadConfiguration(tagsConfigFile);
if (writeDefault) {
for (Map.Entry<String, List<String>> entry : DEFAULT_ATTRS.entrySet()) {
yml.set(entry.getKey(), entry.getValue());
}
try { yml.save(tagsConfigFile); } catch (IOException ignore) {}
}
attrSynonyms.clear();
for (String key : DEFAULT_ATTRS.keySet()) {
List<String> valset = yml.getStringList(key);
if(valset==null||valset.isEmpty()) valset = DEFAULT_ATTRS.get(key);
attrSynonyms.put(key, new ArrayList<>(valset));
}
}
private void initLangConfig() {
langConfigFile = new File(getDataFolder(), "catattrib_lang.yml");
boolean writeDefault = false;
if (!langConfigFile.exists()) writeDefault = true;
YamlConfiguration yml = YamlConfiguration.loadConfiguration(langConfigFile);
if (writeDefault) {
yml.set("actionbar_damage", "§e你对 §c{target} §e造成 §a{damage} §e点喵击伤害");
yml.set("actionbar_heal", "§d[猫吸血] §a你吸收 §c+{heal} §a生命");
yml.set("actionbar_behit", "§c你受到 §e{source} §c的 §c{damage} §c点喵击伤害");
yml.set("crit_message", "§b[猫暴击] 最终伤害 {finaldmg} (基础{base} 加成{add} 药水{pot} 暴击倍数{critmult} 附魔{ench})");
yml.set("lifesteal_chat", "[猫吸血] 你通过吸血回复了 +{heal} 生命");
yml.set("damage_chat", "[猫击] 你对 {target} 造成 {damage} 点伤害");
yml.set("behit_chat", "[猫击] 你受到 {source} 的 {damage} 点伤害");
yml.set("reload_success", "§a[CatAttrib] 属性配置文件重载成功。");
yml.set("cat_check_title", "§a[猫属性检测](只展示装备栏属性):");
try { yml.save(langConfigFile); } catch(IOException ignore) {}
}
langMap.clear();
for(String key : yml.getKeys(false)){
langMap.put(key, yml.getString(key));
}
}
private String formatLang(String key, Map<String,String> params){
String txt = langMap.getOrDefault(key,"");
if(params==null) return txt;
for(Map.Entry<String,String> e:params.entrySet()){
txt = txt.replace("{"+e.getKey()+"}", e.getValue());
}
return txt;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (label.equalsIgnoreCase("reloadattrib")) {
initMainConfig();
initAttributeTagsConfig();
initLangConfig();
sender.sendMessage(langMap.getOrDefault("reload_success","[CatAttrib] 配置文件重载成功!"));
sender.sendMessage("§e倍率配置: "+rateMap);
sender.sendMessage("§e属性别名: "+attrSynonyms);
return true;
}
if (label.equalsIgnoreCase("SmileCat")) {
if (!(sender instanceof Player)) {
sender.sendMessage("喵~ 只有玩家可以检测自己的猫属性哦!");
return true;
}
Player p = (Player)sender;
Map<String, Double> attrs = playerAttrib.getOrDefault(p.getUniqueId(), Collections.emptyMap());
p.sendMessage(langMap.getOrDefault("cat_check_title","§a[猫属性检测]:"));
for (String k : attrSynonyms.keySet()) {
double v = attrs.getOrDefault(k,0d);
p.sendMessage(String.format("§e%s§f: §b%s",k,formatNum(v)));
}
return true;
}
return false;
}
@EventHandler public void onJoin(PlayerJoinEvent e){ refreshPlayerAttrib(e.getPlayer()); }
@EventHandler public void onQuit(PlayerQuitEvent e){ resetPlayerHealthSpeed(e.getPlayer()); playerAttrib.remove(e.getPlayer().getUniqueId()); }
@EventHandler public void onItemHeld(PlayerItemHeldEvent e){ Bukkit.getScheduler().runTaskLater(this, () -> refreshPlayerAttrib(e.getPlayer()), 1L); }
@EventHandler public void onSwapHand(PlayerSwapHandItemsEvent e){ Bukkit.getScheduler().runTaskLater(this, () -> refreshPlayerAttrib(e.getPlayer()), 1L); }
@EventHandler public void onInventoryClick(InventoryClickEvent e){
if (e.getWhoClicked() instanceof Player) {
Bukkit.getScheduler().runTaskLater(this, () -> refreshPlayerAttrib((Player) e.getWhoClicked()), 1L);
}
}
private void refreshPlayerAttrib(Player p){
Map<String, Double> total = new HashMap<>();
Arrays.asList(p.getInventory().getArmorContents()).forEach(item -> {
Map<String, Double> it = attribFromItem(item);
for(Map.Entry<String,Double> e:it.entrySet()){
if(PASSIVE_KEYS.contains(e.getKey())){
total.put(e.getKey(), total.getOrDefault(e.getKey(), 0d) + e.getValue());
}
}
});
playerAttrib.put(p.getUniqueId(), total);
double hp = total.getOrDefault("生命值", 0d);
setPlayerMaxHealth(p, 20d + hp);
if(p.getHealth() > p.getMaxHealth()) p.setHealth(p.getMaxHealth());
double movePercent = Math.max(0d, Math.min(100d, total.getOrDefault("移动速度", 0d)));
double moveBase = rateMap.getOrDefault("move_speed_base", 0.2);
double movePer = rateMap.getOrDefault("move_speed_per", 0.01);
double speed = moveBase + movePer * movePercent;
if (speed > 1.0) speed = 1.0;
if (speed < 0.01) speed = 0.01;
try { p.setWalkSpeed((float) speed); } catch (Throwable ignore) {}
}
private void mergeAttribSelective(Map<String,Double> main, Map<String,Double> add, Set<String> allow){
for(Map.Entry<String,Double> e:add.entrySet()){
if(allow.contains(e.getKey())){
main.put(e.getKey(), main.getOrDefault(e.getKey(), 0d)+e.getValue());
}
}
}
private Map<String, Double> attribFromItem(ItemStack item) {
Map<String, Double> at = new HashMap<>();
if (item==null || !item.hasItemMeta() || !item.getItemMeta().hasLore()) return at;
ItemMeta meta = item.getItemMeta();
List<String> lore = meta.getLore();
Pattern pattern = Pattern.compile("([一-龥A-Za-z]+)[::]\\s*([-]?\\d+\\.?\\d*)");
for (String line : lore) {
String plain = line.replaceAll("§[0-9a-fk-orA-FK-OR]", "");
Matcher m = pattern.matcher(plain);
while (m.find()) {
String key = m.group(1); String vstr = m.group(2);
for (Map.Entry<String, List<String>> syn : attrSynonyms.entrySet()) {
if (syn.getValue().stream().anyMatch(alias -> alias.equalsIgnoreCase(key))) {
try {
double val = Double.parseDouble(vstr);
at.put(syn.getKey(), at.getOrDefault(syn.getKey(), 0d)+val);
} catch (Exception ignored) {}
break;
}
}
}
}
return at;
}
@EventHandler
public void onDamage(EntityDamageByEntityEvent e) {
if (!(e.getEntity() instanceof LivingEntity)) return;
LivingEntity victim = (LivingEntity) e.getEntity();
Player attacker = null;
Map<String, Double> passiveAtr = null;
Map<String, Double> weaponAtr = null;
Material weaponMat = null;
ItemStack weaponItem = null;
if (e.getDamager() instanceof Player) {
attacker = (Player) e.getDamager();
passiveAtr = playerAttrib.getOrDefault(attacker.getUniqueId(), Collections.emptyMap());
weaponItem = attacker.getInventory().getItemInMainHand();
weaponMat = (weaponItem==null?Material.AIR:weaponItem.getType());
weaponAtr = attribFromItem(weaponItem);
} else if (e.getDamager() instanceof Projectile) {
Projectile proj = (Projectile) e.getDamager();
ProjectileSource shooter = proj.getShooter();
if(shooter instanceof Player) {
attacker = (Player) shooter;
Map<String, Double> attr = arrowAttribMap.get(proj.getUniqueId());
weaponAtr = attr!=null? attr : new HashMap<>();
passiveAtr = playerAttrib.getOrDefault(attacker.getUniqueId(), Collections.emptyMap());
weaponMat = arrowWeaponMap.getOrDefault(proj.getUniqueId(), Material.BOW);
weaponItem = arrowWeaponStackMap.get(proj.getUniqueId());
}
}
if (attacker != null) {
double base = getBaseWeaponDamage(weaponMat, weaponItem);
double origEventDamage = e.getDamage();
Map<String,Double> atkAtr = new HashMap<>();
if(weaponAtr!=null) mergeAttribSelective(atkAtr, weaponAtr, ATTACK_KEYS);
double loreAdd = atkAtr.getOrDefault("伤害", 0d) * rateMap.getOrDefault("damage_bonus_rate", 1.0);
double attackSpeed = WEAPON_BASE_ATTACK_SPEED.getOrDefault(weaponMat, 4.0) +
atkAtr.getOrDefault("攻击速度", 0d) * rateMap.getOrDefault("attack_speed_base", 1.0);
double critProb = atkAtr.getOrDefault("暴击",0d);
double critDmg = atkAtr.getOrDefault("暴击伤害",0d);
boolean isCrit = (critProb>0 && random.nextDouble() < critProb);
double enchBonus = origEventDamage - base;
if(enchBonus<0) enchBonus = 0;
double totalDamage = origEventDamage + loreAdd;
if(isCrit) {
// 动态倍率配比
double realCritDmg = rateMap.getOrDefault("critical_base", 1.0) +
critDmg / 10.0 * rateMap.getOrDefault("critical_per10", 0.5);
double norm = base + loreAdd + (origEventDamage-base - enchBonus);
double afterCrit = norm * realCritDmg + enchBonus;
totalDamage = afterCrit;
if(attacker.hasPermission("catattrib.chatdmg") || attacker.isOp()){
Map<String,String> m = new HashMap<>();
m.put("finaldmg",formatNum(afterCrit)); m.put("base",formatNum(base)); m.put("add",formatNum(loreAdd));
m.put("pot",formatNum(origEventDamage-base-enchBonus));
m.put("critmult",formatNum(realCritDmg)); m.put("ench",formatNum(enchBonus));
attacker.sendMessage(formatLang("crit_message", m));
}
}
double victimDef = 1.0;
if(victim instanceof Player){
Map<String, Double> victimPassive = playerAttrib.getOrDefault(victim.getUniqueId(), Collections.emptyMap());
victimDef = Math.max(1.0, victimPassive.getOrDefault("防御", 0d));
}
totalDamage = totalDamage / victimDef;
e.setDamage(Math.max(0.1, totalDamage));
double lifesteal = atkAtr.getOrDefault("吸血", 0d) * rateMap.getOrDefault("lifesteal_rate",1.0);
if(lifesteal > 0.0 && attacker != null && totalDamage > 0.0){
double heal = totalDamage * (lifesteal/100.0);
double originHP = attacker.getHealth();
double maxHP = attacker.getMaxHealth();
double finalHP = Math.min(originHP + heal, maxHP);
try{ attacker.setHealth(finalHP); } catch(Exception ignored){}
Map<String,String> m = new HashMap<>();
m.put("heal", formatNum(heal));
sendActionBar(attacker, formatLang("actionbar_heal", m));
if(attacker.hasPermission("catattrib.chatdmg") || attacker.isOp())
attacker.sendMessage(formatLang("lifesteal_chat", m));
}
}
if (victim instanceof Player && attacker == null) {
Player v = (Player)victim;
Map<String, Double> atr = playerAttrib.getOrDefault(v.getUniqueId(), Collections.emptyMap());
double def = Math.max(1.0, atr.getOrDefault("防御",0d));
double dmg = e.getDamage() / def;
e.setDamage(Math.max(0.1, dmg));
}
try {
double finalDmg = e.getFinalDamage();
if (attacker != null) {
String tarName = getEntityShowName(victim);
Map<String, String> m = new HashMap<>();
m.put("target",tarName); m.put("damage",formatNum(finalDmg));
sendActionBar(attacker, formatLang("actionbar_damage", m));
if(attacker.hasPermission("catattrib.chatdmg") || attacker.isOp())
attacker.sendMessage(formatLang("damage_chat", m));
}
if (victim instanceof Player) {
String srcName = attacker==null ? getEntityShowName(e.getDamager()) : getEntityShowName(attacker);
Map<String,String> m = new HashMap<>();
m.put("source",srcName); m.put("damage",formatNum(finalDmg));
sendActionBar((Player)victim, formatLang("actionbar_behit", m));
Player v = (Player)victim;
if(v.hasPermission("catattrib.chatdmg") || v.isOp())
v.sendMessage(formatLang("behit_chat", m));
}
} catch (Exception ex) {
getLogger().warning("伤害ActionBar/聊天栏发送异常: "+ex.getMessage());
}
}
@EventHandler
public void onEntityDamage(EntityDamageEvent e){
try {
if(e instanceof EntityDamageByEntityEvent) return;
if(e.getEntity() instanceof Player){
String cause = e.getCause().name();
Map<String,String> m = new HashMap<>();
m.put("source",cause); m.put("damage",formatNum(e.getFinalDamage()));
sendActionBar((Player)e.getEntity(), formatLang("actionbar_behit", m));
Player v = (Player)e.getEntity();
if(v.hasPermission("catattrib.chatdmg") || v.isOp())
v.sendMessage(formatLang("behit_chat", m));
}
} catch(Exception ex){
getLogger().warning("通用伤害ActionBar/聊天栏发送异常:"+ex.getMessage());
}
}
@EventHandler
public void onProjectileLaunch(ProjectileLaunchEvent event) {
if (!(event.getEntity().getShooter() instanceof Player)) return;
Player shooter = (Player) event.getEntity().getShooter();
Map<String, Double> attr = attribFromItem(shooter.getInventory().getItemInMainHand());
arrowAttribMap.put(event.getEntity().getUniqueId(), attr);
ItemStack main = shooter.getInventory().getItemInMainHand();
arrowWeaponMap.put(event.getEntity().getUniqueId(), (main==null?Material.BOW:main.getType()));
if(main!=null && main.getType()!=Material.AIR && main.getAmount()>0){
arrowWeaponStackMap.put(event.getEntity().getUniqueId(), main.clone());
}else{
arrowWeaponStackMap.put(event.getEntity().getUniqueId(), null);
}
}
@EventHandler
public void onDamageAfter(EntityDamageEvent event){
if(event.getEntity() instanceof LivingEntity){
arrowAttribMap.entrySet().removeIf(entry -> Bukkit.getEntity(entry.getKey())==null);
arrowWeaponMap.entrySet().removeIf(entry -> Bukkit.getEntity(entry.getKey())==null);
arrowWeaponStackMap.entrySet().removeIf(entry -> Bukkit.getEntity(entry.getKey())==null);
}
}
private double getBaseWeaponDamage(Material mat, ItemStack item) {
if (item == null || item.getType() == Material.AIR || item.getAmount() <= 0) return 1.0;
return WEAPON_BASE_DAMAGE.getOrDefault(mat, 1.0);
}
private void setPlayerMaxHealth(Player p, double hp) {
try { p.setMaxHealth(hp); } catch (Throwable ignore2) {}
}
private void resetPlayerHealthSpeed(Player p) {
setPlayerMaxHealth(p, 20d);
if(p.getHealth() > 20d) p.setHealth(20d);
try{ p.setWalkSpeed(0.2f); } catch (Throwable ignore) {}
}
private String formatNum(double d) {
return String.format("%.1f", d);
}
private String getEntityShowName(Entity e) {
if (e==null) return "未知";
try {
String cn = e.getCustomName();
if (cn != null && !cn.isEmpty()) {
return stripColor(cn);
}
} catch (Throwable ignore) {}
if (e instanceof Player) {
return ((Player)e).getName();
}
return e.getType().name();
}
private String stripColor(String s) {
if(s==null) return "";
return s.replaceAll("§[0-9a-fk-orA-FK-OR]", "");
}
private void sendActionBar(Player p, String msg){
try { p.sendActionBar(msg); } catch (Throwable ignore) {
try { p.spigot().sendMessage(net.md_5.bungee.api.ChatMessageType.ACTION_BAR,
new net.md_5.bungee.api.chat.TextComponent(msg));
} catch (Throwable err) { p.sendMessage(msg); }
}
}
}