本帖最后由 a1294790523 于 2020-6-9 13:50 编辑
ChunksExplorerLimiter
跑图克星
还在担心玩家跑图吗?这个插件提供了通过限制跑图区块来限制玩家跑图的插件,尽情使用吧!
(温馨提示:在使用世界生成/预加载工具时请将该世界移出包含列表,否则将导致误报)
插件截图:
更新日志:
v1.8
解决了不会提示跑图警告信息的bug,放宽踢出时的延迟,感谢huzpsb的提醒
v1.7 解决了CME,不再存储玩家跑图信息,支持1.7.10并添加查看跑图信息的指令
v1.6
重大bug修复,修复了跑图被踢出后不会再被踢的bug
v1.5
再再次根据@Karlatemp的建议修复了特定情况下不会警告的问题
v1.4
细微级别的优化
v1.3
再次根据@Karlatemp的建议进行修改并使用了之前忘记使用的遍历玩家的方式,非常感谢!
v1.2
根据@Karlatemp的建议进行修改,同时修复bug
v1.1
修复了重载导致清理任务重复的bug
|
指令:
以下命令权限都为cel.use,默认op拥有
/cel reload——重载插件配置
/cel stats——查看玩家跑图信息
配置文件(区块数量限制建议根据服务器实际情况调整):
- #配置版本号,请勿修改
- version: 2
- #每个玩家跑图区块数量的限制
- chunksLimit: 500
- #玩家跑图区块数量的重置时间(单位为秒)
- cleanTime: 3600
- #生效的世界
- worlds:
- - world
- #提示信息的前缀
- prefix: '§6[提示] '
- #达到目标区块数量后踢出的信息
- chunksLimitKickMessage: '你已到达跑图上限,请一小时后再试'
- #区块数量到达百分之多少时才会提示
- warningRate: 0.8
- #提示信息
- chunksLimitWarningMessage: '你即将到达跑图上限,请不要再跑图'
复制代码
|
源代码(使用CC0协议):
- package sandtechnology.chunksexplorerlimiter;
- import org.bukkit.Bukkit;
- import org.bukkit.Location;
- import org.bukkit.command.Command;
- import org.bukkit.command.CommandExecutor;
- import org.bukkit.command.CommandSender;
- import org.bukkit.event.EventHandler;
- import org.bukkit.event.EventPriority;
- import org.bukkit.event.Listener;
- import org.bukkit.event.world.ChunkPopulateEvent;
- import org.bukkit.plugin.PluginManager;
- import org.bukkit.plugin.java.JavaPlugin;
- import java.util.Comparator;
- import java.util.List;
- import java.util.Map;
- import java.util.UUID;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.stream.Collectors;
- //v1.2 感谢@Karlatemp的建议,已进行修改
- //v1.3 再次根据@Karlatemp的建议进行修改
- //v1.4 再再次根据@Karlatemp的建议进行修改
- //v1.5 再再再次根据@Karlatemp的建议进行修改
- //v1.7 解决了CME,不再存储玩家跑图信息,支持1.7.10并添加查看跑图信息的指令
- //v1.8 解决了不会提示跑图警告信息的bug,放宽踢出时的延迟,感谢huzpsb的提醒
- public class ChunksExplorerLimiter extends JavaPlugin implements CommandExecutor, Listener {
- private final Map<UUID, AtomicInteger> chunksExplorerMap = new ConcurrentHashMap<>();
- private String prefix;
- private String chunksLimitKickMessage;
- private int chunksLimit;
- private String chunksLimitWarningMessage;
- private List<String> worlds;
- private double warningRate;
- public ChunksExplorerLimiter() {
- }
- @Override
- public void onEnable() {
- saveDefaultConfig();
- settingVars();
- getCommand("CEL").setExecutor(this);
- Bukkit.getScheduler().runTaskTimerAsynchronously(this, chunksExplorerMap::clear, 0, getConfig().getLong("cleanTime") * 20);
- Bukkit.getPluginManager().registerEvents(this, this);
- }
- private void settingVars() {
- if (getConfig().getInt("version") == 1) {
- getConfig().set("warningRate", 0.8);
- getConfig().set("version", 2);
- saveConfig();
- }
- prefix = getConfig().getString("prefix");
- chunksLimit = getConfig().getInt("chunksLimit");
- worlds = getConfig().getStringList("worlds");
- warningRate = getConfig().getDouble("warningRate");
- chunksLimitWarningMessage = getConfig().getString("chunksLimitWarningMessage");
- chunksLimitKickMessage = getConfig().getString("chunksLimitKickMessage");
- }
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- if (args.length == 1) {
- if (args[0].equalsIgnoreCase("reload")) {
- //不直接调用onDisable和onEnable方法
- PluginManager pluginManager = getServer().getPluginManager();
- pluginManager.disablePlugin(this);
- pluginManager.enablePlugin(this);
- sender.sendMessage("§a" + prefix + "重载完成!");
- return true;
- } else if (args[0].equalsIgnoreCase("stats")) {
- Bukkit.getScheduler().runTaskAsynchronously(this, () ->
- sender.sendMessage("§a跑图数据如下:\n" + chunksExplorerMap.entrySet().stream().sorted(Comparator.comparingInt(e -> e.getValue().get())).limit(10).map(e -> getServer().getOfflinePlayer(e.getKey()).getName() + ":" + e.getValue().get() + "个区块").collect(Collectors.joining("\n"))));
- return true;
- }
- }
- sender.sendMessage("命令帮助:" +
- "\n /cel reload ——重载插件" +
- "\n /cel stats ——查看Top10跑图信息");
- return true;
- }
- @Override
- public void onDisable() {
- }
- @EventHandler(priority = EventPriority.HIGHEST)
- public void onChunkGenerated(ChunkPopulateEvent event) {
- if (!worlds.contains(event.getChunk().getWorld().getName())) {
- return;
- }
- //获取每个玩家距离生成区块的距离,选取最近一个玩家
- Location location = event.getChunk().getBlock(7, 127, 7).getLocation();
- event.getChunk().getWorld().getPlayers().stream().min(Comparator.comparingDouble(player -> player.getLocation().distanceSquared(location))).ifPresent(
- player -> {
- UUID uuid = player.getUniqueId();
- int current = chunksExplorerMap.computeIfAbsent(uuid, (key) -> new AtomicInteger(0)).incrementAndGet();
- //强转int截断取整避免浮点数
- if ((int) (chunksLimit * warningRate) == current) {
- player.sendMessage(prefix + chunksLimitWarningMessage);
- }
- if (current >= chunksLimit) {
- Bukkit.getScheduler().runTaskLater(this, () ->
- {
- if (player.isOnline()) {
- player.kickPlayer(prefix + chunksLimitKickMessage);
- }
- }, 3);
- }
- }
- );
- }
- }
复制代码 |
本插件所用所有代码均为原创,不存在借用/抄袭等行为
你跑你

