Add files via upload

This commit is contained in:
Xanadu13
2022-08-11 21:57:12 +08:00
committed by GitHub
parent 84d499413c
commit 8cdcdce2c4
14 changed files with 1645 additions and 0 deletions
@@ -0,0 +1,102 @@
package xanadu.enderdragon;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import xanadu.enderdragon.commands.MainCommand;
import xanadu.enderdragon.commands.TabCompleter;
import xanadu.enderdragon.events.*;
import xanadu.enderdragon.listeners.InventoryClick;
import xanadu.enderdragon.metrics.Metrics;
import java.io.File;
public final class EnderDragon extends JavaPlugin {
public static String prefix = "";
public static Plugin plugin;
public static Server server;
public static PluginManager pm;
public static File data0;
public static File language0;
public static FileConfiguration data;
public static FileConfiguration language;
@Override
public void onEnable() {
plugin = this;
server = plugin.getServer();
pm = Bukkit.getPluginManager();
if (!new File(getDataFolder(), "config.yml").exists()) {
saveDefaultConfig();
Bukkit.getConsoleSender().sendMessage("§a[EnderDragon] 未检测到config.yml文件,正在生成新的配置文件");
}
if (!new File(getDataFolder(), "data.yml").exists()) {
this.saveResource("data.yml",false);
Bukkit.getConsoleSender().sendMessage("§a[EnderDragon] 未检测到data.yml文件,正在生成新的配置文件");
}
if (!new File(getDataFolder(), "language.yml").exists()) {
this.saveResource("language.yml",false);
Bukkit.getConsoleSender().sendMessage("§a[EnderDragon] 未检测到language.yml文件,正在生成新的配置文件");
}
data0 = new File(plugin.getDataFolder(),"data.yml");
language0 = new File(plugin.getDataFolder(),"language.yml");
data = YamlConfiguration.loadConfiguration(data0);
language = YamlConfiguration.loadConfiguration(language0);
prefix = language.getString("prefix");
if(!getConfig().getString("version").equals("1.8.2") ){
Bukkit.getConsoleSender().sendMessage("§c[EnderDragon] config.yml版本与插件不对应,请更新配置文件");
}
if(!data.getString("version").equals("1.8.1") ){
Bukkit.getConsoleSender().sendMessage("§c[EnderDragon] data.yml版本与插件不对应,请更新配置文件");
}
if(!language.getString("version").equals("1.8") ){
Bukkit.getConsoleSender().sendMessage("§c[EnderDragon] language.yml版本与插件不对应,请更新配置文件");
}
Bukkit.getConsoleSender().sendMessage("§a[EnderDragon] 插件已加载");
Bukkit.getConsoleSender().sendMessage("§a[EnderDragon] 作者:Xanadu13");
pm.registerEvents(new CreatureHurt(),this);
pm.registerEvents(new DragonAttack(),this);
pm.registerEvents(new DragonDeath(),this);
pm.registerEvents(new DragonHeal(),this);
pm.registerEvents(new DragonSpawn(),this);
pm.registerEvents(new InventoryClick(),this);
getCommand("enderdragon").setExecutor(new MainCommand());
getCommand("enderdragon").setTabCompleter(new TabCompleter());
Metrics metrics = new Metrics(this,14850);
}
@Override
public void onDisable()
{
Bukkit.getConsoleSender().sendMessage("§e[EnderDragon] 插件已卸载");
}
public static String itemStackToString(ItemStack itemStack) {
YamlConfiguration yml = new YamlConfiguration();
yml.set("item", itemStack);
return yml.saveToString();
}
public static ItemStack StringToItemStack(String str) {
YamlConfiguration yml = new YamlConfiguration();
ItemStack item;
try {
yml.loadFromString(str);
item = yml.getItemStack("item");
} catch (InvalidConfigurationException ex) {
item = new ItemStack(Material.AIR, 1);
}
return item;
}
}
@@ -0,0 +1,139 @@
package xanadu.enderdragon.commands;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import static xanadu.enderdragon.EnderDragon.*;
public class MainCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 1) {
if (args[0].equalsIgnoreCase("reload")) {
if(!sender.hasPermission("ed.reload")){
sender.sendMessage("§4你没有使用该命令的权限");
return false;
}
plugin.reloadConfig();
data = YamlConfiguration.loadConfiguration(data0);
language = YamlConfiguration.loadConfiguration(language0);
prefix = language.getString("prefix");
sender.sendMessage(prefix + "§a配置文件已重载");
}
}
else if (args[0].equalsIgnoreCase("drop") ) {
if (!(sender instanceof Player || args[1].equalsIgnoreCase("clear"))) {
sender.sendMessage(prefix + "§c这个指令只能由玩家执行");
return false;
}
if (args[1].equalsIgnoreCase("add")) {
if(!sender.hasPermission("ed.drop.change")){
sender.sendMessage("§4你没有使用该命令的权限");
return false;
}
if(args.length == 3 ) {
Player p = (Player) sender;
if(p.getItemInHand().getType() == Material.AIR){
p.sendMessage(prefix + "§c添加掉落物失败,你手上没有拿物品...");
return false;
}
String ChanceStr = args[2];
double chance = 0;
try {
chance = Double.parseDouble(ChanceStr);
} catch (NumberFormatException ex){
p.sendMessage(prefix + "§c您应该输入数字而不是 " + ChanceStr);
throw new NumberFormatException("\n\n"
+ "\33[31;1m" + "错误的命令: /ed drop add " + ChanceStr + "\n"
+ "\33[33;1m" + "正确用法: /ed drop add 数字" + "\n"
+ "\33[0m");
}
List<String> datum = data.getStringList("items");
ItemStack item = p.getItemInHand();
datum.add(itemStackToString(item));
datum.add(String.valueOf(chance));
data.set("items", datum);
try {
data.save(data0);
} catch (IOException e) {
throw new RuntimeException(e);
}
data = YamlConfiguration.loadConfiguration(data0);
p.sendMessage(prefix + "§a掉落物添加成功,概率为: §c" + chance + "%");
}
}
if (args[1].equalsIgnoreCase("clear")) {
if(!sender.hasPermission("ed.drop.change")){
sender.sendMessage("§4你没有使用该命令的权限");
return false;
}
if(args.length == 2 ) {
data.set("items","");
try {
data.save(data0);
} catch (IOException e) {
throw new RuntimeException(e);
}
data = YamlConfiguration.loadConfiguration(data0);
sender.sendMessage(prefix + "§a掉落物配置已清空");
}
}
if (args[1].equalsIgnoreCase("gui")) {
if(!sender.hasPermission("ed.drop.gui")){
sender.sendMessage("§4你没有使用该命令的权限");
return false;
}
if(args.length == 2 ) {
Player p = (Player) sender;
String title = plugin.getConfig().getString("special-dragon.drop-gui-title");
if(title == null){title = "";}
Inventory inv = Bukkit.createInventory(null,54,title);
List<String> datum = data.getStringList("items");
int max = datum.size() / 2 ;
for(int i=0 ; i<max ; ){
i = i + 1;
double chance = Double.parseDouble(datum.get(i*2-1));
String str = datum.get(i*2-2);
ItemStack item = StringToItemStack(str);
ItemMeta meta = item.getItemMeta();
if(meta != null) {
List<String> lore = meta.getLore();
if(lore != null) {
lore.add("§6(掉落概率: " + chance + "%)§r");
meta.setLore(lore);
}
else{
meta.setLore(Collections.singletonList("§6(掉落概率: " + chance + "%)§r"));
}
item.setItemMeta(meta);
}
inv.setItem(i-1,item);
if(i == 54){break;}
}
p.openInventory(inv);
}
}
}
else {
sender.sendMessage("§e/ed reload 重载配置文件");
sender.sendMessage("§e/ed drop 特殊龙掉落物设置");
}
return false;
}
}
@@ -0,0 +1,45 @@
package xanadu.enderdragon.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.List;
public class TabCompleter implements org.bukkit.command.TabCompleter {
List<String> arguments = new ArrayList<>();
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
List<String> result = new ArrayList<>();
if (args.length == 1) {
arguments.clear();
arguments.add("reload");
arguments.add("drop");
for (String s : arguments) {
if (s.toLowerCase().startsWith(args[0].toLowerCase())) {
result.add(s);
}
}
return result;
}
else if (args.length == 2){
arguments.clear();
arguments.add("add");
arguments.add("clear");
arguments.add("gui");
if(args[0].equalsIgnoreCase("drop")) {
for (String s : arguments) {
if (s.toLowerCase().startsWith(args[1].toLowerCase())) {
result.add(s);
}
}
}
return result;
}
return null;
}
}
@@ -0,0 +1,28 @@
package xanadu.enderdragon.events;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import static xanadu.enderdragon.EnderDragon.*;
public class CreatureHurt implements Listener {
@EventHandler
public void OnCreatureHurt(EntityDamageByEntityEvent e){
int DamageVisual = plugin.getConfig().getInt("special-dragon.damage-visible");
String message = language.getString("damage-display");
if(DamageVisual == 0){return;}
if(e.getDamager().getType() != EntityType.PLAYER){return;}
if(e.getEntity().getType() != EntityType.ENDER_DRAGON){return;}
Player p = (Player) e.getDamager();
double damage = e.getDamage();
message = message.replaceAll("%damage%", String.valueOf(damage));
if(DamageVisual == 1){p.sendTitle("",message,5,40,5);}
if(DamageVisual == 2){p.sendMessage(message);}
if(DamageVisual == 3){p.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(message));}
}
}
@@ -0,0 +1,63 @@
package xanadu.enderdragon.events;
import org.bukkit.entity.EnderDragon;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.List;
import static xanadu.enderdragon.EnderDragon.*;
public class DragonAttack implements Listener {
@EventHandler
public void OnDragonAttack(EntityDamageByEntityEvent e){
if(e.getDamager().getType() != EntityType.ENDER_DRAGON){return;}
if(!e.getDamager().getScoreboardTags().contains("special")){return;}
double multiple = plugin.getConfig().getDouble("special-dragon.damage-multiply");
double rate = plugin.getConfig().getDouble("special-dragon.suck-blood.rate") / 100;
double BasicSuck = plugin.getConfig().getDouble("special-dragon.suck-blood.base-suck-blood");
boolean SuckBlood = plugin.getConfig().getBoolean("special-dragon.suck-blood.enable");
boolean OnlyPlayer = plugin.getConfig().getBoolean("special-dragon.suck-blood.only-player");
List<String> effects = plugin.getConfig().getStringList("special-dragon.attack-effect");
if(e.getEntity() instanceof Player) {
Player p = (Player) e.getEntity();
if (multiple > 0) {
double damage = e.getDamage();
e.setDamage(damage * multiple);
}
for (String str : effects) {
if (str.length() > 0) {
String type = str.substring(0, str.indexOf(" "));
int time = Integer.parseInt(str.substring(str.indexOf(" ") + 1, str.lastIndexOf(" ")));
int level = Integer.parseInt(str.substring(str.lastIndexOf(" ") + 1));
PotionEffectType effectType = PotionEffectType.getByName(type.toUpperCase());
if (effectType != null) {
p.addPotionEffect(new PotionEffect(effectType, time * 20, level - 1));
}
}
}
if(!SuckBlood){return;}
double FinalDamage = e.getFinalDamage();
EnderDragon dragon = (EnderDragon) e.getDamager();
double health = dragon.getHealth()+FinalDamage*rate+BasicSuck;
if(health > dragon.getMaxHealth()){health = dragon.getMaxHealth();}
dragon.setHealth(health);
}
else{
if((!SuckBlood) || OnlyPlayer){return;}
double FinalDamage = e.getFinalDamage();
EnderDragon dragon = (EnderDragon) e.getDamager();
double health = dragon.getHealth()+FinalDamage*rate+BasicSuck;
if(health > dragon.getMaxHealth()){health = dragon.getMaxHealth();}
dragon.setHealth(health);
}
}
}
@@ -0,0 +1,121 @@
package xanadu.enderdragon.events;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.scheduler.BukkitRunnable;
import xanadu.enderdragon.EnderDragon;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import static xanadu.enderdragon.EnderDragon.*;
public class DragonDeath implements Listener {
@EventHandler
public void OnDragonDeath(EntityDeathEvent e) throws IOException {
if(e.getEntity().getType() != EntityType.ENDER_DRAGON){return;}
int times = data.getInt("times");
data.set("times",times+1);
data.save(data0);
Player p = e.getEntity().getKiller();
int exp = plugin.getConfig().getInt("special-dragon.exp-drop");
int x = plugin.getConfig().getInt("special-dragon.dragon-egg-spawn.x");
int y = plugin.getConfig().getInt("special-dragon.dragon-egg-spawn.y");
int z = plugin.getConfig().getInt("special-dragon.dragon-egg-spawn.z");
long delay = plugin.getConfig().getInt("special-dragon.dragon-egg-spawn.delay");
boolean UseCMD = plugin.getConfig().getBoolean("command.enable");
boolean EggChance = plugin.getConfig().getInt("special-dragon.dragon-egg-spawn.chance") > ThreadLocalRandom.current().nextInt(0, 100);
boolean AllBroadcast = !plugin.getConfig().getBoolean("only-special-death-remind");
String skill = language.getString("killer-message");
String KillMsg = language.getString("dragon-killing-broadcast");
String nobody = language.getString("nobody-kill");
String InvIsFull = language.getString("player-inv-full");
if(KillMsg != null) {
if(p != null) {
if ((e.getEntity().getScoreboardTags().contains("special")) || (AllBroadcast)) {
Bukkit.broadcastMessage(prefix + KillMsg.replaceAll("%times%", String.valueOf(times)).replaceAll("%player%", p.getDisplayName()));
}
}
else {
StringBuilder names = new StringBuilder();
for (Entity entity : e.getEntity().getNearbyEntities(5,5,5)) {
if (entity instanceof Player) {
names.append(((Player) entity).getDisplayName()).append(",");
}
}
String name = names.toString();
if(name.equals("")){name = nobody;}
if(name.endsWith(",")){name = name.substring(0,name.length()-1);}
if ((e.getEntity().getScoreboardTags().contains("special")) || (AllBroadcast)) {
Bukkit.broadcastMessage(prefix + KillMsg.replaceAll("%times%", String.valueOf(times)).replaceAll("%player%", name));
}
}
}
if(UseCMD) {
if (e.getEntity().getScoreboardTags().contains("special")) {
List<String> CMDList = plugin.getConfig().getStringList("command.special-dragon");
for (String command : CMDList) {
if (!(p == null && command.contains("%player%"))) {
server.dispatchCommand(server.getConsoleSender(), command.replaceAll("%player%", p.getName()));
}
}
}
else {
List<String> CMDList = plugin.getConfig().getStringList("command.normal-dragon");
for (String command : CMDList) {
if (!(p == null && command.contains("%player%"))) {
server.dispatchCommand(server.getConsoleSender(), command.replaceAll("%player%", p.getName()));
}
}
}
}
if(e.getEntity().getScoreboardTags().contains("special")){
e.setDroppedExp(exp);
if (EggChance) {
BukkitRunnable runnable = new BukkitRunnable() {
@Override
public void run() {
Block block = e.getEntity().getWorld().getBlockAt(x, y, z);
block.setType(Material.DRAGON_EGG);
}
};
runnable.runTaskLater(EnderDragon.getPlugin(EnderDragon.class), delay );
}
if(p != null){p.sendMessage(prefix + skill);}
List<String> datum = data.getStringList("items");
int max = datum.size() / 2 ;
boolean warning = false;
for(int i=0 ; i<max ; ){
i = i + 1;
double chance = Double.parseDouble(datum.get(i*2-1));
boolean judge = chance > Math.random() * 100;
if(judge){
String str = datum.get(i*2-2);
ItemStack item = StringToItemStack(str);
if(p == null){
e.getEntity().getWorld().dropItem(e.getEntity().getLocation(),item);
}
else{
if (p.getInventory().firstEmpty() == -1) {
warning = true;
}
PlayerInventory inv = p.getInventory();
inv.addItem(item);
}
}
}
if(warning){p.sendMessage(prefix + InvIsFull);}
}
}
}
@@ -0,0 +1,24 @@
package xanadu.enderdragon.events;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import static xanadu.enderdragon.EnderDragon.plugin;
public class DragonHeal implements Listener {
@EventHandler
public void OnDragonHeal(EntityRegainHealthEvent e){
if(e.getEntity().getType() != EntityType.ENDER_DRAGON){return;}
if(!e.getRegainReason().equals(EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL)){return;}
double NormalHeal = plugin.getConfig().getDouble("normal-dragon.crystal-heal");
double SpecialHeal = plugin.getConfig().getDouble("special-dragon.crystal-heal");
if(e.getEntity().getScoreboardTags().contains("special")){
e.setAmount(SpecialHeal / 2);
}
else{
e.setAmount(NormalHeal / 2);
}
}
}
@@ -0,0 +1,67 @@
package xanadu.enderdragon.events;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.scoreboard.Team;
import java.util.concurrent.ThreadLocalRandom;
import static xanadu.enderdragon.EnderDragon.*;
public class DragonSpawn implements Listener {
@EventHandler
public void OnDragonSpawn(CreatureSpawnEvent e){
if(e.getEntity().getType() != EntityType.ENDER_DRAGON){return;}
int times = data.getInt("times");
double health = plugin.getConfig().getDouble("special-dragon.max-health");
double SpawnHealth = plugin.getConfig().getDouble("special-dragon.spawn-health");
int circle = plugin.getConfig().getInt("special-dragon.respawn-circle");
boolean chance = plugin.getConfig().getInt("special-dragon.chance") > ThreadLocalRandom.current().nextInt(0, 100);
boolean SpecialMsg0 = plugin.getConfig().getBoolean("special-dragon.spawn-remind");
String SpawnMsg = language.getString("dragon-spawn-broadcast");
String SpecialMsg = language.getString("special-broadcast");
String Name = plugin.getConfig().getString("special-dragon.name");
String NormalName = plugin.getConfig().getString("normal-dragon.name");
String color = plugin.getConfig().getString("special-dragon.glow-color");
if(SpawnMsg == null){SpawnMsg = "none";}
if(!SpawnMsg.equals("none")){
Bukkit.broadcastMessage(prefix + SpawnMsg.replaceAll("%times%", String.valueOf(times)));
}
if(times % circle == 0 && chance) {
e.getEntity().addScoreboardTag("special");
if(health > 0) {
AttributeInstance MaxHealth = e.getEntity().getAttribute(Attribute.GENERIC_MAX_HEALTH);
assert MaxHealth != null;
MaxHealth.addModifier(new AttributeModifier("最大生命值", health - 200, AttributeModifier.Operation.ADD_NUMBER));
}
e.getEntity().setHealth(SpawnHealth);
if (color != null) {
if(!color.equalsIgnoreCase("disable")) {
color = color.toUpperCase();
if(server.getScoreboardManager().getMainScoreboard().getTeam("enderdragon-glow") == null) {
server.getScoreboardManager().getMainScoreboard().registerNewTeam("enderdragon-glow");
}
Team team = server.getScoreboardManager().getMainScoreboard().getTeam("enderdragon-glow");
team.setColor(ChatColor.valueOf(color));
team.addEntry(e.getEntity().getUniqueId().toString());
e.getEntity().setGlowing(true);
}
}
e.getEntity().setCustomName(Name);
if(SpecialMsg0 && SpecialMsg != null){
Bukkit.broadcastMessage(prefix + SpecialMsg);
}
}
else{
e.getEntity().setCustomName(NormalName);
}
}
}
@@ -0,0 +1,14 @@
package xanadu.enderdragon.listeners;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
public class InventoryClick implements Listener {
@EventHandler(priority = EventPriority.HIGH)
public void OnInventoryClick(InventoryClickEvent e){
if(!e.getView().getTitle().contains("§b特殊龙掉落物§r")){return;}
e.setCancelled(true);
}
}
@@ -0,0 +1,849 @@
package xanadu.enderdragon.metrics;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HttpsURLConnection;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
public class Metrics {
private final Plugin plugin;
private final MetricsBase metricsBase;
/**
* Creates a new Metrics instance.
*
* @param plugin Your plugin instance.
* @param serviceId The id of the service. It can be found at <a
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/
public Metrics(JavaPlugin plugin, int serviceId) {
this.plugin = plugin;
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
if (!config.isSet("serverUuid")) {
config.addDefault("enabled", true);
config.addDefault("serverUuid", UUID.randomUUID().toString());
config.addDefault("logFailedRequests", false);
config.addDefault("logSentData", false);
config.addDefault("logResponseStatusText", false);
// Inform the server owners about bStats
config
.options()
.header(
"bStats (https://bStats.org) collects some basic information for plugin authors, like how\n"
+ "many people use their plugin and their total player count. It's recommended to keep bStats\n"
+ "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n"
+ "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n"
+ "anonymous.")
.copyDefaults(true);
try {
config.save(configFile);
} catch (IOException ignored) {
}
}
// Load the data
boolean enabled = config.getBoolean("enabled", true);
String serverUUID = config.getString("serverUuid");
boolean logErrors = config.getBoolean("logFailedRequests", false);
boolean logSentData = config.getBoolean("logSentData", false);
boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false);
metricsBase =
new MetricsBase(
"bukkit",
serverUUID,
serviceId,
enabled,
this::appendPlatformData,
this::appendServiceData,
submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
plugin::isEnabled,
(message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error),
(message) -> this.plugin.getLogger().log(Level.INFO, message),
logErrors,
logSentData,
logResponseStatusText);
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
}
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("playerAmount", getPlayerAmount());
builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0);
builder.appendField("bukkitVersion", Bukkit.getVersion());
builder.appendField("bukkitName", Bukkit.getName());
builder.appendField("javaVersion", System.getProperty("java.version"));
builder.appendField("osName", System.getProperty("os.name"));
builder.appendField("osArch", System.getProperty("os.arch"));
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
}
private void appendServiceData(JsonObjectBuilder builder) {
builder.appendField("pluginVersion", plugin.getDescription().getVersion());
}
private int getPlayerAmount() {
try {
// Around MC 1.8 the return type was changed from an array to a collection,
// This fixes java.lang.NoSuchMethodError:
// org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
return onlinePlayersMethod.getReturnType().equals(Collection.class)
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (Exception e) {
// Just use the new method if the reflection failed
return Bukkit.getOnlinePlayers().size();
}
}
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "3.0.0";
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
private final String platform;
private final String serverUuid;
private final int serviceId;
private final Consumer<JsonObjectBuilder> appendPlatformDataConsumer;
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
private final Consumer<Runnable> submitTaskConsumer;
private final Supplier<Boolean> checkServiceEnabledSupplier;
private final BiConsumer<String, Throwable> errorLogger;
private final Consumer<String> infoLogger;
private final boolean logErrors;
private final boolean logSentData;
private final boolean logResponseStatusText;
private final Set<CustomChart> customCharts = new HashSet<>();
private final boolean enabled;
/**
* Creates a new MetricsBase class instance.
*
* @param platform The platform of the service.
* @param serviceId The id of the service.
* @param serverUuid The server uuid.
* @param enabled Whether or not data sending is enabled.
* @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all platform-specific data.
* @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all service-specific data.
* @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be
* used to delegate the data collection to a another thread to prevent errors caused by
* concurrency. Can be {@code null}.
* @param checkServiceEnabledSupplier A supplier to check if the service is still enabled.
* @param errorLogger A consumer that accepts log message and an error.
* @param infoLogger A consumer that accepts info log messages.
* @param logErrors Whether or not errors should be logged.
* @param logSentData Whether or not the sent data should be logged.
* @param logResponseStatusText Whether or not the response status text should be logged.
*/
public MetricsBase(
String platform,
String serverUuid,
int serviceId,
boolean enabled,
Consumer<JsonObjectBuilder> appendPlatformDataConsumer,
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
Consumer<Runnable> submitTaskConsumer,
Supplier<Boolean> checkServiceEnabledSupplier,
BiConsumer<String, Throwable> errorLogger,
Consumer<String> infoLogger,
boolean logErrors,
boolean logSentData,
boolean logResponseStatusText) {
this.platform = platform;
this.serverUuid = serverUuid;
this.serviceId = serviceId;
this.enabled = enabled;
this.appendPlatformDataConsumer = appendPlatformDataConsumer;
this.appendServiceDataConsumer = appendServiceDataConsumer;
this.submitTaskConsumer = submitTaskConsumer;
this.checkServiceEnabledSupplier = checkServiceEnabledSupplier;
this.errorLogger = errorLogger;
this.infoLogger = infoLogger;
this.logErrors = logErrors;
this.logSentData = logSentData;
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
// WARNING: Removing the option to opt-out will get your plugin banned from bStats
startSubmitting();
}
}
public void addCustomChart(CustomChart chart) {
this.customCharts.add(chart);
}
private void startSubmitting() {
final Runnable submitTask =
() -> {
if (!enabled || !checkServiceEnabledSupplier.get()) {
// Submitting data or service is disabled
scheduler.shutdown();
return;
}
if (submitTaskConsumer != null) {
submitTaskConsumer.accept(this::submitData);
} else {
this.submitData();
}
};
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution
// of requests on the
// bStats backend. To circumvent this problem, we introduce some randomness into the initial
// and second delay.
// WARNING: You must not modify and part of this Metrics class, including the submit delay or
// frequency!
// WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it!
long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
scheduler.scheduleAtFixedRate(
submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS);
}
private void submitData() {
final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder();
appendPlatformDataConsumer.accept(baseJsonBuilder);
final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder();
appendServiceDataConsumer.accept(serviceJsonBuilder);
JsonObjectBuilder.JsonObject[] chartData =
customCharts.stream()
.map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors))
.filter(Objects::nonNull)
.toArray(JsonObjectBuilder.JsonObject[]::new);
serviceJsonBuilder.appendField("id", serviceId);
serviceJsonBuilder.appendField("customCharts", chartData);
baseJsonBuilder.appendField("service", serviceJsonBuilder.build());
baseJsonBuilder.appendField("serverUUID", serverUuid);
baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION);
JsonObjectBuilder.JsonObject data = baseJsonBuilder.build();
scheduler.execute(
() -> {
try {
// Send the data
sendData(data);
} catch (Exception e) {
// Something went wrong! :(
if (logErrors) {
errorLogger.accept("Could not submit bStats metrics data", e);
}
}
});
}
private void sendData(JsonObjectBuilder.JsonObject data) throws Exception {
if (logSentData) {
infoLogger.accept("Sent bStats metrics data: " + data.toString());
}
String url = String.format(REPORT_URL, platform);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("User-Agent", "Metrics-Service/1");
connection.setDoOutput(true);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
outputStream.write(compressedData);
}
StringBuilder builder = new StringBuilder();
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
if (logResponseStatusText) {
infoLogger.accept("Sent data to bStats and received response: " + builder);
}
}
/** Checks that the class was properly relocated. */
private void checkRelocation() {
// You can use the property to disable the check in your test environment
if (System.getProperty("bstats.relocatecheck") == null
|| !System.getProperty("bstats.relocatecheck").equals("false")) {
// Maven's Relocate is clever and changes strings, too. So we have to use this little
// "trick" ... :D
final String defaultPackage =
new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'});
final String examplePackage =
new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
// We want to make sure no one just copy & pastes the example and uses the wrong package
// names
if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage)
|| MetricsBase.class.getPackage().getName().startsWith(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
/**
* Gzips the given string.
*
* @param str The string to gzip.
* @return The gzipped string.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
gzip.write(str.getBytes(StandardCharsets.UTF_8));
}
return outputStream.toByteArray();
}
}
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public abstract static class CustomChart {
private final String chartId;
protected CustomChart(String chartId) {
if (chartId == null) {
throw new IllegalArgumentException("chartId must not be null");
}
this.chartId = chartId;
}
public JsonObjectBuilder.JsonObject getRequestJsonObject(
BiConsumer<String, Throwable> errorLogger, boolean logErrors) {
JsonObjectBuilder builder = new JsonObjectBuilder();
builder.appendField("chartId", chartId);
try {
JsonObjectBuilder.JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
builder.appendField("data", data);
} catch (Throwable t) {
if (logErrors) {
errorLogger.accept("Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return builder.build();
}
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
/**
* An extremely simple JSON builder.
*
* <p>While this class is neither feature-rich nor the most performant one, it's sufficient enough
* for its use-case.
*/
public static class JsonObjectBuilder {
private StringBuilder builder = new StringBuilder();
private boolean hasAtLeastOneField = false;
public JsonObjectBuilder() {
builder.append("{");
}
/**
* Appends a null field to the JSON.
*
* @param key The key of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendNull(String key) {
appendFieldUnescaped(key, "null");
return this;
}
/**
* Appends a string field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String value) {
if (value == null) {
throw new IllegalArgumentException("JSON value must not be null");
}
appendFieldUnescaped(key, "\"" + escape(value) + "\"");
return this;
}
/**
* Appends an integer field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int value) {
appendFieldUnescaped(key, String.valueOf(value));
return this;
}
/**
* Appends an object to the JSON.
*
* @param key The key of the field.
* @param object The object.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject object) {
if (object == null) {
throw new IllegalArgumentException("JSON object must not be null");
}
appendFieldUnescaped(key, object.toString());
return this;
}
/**
* Appends a string array to the JSON.
*
* @param key The key of the field.
* @param values The string array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values)
.map(value -> "\"" + escape(value) + "\"")
.collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an integer array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an object array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends a field to the object.
*
* @param key The key of the field.
* @param escapedValue The escaped value of the field.
*/
private void appendFieldUnescaped(String key, String escapedValue) {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
if (key == null) {
throw new IllegalArgumentException("JSON key must not be null");
}
if (hasAtLeastOneField) {
builder.append(",");
}
builder.append("\"").append(escape(key)).append("\":").append(escapedValue);
hasAtLeastOneField = true;
}
/**
* Builds the JSON string and invalidates this builder.
*
* @return The built JSON string.
*/
public JsonObject build() {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
JsonObject object = new JsonObject(builder.append("}").toString());
builder = null;
return object;
}
/**
* Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
*
* <p>This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
* Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
*
* @param value The value to escape.
* @return The escaped value.
*/
private static String escape(String value) {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '"') {
builder.append("\\\"");
} else if (c == '\\') {
builder.append("\\\\");
} else if (c <= '\u000F') {
builder.append("\\u000").append(Integer.toHexString(c));
} else if (c <= '\u001F') {
builder.append("\\u00").append(Integer.toHexString(c));
} else {
builder.append(c);
}
}
return builder.toString();
}
/**
* A super simple representation of a JSON object.
*
* <p>This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
* allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
* JsonObject)}.
*/
public static class JsonObject {
private final String value;
private JsonObject(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
}
}
+129
View File
@@ -0,0 +1,129 @@
#配置文件版本,请勿修改
version: 1.8.2
#普通末影龙
normal-dragon:
name: '末影龙'
#被末影水晶治疗时,末影龙每秒的回血量(原版末影龙每秒回复2点生命)
crystal-heal: 2.0
#特殊末影龙
special-dragon:
name: '特殊末影龙'
#每几次有概率复活出特殊的末影龙
#千万不要设置该项为0!!!!!否则会引发报错
#如果不希望复活出特殊末影龙,请设置下方的概率为0
respawn-circle: 10
#生成特殊末影龙的概率,请填入0-100的整数(单位:%)
chance: 100
#生成特殊龙后全服提醒
spawn-remind: true
#若设置为false代表普通末影龙死亡也会全服播报
#若设置为true代表仅特殊末影龙死亡会全服播报
only-special-death-remind: false
#特殊末影龙最大血量
#请填入大于0的数字,否则会按原版末影龙血量生成
#示例:“100”、“1000.0”、“200.00”
#TIPS:原版末影龙血量为200
max-health: 400
#特殊末影龙生成时血量
#请填入大于0的数字,否则会按特殊末影龙最大血量生成
#请勿设置此值大于特殊龙最大血量(max-health)
spawn-health: 200
#特殊龙对玩家伤害倍数
#请输入大于0的数字,否则会按原版末影龙伤害计算
#示例:“0.8”、“1.25”、“3”
damage-multiply: 1.0
#特殊龙攻击玩家对玩家造成的药水效果
#请严格安装'药水效果 持续时间 药水等级'的格式
#持续时间请填入大于0的整数(单位:秒)
#药水等级取值1-256的整数,输入1即代表1级,与effect指令不同!
attack-effect:
- 'wither 10 1'
- 'hunger 60 1'
- 'weakness 30 2'
#特殊末影龙掉落经验值
#TIPS:原版第一次击杀末影龙掉落12000经验,此后每一次击杀掉落500经验(请填入整数)
exp-drop: 12000
#龙蛋生成设置
dragon-egg-spawn:
#生成龙蛋概率
#请填入0-100的整数(单位:%)
#填入0代表不生成龙蛋
chance: 100
#生成龙蛋延迟
#末影龙死后多少时间生成龙蛋(单位:游戏刻,20游戏刻=1秒)
#填入大于或等于0的整数
#末影龙死后大约20秒(400游戏刻)后末地传送门生成,因此加一个延迟可以形成龙蛋和传送门几乎一起出现的效果
#此外,如果在龙蛋生成前玩家在下方放了火把,床之类的方块,龙蛋下落后会直接形成掉落物回主世界,加一个延迟可以减少不必要的麻烦
delay: 410
#龙蛋生成坐标(末地)
#y轴设置70一般没问题,想精确的话请依照自己服务器情况而定
x: 0
y: 70
z: 0
#特殊龙发光的颜色
#若要关闭发光功能,设置该项为“disable”
#所有可用颜色如下(不区分大小写)
#AQUA-青色,BLACK-黑色,BLUE-蓝色,DARK_AQUA-深青,DARK_BLUE-深蓝,DARK_GRAY-深灰,DARK_GREEN-深绿,DARK_PURPLE-深紫,DARK_RED-深红,GOLD-金色,GRAY-灰色,GREEN-绿色,LIGHT_PURPLE-紫色,RED-红色,WHITE-白色,YELLOW-黄色
glow-color: GOLD
#玩家攻击末影龙时伤害显示
#填入0代表不显示
#填入1代表显示在副标题栏(会有略微视野遮挡)
#输入2代表显示在聊天栏(可能会刷屏)
#输入3代表显示在ActionBar(推荐)
damage-visible: 3
#指令/ed drop gui 打开的页面的标题
drop-gui-title: '§b特殊龙掉落物§r'
#被末影水晶治疗时,特殊龙每秒的回血量(原版末影龙被水晶治疗时每秒回复2点生命)
crystal-heal: 5.0
#特殊龙对其他生物造成伤害后吸血
#吸血量=伤害*吸血率(自动按百分数计算)+基础吸血量
suck-blood:
enable: true
#设置为true时,特殊龙攻击其他生物时不吸血
only-player: true
#吸血率(单位:%)
rate: 50
#基础吸血量
base-suck-blood: 10
command:
#是否启用在以下生物死亡后执行特定指令
#以下指令均可用%player%代表击杀末影龙的玩家的名字
#如果玩家从未造成过有效攻击(比如一直用床炸而从未亲自攻击),涉及%player%的这条指令将被忽略
#举例:- 'give %player% diamond 1'
enable: false
#普通末影龙
normal-dragon:
- ''
- ''
- ''
#特殊龙
special-dragon:
- ''
- ''
- ''
+10
View File
@@ -0,0 +1,10 @@
#配置文件版本,请勿修改
version: 1.8.1
# 已生成过的末影龙数量
# 如果你安装本插件时,末影龙已被杀死过,请自行修改这个值(请填入整数)
# 指令召唤和指令杀死的末影龙不会被计入次数
# 注意:插件开始使用后不需再修改这个值
times: 1
items: []
+31
View File
@@ -0,0 +1,31 @@
#配置文件版本,请勿修改
version: 1.8
#消息提示前缀
prefix: '§7[§eEnderDragon§7]§r '
#末影龙生成时全服提示内容
#仅在此处有效:可用%times%代替这条末影龙的数字序号,输入“none”代表不提示
dragon-spawn-broadcast: '§a第 %times% 条末影龙已被复活'
#生成特殊龙后全服提醒内容
#只有当上一个值为true时,该提醒内容才有效
special-broadcast: '§6这条末影龙散发着神秘的气息...'
#末影龙被击杀时全服提示内容
#仅在此处有效:可用%times%代替这条末影龙的数字序号,可用%player%代替击杀末影龙的玩家的名字
#插件会智能检测周围的玩家来替换%player%,如果实在检测不到将用下方条目中的内容替代%player%
dragon-killing-broadcast: '§b第 %times% 条末影龙已被 %player% 击杀'
#无玩家参与时,%player%将被该项代替
nobody-kill: '玩家远程'
#发给击杀特殊龙的玩家的话
killer-message: '§6恭喜你,这条龙掉落了龙蛋和大量经验!'
#每次玩家攻击末影龙时,显示给攻击者的提示
#仅在此处有效:可用%damage%代替伤害数值
damage-display: '对末影龙的攻击伤害:%damage%'
#特殊末影龙成功掉落物品,但是击杀者背包已满时,显示给击杀者的提示
player-inv-full: '§c您的背包没有空余格子,特殊末影龙战利品可能已掉落在您脚下'
+23
View File
@@ -0,0 +1,23 @@
name: EnderDragon
version: 1.8.2
main: xanadu.enderdragon.EnderDragon
api-version: 1.13
#若要将插件装在低于1.13版本的服务器,请修改api-version的值为相应版本号
#举例:如果我要装在1.12.2的服务器,那么设置如下:
#api-version: 1.12
commands:
enderdragon:
description: '插件主指令'
aliases: [ed]
permission: ''
permission-message: '§4你没有使用该命令的权限'
permissions:
ed.reload:
description: '允许重载插件配置文件'
default: op
ed.drop.change:
description: '允许修改特殊龙掉落物'
default: op
ed.drop.gui:
description: '允许查看特殊龙掉落物'
default: true