ChunkPreloader.java v1.5
圆环与区域支持、更快批量、紧急终止和chunk生成控制台输出
命令列表
- preload区域或圆环区块预生成:/preload x1 z1 x2 z2 <唯一识别码> 或 /preload r 最小半径 最大半径 <唯一识别码>
- preloadset设置每tick生成区块最大数量 /preloadset <数量>
- trunlod查询/终止预生成任务 /trunlod now <唯一识别码> /trunlod stop <唯一识别码>
package com.chunkpreloader;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @pluginName ChunkPreloader
* @author 预加载区块
* @version 1.5
* @description 圆环与区域支持、更快批量、紧急终止和chunk生成控制台输出
* [command]preload|区域或圆环区块预生成:/preload x1 z1 x2 z2 <唯一识别码> 或 /preload r 最小半径 最大半径 <唯一识别码>[/command]
* [command]preloadset|设置每tick生成区块最大数量 /preloadset <数量>[/command]
* [command]trunlod|查询/终止预生成任务 /trunlod now <唯一识别码> /trunlod stop <唯一识别码>[/command]
* [Permission]chunkpreloader.use|可以使用所有相关命令与设置[/Permission]
*/
public class ChunkPreloader extends JavaPlugin {
private final Map<String, PreloadTask> tasks = new ConcurrentHashMap<>();
private final Queue<String> taskQueue = new LinkedList<>();
private int chunksPerTick = 100; // 默认极高速度
private BukkitRunnable mainTask;
@Override
public void onEnable() {
getLogger().info("ChunkPreloader已启用。");
mainTask = new BukkitRunnable() {
@Override
public void run() {
int total = 0;
while (total < chunksPerTick && !taskQueue.isEmpty()) {
String code = taskQueue.peek();
PreloadTask pt = tasks.get(code);
if (pt == null) {
taskQueue.poll();
continue;
}
boolean finished = pt.tickOnce(Math.min(chunksPerTick - total, chunksPerTick));
total += pt.lastBatchAmount;
if (finished) {
tasks.remove(code);
taskQueue.poll();
}
}
}
};
mainTask.runTaskTimer(this, 1L, 1L);
}
@Override
public void onDisable() {
if (mainTask != null) mainTask.cancel();
for (PreloadTask t : tasks.values()) {
t.forceStop("插件关闭,任务被终止。");
}
tasks.clear();
taskQueue.clear();
getLogger().info("ChunkPreloader已关闭。");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("preloadset")) {
if (!sender.hasPermission("chunkpreloader.use")) {
sender.sendMessage("§c你没有权限设置并发参数。");
return true;
}
if (args.length != 1) {
sender.sendMessage("§e用法: /preloadset <每tick区块生成数量>(推荐100~1000,高于1000风险巨大)");
return true;
}
try {
int val = Integer.parseInt(args[0]);
if (val < 1) {
sender.sendMessage("§c数量必须大于0。");
return true;
}
if (val > 1000) {
sender.sendMessage("§c警告:高于1000极易卡死/拖死服务器,仅在十分明白后果时才设定!");
}
chunksPerTick = val;
sender.sendMessage("§a已设定每tick生成区块数量为: §e" + val);
} catch (Exception e) {
sender.sendMessage("§c请输入合法整数。");
}
return true;
}
// preload命令
if (command.getName().equalsIgnoreCase("preload")) {
if (!sender.hasPermission("chunkpreloader.use")) {
sender.sendMessage("§c你没有权限使用此命令。");
return true;
}
if (args.length == 0) {
sender.sendMessage("§c用法: /preload <x1> <z1> <x2> <z2> <识别码> 或 /preload r <最小半径> <最大半径> <识别码>");
return true;
}
// 圆环模式
if (args[0].equalsIgnoreCase("r")) {
if (args.length < 3 || args.length > 4) {
sender.sendMessage("§c用法: /preload r <最小半径> <最大半径> <唯一识别码>(可选)");
return true;
}
double minRadius, maxRadius;
try {
minRadius = Double.parseDouble(args[1]);
maxRadius = Double.parseDouble(args[2]);
} catch (Exception e) {
sender.sendMessage("§c无效半径,请输入数字!");
return true;
}
if (minRadius < 0 || maxRadius <= minRadius) {
sender.sendMessage("§c最大半径需大于最小半径,且最小半径需>=0!");
return true;
}
String code;
if (args.length == 4) {
code = args[3];
if (tasks.containsKey(code)) {
sender.sendMessage("§c任务识别码已存在,请更换一个!");
return true;
}
} else {
code = "TASKR" + System.currentTimeMillis() % 100000 + new Random().nextInt(9999);
while (tasks.containsKey(code)) {
code = "TASKR" + System.currentTimeMillis() % 100000 + new Random().nextInt(9999);
}
sender.sendMessage("§a未指定唯一识别码,已自动生成: §e" + code);
}
World world = null;
if (sender instanceof Player) {
world = ((Player) sender).getWorld();
} else if (!Bukkit.getWorlds().isEmpty()) {
world = Bukkit.getWorlds().get(0);
}
if (world == null) {
sender.sendMessage("§c无法确定世界。");
return true;
}
sender.sendMessage("§e正在多线程计算所有区块列表(不影响主线程),请稍后……");
// 异步圆环chunk坐标计算
new Thread(() -> {
Set<String> chunkSet = new HashSet<>();
int minChunkX = (int)Math.floor((-maxRadius) / 16);
int maxChunkX = (int)Math.ceil((maxRadius) / 16);
int minChunkZ = (int)Math.floor((-maxRadius) / 16);
int maxChunkZ = (int)Math.ceil((maxRadius) / 16);
double minR2 = minRadius * minRadius;
double maxR2 = maxRadius * maxRadius;
// 在这里遍历chunk坐标
for (int cx = minChunkX; cx <= maxChunkX; cx++) {
for (int cz = minChunkZ; cz <= maxChunkZ; cz++) {
double wx = cx * 16 + 8;
double wz = cz * 16 + 8;
double dist2 = wx * wx + wz * wz;
if (dist2 >= minR2 && dist2 <= maxR2) {
// 控制台输出关键点判断
if (cx == (-595606 >> 4) && cz == (803270 >> 4)) {
System.out.println("[ChunkPreloader] 尝试生成 x=-595606 z=803270 (chunk:" + cx + "," + cz +")");
}
chunkSet.add(cx + "," + cz);
}
}
}
List<int[]> chunkList = new ArrayList<>();
for (String cc : chunkSet) {
String[] arr = cc.split(",");
chunkList.add(new int[]{Integer.parseInt(arr[0]), Integer.parseInt(arr[1])});
}
int total = chunkList.size();
Bukkit.getScheduler().runTask(this, () -> {
sender.sendMessage("§a圆环任务已准备好: §e" + code +
" §f以(0,0)为圆心,半径[" + minRadius + " ~ " + maxRadius +
"],共§e" + total + "§a个区块");
sender.sendMessage("§6注意: 区块生成会极其暴力批量进行,有风险极度拖死服务器,请时刻关注TPS和内存!");
PreloadTask task = new PreloadTask(code, world, chunkList, sender, total);
tasks.put(code, task);
taskQueue.offer(code);
});
}).start();
return true;
}
// 区域模式略(与上次版本相同,为节约篇幅略去)
// ...
// 此处省略非圆环区域生成逻辑(同上一版本没有修改)
}
// trunlod命令略(与上次版本相同)
// ...
return false;
}
private static class PreloadTask {
final String code;
final World world;
final List<int[]> chunkList;
final CommandSender sender;
final int totalChunks;
volatile int done = 0;
boolean completed = false;
int progressNotice = 0;
int lastBatchAmount = 0;
PreloadTask(String code, World world, List<int[]> chunkList, CommandSender sender, int total) {
this.code = code;
this.world = world;
this.chunkList = chunkList;
this.sender = sender;
this.totalChunks = total;
}
public boolean tickOnce(int batch) {
if (completed) return true;
lastBatchAmount = 0;
int end = Math.min(done + batch, chunkList.size());
for (int i = done; i < end; i++) {
int[] cur = chunkList.get(i);
int cx = cur[0], cz = cur[1];
if (!world.isChunkLoaded(cx, cz)) {
world.loadChunk(cx, cz, true);
}
lastBatchAmount++;
}
done += lastBatchAmount;
int pct = done * 100 / totalChunks;
if (pct / 10 > progressNotice / 10) {
sender.sendMessage("§e[" + code + "]进度: " + done + "/" + totalChunks + " (" + pct + "%)...");
progressNotice = pct;
}
if (done >= chunkList.size()) {
completed = true;
sender.sendMessage("§b[" + code + "]区块预生成全部完成!");
return true;
}
return false;
}
public void forceStop(String reason) {
completed = true;
if (sender != null) sender.sendMessage("§c[" + code + "]预生成任务被终止: " + reason);
}
}
}