呢(草)
- import com.google.common.cache.Cache;
- import com.google.common.cache.CacheBuilder;
- import com.google.common.cache.CacheLoader;
- import com.google.common.cache.LoadingCache;
- import org.bukkit.Bukkit;
- import org.bukkit.Location;
- import org.bukkit.command.Command;
- import org.bukkit.command.CommandSender;
- import org.bukkit.entity.Player;
- import org.bukkit.event.EventHandler;
- import org.bukkit.event.EventPriority;
- import org.bukkit.event.world.ChunkPopulateEvent;
- import org.bukkit.plugin.PluginManager;
- import org.bukkit.plugin.java.JavaPlugin;
- import org.jetbrains.annotations.NotNull;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.IOException;
- import java.nio.charset.StandardCharsets;
- import java.nio.file.Files;
- import java.nio.file.OpenOption;
- import java.nio.file.StandardOpenOption;
- import java.util.*;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.logging.Level;
- public class AKM extends JavaPlugin {
- public void onEnable() {
- try {
- if (Files.notExists(dataPath)) {
- Files.write(dataPath, new byte[]{'{', '}'}); // 直接写byte他不香吗 ?
- // - Files.write(dataPath, Collections.singleton("{}"), StandardCharsets.UTF_8);
- } /*// + */ else {
- // 直接使用数据流,不经过String, 减少内存使用+减少CPU使用
- try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
- chunksExplorerMap.putAll(gson.fromJson(reader, Map.class));
- }
- }
- // - chunksExplorerMap.putAll(gson.fromJson(String.join("\n", Files.readAllLines(getDataFolder().toPath().resolve("data.json"), StandardCharsets.UTF_8)), chunksExplorerMapType));
- } catch (IOException e) {
- // 亲爱的, Logger怎么用都8知道??
- // - getLogger().warning("玩家跑图数据加载失败!");
- // - e.printStackTrace();
- getLogger().log(Level.WARNING, "玩家跑图数据加载失败!", e);
- }
- }
- private LoadingCache<UUID, AtomicInteger> chunksExplorerMap;
- public void onDisable() {
- // 直接使用数据流,不经过String, 减少内存使用+减少CPU使用
- try /*+*/ (BufferedWriter writer = Files.newBufferedWriter(dataPath, StandardCharsets.UTF_8)) {
- // Files.write(dataPath, Collections.singleton(gson.toJson(chunksExplorerMap)), StandardCharsets.UTF_8);
- gson.toJson(chunksExplorerMap, writer);
- } catch (IOException e) {
- // - getLogger().warning("玩家跑图数据保存失败!");
- // - e.printStackTrace();
- getLogger().log(Level.WARNING, "玩家跑图数据保存失败!", e);
- }
- //... chunksExplorerMap Initialize
- chunksExplorerMap = CacheBuilder.newBuilder().expireAfterAccess(
- getConfig().getLong("cleanTime"), TimeUnit.SECONDS
- ).build(new CacheLoader<UUID, AtomicInteger>() {
- @Override
- public AtomicInteger load(@NotNull UUID key) throws Exception {
- return new AtomicInteger();
- }
- });
- this.Config_ChunksLimit = getConfig().getInt("chunksLimit");
- Config_ChunksWarmingLimit = (int) (Config_ChunksLimit * 0.8);
- // 由
- // if (chunksExplorerMap.get(uuid) > getConfig().getInt("chunksLimit"))
- // 推出
- Config_ChunksRealLimit = Config_ChunksLimit + 1;
- this.Config_Prefix = getConfig().getString("prefix");
- this.Config_chunksLimitWarningMessage = getConfig().getString("chunksLimitWarningMessage");
- Config_chunksLimitKickMessage = getConfig().getString("chunksLimitKickMessage");
- worlds = getConfig().getStringList("worlds");
- }
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- if (args.length == 1) {
- switch (args[0].toLowerCase()) { // Command: /ecl reload
- case "rl":
- case "reload":
- if (!sender.hasPermission("cel.reload")) {
- sender.sendMessage(command.getPermissionMessage());
- return true;
- }
- // 不要直接玩onEnable()/onDisable(), 那样的话会造成[监听器]和[各种定时/延时]没能正确注销
- PluginManager pm = Bukkit.getPluginManager();
- pm.disablePlugin(this);
- pm.enablePlugin(this);
- sender.sendMessage("§6ChunksExplorerLimiter§r>>§b 重载完成!");
- return true; // End execute
- }
- }
- // Default send version info
- if (!sender.hasPermission("cel.version")) {
- sender.sendMessage(command.getPermissionMessage());
- return true;
- }
- sender.sendMessage("§bChunksExplorerLimiter §6v1.2§r by§b a1294790523");
- return true;
- }
- private int Config_ChunksLimit; // 在onEnable中定义
- private int Config_ChunksRealLimit; // 在onEnable中定义
- private int Config_ChunksWarmingLimit;
- private String
- Config_Prefix, Config_chunksLimitWarningMessage,
- Config_chunksLimitKickMessage;
- private List<String> worlds;
- @EventHandler(priority = EventPriority.HIGHEST)
- public void onChunkGenerated(ChunkPopulateEvent event) {
- // if (getConfig().getStringList("worlds").stream().noneMatch(event.getChunk().getWorld().getName()::equals)) {
- if (!worlds.contains(event.getChunk().getWorld().getName())) {
- return;
- }
- //获取每个玩家距离生成区块的距离,选取最近一个玩家
- Location location = event.getChunk().getBlock(7, 127, 7).getLocation();
- final Optional<Player> nearest = location.getWorld().getPlayers().stream().min(Comparator.comparingDouble(
- player -> player.getLocation().distanceSquared(location))
- );
- if (!nearest.isPresent()) {
- // Player not found
- return;
- }
- Player player = nearest.get();
- // chunksExplorerMap.put(uuid, chunksExplorerMap.getOrDefault(uuid, 0) + 1);
- int current;
- try {
- current = chunksExplorerMap.get(player.getUniqueId()).incrementAndGet();
- } catch (ExecutionException error) {
- // Oh 我亲爱的上帝, 这不应该[不可能]发生
- throw new RuntimeException(error);
- }
- /*
- 真的牛逼,配置是 chunksLimit 然后源码是 [C]hunksLimit
- ==>
- if (chunksExplorerMap.get(uuid) * 0.8 ==
- if (chunksExplorerMap.get(uuid) >
- 第一句怎么也不会触发吧,触发第二句的时候就已经把人T了, 哇, 真的牛逼兄弟
- =============================================================
- if (chunksExplorerMap.get(uuid) * 0.8 == getConfig().getInt("ChunksLimit")) {
- sendMessage(uuid, getConfig().getString("chunksLimitWarningMessage"));
- }
- if (chunksExplorerMap.get(uuid) > getConfig().getInt("chunksLimit")) {
- Player player = getServer().getPlayer(uuid);
- if (player != null) {
- player.kickPlayer(Config_Prefix + getConfig().getString("chunksLimitKickMessage"));
- }
- }
- */
- if (current == Config_ChunksWarmingLimit) {
- player.sendMessage(Config_Prefix + Config_chunksLimitWarningMessage);
- // sendMessage(player, Config_chunksLimitWarningMessage);
- }
- if (current == Config_ChunksRealLimit) { // int特性, 不需要大于 / 等于
- player.kickPlayer(Config_Prefix + Config_chunksLimitKickMessage);
- }
- }
- }
复制代码 |
本帖最后由 a1294790523 于 2020-1-15 01:46 编辑
首先感谢code review,这个插件的制作时间也就几十分钟,是即兴开发出来的,bug也不少hhh
在查看你的修改中我发现我的确不了解google相关的库和logger库,但是我个人认为没必要直接将config的值直接做本地变量访问,毕竟是存储在内存内的,不过在频繁使用的情况下的确能减少错误概率并增加效率。
对于下面判断访问区块的核心代码,我觉得是存在距离相同的情况的,于是就使用limit 1的限制来判断,因为使用了foreach,也避免了判断是否存在。
也感谢你指出在reload实现中的错误,之前一直没意识到这个问题233
总之,我会根据你的建议进行修改,非常感谢!
下面是一些对你的实现的问题:
1.如果服务器在重置时间内不断重启,使用你的实现不是就不能保证插件正常重置时间吗?
2.我感觉对于插件来说,指令应该是越简单越好,不需要显示作者信息,请问你是怎么认为的呢?
3.对于json内的反序列化,直接使用Map.class会有uncheckedWarning,为什么不使用Type呢?
本帖最后由 Karlatemp 于 2020-1-15 04:12 编辑
a1294790523 发表于 2020-1-15 01:39
首先感谢code review,这个插件的制作时间也就几十分钟,是即兴开发出来的,bug也不少hhh
在查看你的修改 ...
1. 不太清楚你说的什么,按照我的解读
数据的加载在onenable里,而不是onload/构建器里,所以只要重新加载,一切内存数据都被清空
另外,那个缓存可能是有点不太合理的,你也许需要在加点修改和测试,因为review的时候是直接写的没有经过任何测试,请在数据保存这个方面在好好修改一下
另外,我个人认为你的区块时间移动记录只是一个临时值,个人认为没有必要存到磁盘,如果你要存到磁盘的话你得研究一下怎么控制cache的过期时间了
2 命令这方面看作者喜好吧,不过我个人推荐一个基础信息显示,重载加个二级参数免得有些人手贱还是**打了这个命令导致数据清空
3关于反序列化,我只是粗略的写了一下,并没有过多考虑,不过因为这个我突然发现这个type似乎穿错值了
我的review中的类型是uuid,atomic integer,所以type应该是这个
Type type = new TypeToken<Map<UUID,AtomicInteger>>(){}.getType();
这个type确实是我的疏忽吧,type类型改成上面这个就好(推荐存到 private static final)
4 另外,我几个小时前给出的代码还有下面地方的错误
tojson方法中应该要再调用一次map的asMap()方法转为jdk的map避免gson错误存储
gson.toJson(...map.asMap(), writer);
onDisable里面
//... chunksExplorerMap Initialize
以及后面一堆赋值应该在onEnable里面才对的,我放错了位置,
我看了一下你最新代码,再给出下面的建议
1。onEnable的数据加载,你的newBufferedReader没有用try-with-resource,也就是没有关闭输入流
可以看看 https://xw.qq.com/cmsid/20180725G1ULCR00
2 onDisable没有用newBufferedWriter写入,还是走了String,在小存储中没事,数据量大了额外占用的资源也会非常大,至于为什么我这么说你可以去GitHub翻gson源码
3 你的玩家处理机制,直接用min方法就行,没必要foreach
min返回的是一个optional,用isPresent确定是否有值
这个方法也只会返回一个对象,你没必要担心玩家的距离是否相等,因为这个和你的limit(1)是一样的
map的值我推荐用atomic integer,因为他封装有很多方法并且不需要用put加值
int current = map.megreIfAbsent(uuid, (a)->new AtomicInteger()).increaseAndGet();
本帖最后由 a1294790523 于 2020-1-15 12:37 编辑
已修改,忘记使用try-with-resource是我的疏忽hhhh
区块数量的记录的话我感觉是需要保存的,但在目前我的实现里面是鸡肋2333,保存主要是拿来看跑图的玩家的
经过这几次的修改,实现变得更简洁了,非常感谢!
笑死, 这要让多少小朋友哭泣
cat端踢出玩家后崩服= =
请提供崩溃日志,我估计是因为你使用了异步版本的原因
插件会检测服务器op的移动吗,比如说到处tp会不会导致op被踢?
这个插件的原理是根据是否有新区块生成来的,属于无差别攻击
所以op也会被踢,暂时不打算加入白名单的机制
非常“暴躁”且实用的插件

建议加入id白名单,看上面的cat端会崩溃,有了一点点抵触qaq

是这样的,因为跑图负载带来的是服务器的全局负载,所以就不打算加入白名单功能,因为这样的话这个插件其实也就没什么意义了
6就完事了
求1710版本的
随机传送也会被t吗
我同意让玩家们跑图,但不想频繁跑图
有没有1.7的
cat稳定版本t人之后也蹦了。。
不错哦 看好你
楼主请问一下,玩家跑图区块计算是加载的区块数量还是人经过的区块数量?
是实际生成的新区块数量,落实到玩家上取最近的玩家作为跑图玩家
我在2月20号按这个插件 一天一蹦我以为是端的问题还是地图问题 于是换端换地图 你新版的也会蹦服
t人之后也蹦了 java.util.ConcurrentModificationException
我还以为是java的问题 也换了java
1.9.4per龙头
楼主请问一下,我的服务器是catserver核心,版本1.12,我改config文件后重启插件并没有做出改变,我指我改的树枝和文字。。是不是catserver核心不兼容这个插件?