java - 传送到下一个玩家

标签 java bukkit

我正在开发一个 Spigot 1.8.9 插件,并试图添加一个功能,当工作人员右键单击一个项目时,它会将他们传送到下一个没有消失的玩家而不是他们自己,如果没有,它应该返回空。

单击时,我尝试使用以下方法将所有可能的用户添加到列表中

public static List<User> getPossibleUsers(User user){
    List<User> result = new ArrayList<>();
    for(User target : users)
        if(!target.isVanished() && !user.getUUID().equals(target.getUUID()))
            result.add(target);
    return result;
}

员工还被分配了一个名为 nextPlayer 的整数。当他们登录时设置为0。然后当他们单击时,我将一个添加到 int 以便下次他们单击它时可以获得下一个用户。
private User getNextPlayer(User user) {
    int next = user.nextPlayer;
    List<User> users = getPossibleUsers(user);
    if(users.size() == 0)
        return null;
    int current = 0;
    for(User target : users) {
        if(current == next){
           return target;
        }
        current++;
    }
    user.nextPlayer = next;
}

问题是我不知道如何正确制作 getNextPlayer 方法并使其高效。我也想这样做,一旦它击中最后一个玩家,它就会循环回第一个玩家。

最佳答案

如果您希望它高效,我建议您以完全不同的方式思考您的问题,但在这种情况下,效率确实不是问题,所以我选择不过早地优化,而是使用您已经拥有的代码.

public static List<User> getPossibleUsers(User user){
    List<User> result = new ArrayList<>();
    for(User target : users)
        if(!target.isVanished() && !user.getUUID().equals(target.getUUID()))
            result.add(target);
    return result;
}

这当前以相同的顺序返回用户,因为它们是在用户上定义的。

这最好有一个自然的排序顺序,否则当人们加入/离开服务器时你会遇到问题,因为它会导致人们改变他们在列表中的顺序。

现在让我们回到第一任校长。
    int next = user.nextPlayer;

看起来您正在将播放器的索引存储在您已经在“用户”中的列表中。

一旦你有了这个,你就可以直接从列表中访问该索引。

https://docs.oracle.com/javase/8/docs/api/java/util/List.html#get-int-
E get(int index)
所以,做 users.get(next++);这就是“修复”上面代码所需要做的全部工作。它下一步递增,并在该位置获取用户(假设排序是一致的,并且没有改变)但是,如果它超出列表的范围,它可能会抛出异常,因此我们将其包装在
if(next <= users.length) {
    users.get(next++);
} else return null;

如果否则会引发异常,这会将其更改为返回 null。

但是 所有这些仍然有一个致命的缺陷,如果列表在调用之间发生变异,你可能会跳过或改变顺序。

对此更好的解决方案是缓存访问过的用户以及上次访问的用户。

如果用户已排序,并且您存储上次访问的用户而不是索引,则您存储的数据对更改的弹性要大得多,并且与您想要的行为更匹配。

为了更紧密地满足您的需求,您正在询问。
  • 生成一个可预测的、有序的用户列表,其中不包括管理员或消失的任何其他人,以帮助管理员预测他们要去哪里。
  • 通过使用工具右键单击,在此列表中旋转,(注意这是异步的,因此需要保存所有状态)
  • 确保在重复序列之前访问了所有访问过的用户。
  • public class TeleportTooldata {
        private ListIterator<UUID> cursor;
        private List<UUID> cachedOrder;
    
        public TeleportTooldata(List<UUID> applicableUsers) {
            cachedOrder = applicableUsers;
        }
    
        @Nullable
        public UUID next() {
            if (!cursor.hasNext()) return null;
            UUID next = cursor.next();
            if (!cachedOrder.contains(next)) {
                cachedOrder.add(next);
            }
            return next;
        }
    
        public void Update(List<UUID> applicableUsers) {
            applicableUsers.removeAll(cachedOrder);
            cachedOrder.addAll(applicableUsers);
        }
    }
    
    public class TeleportToolUtil {
        YourPluginUserRepo repo;
        Map<User, TeleportTooldata> storage; //This could be a cache, make sure to remove if they log out, or maybe timed as well.
    
        public List<UUID> getApplicableUsers() {
            return repo.getOnlineUsers().stream()
                    .filter(User::isVanish)
                    .sorted(Comparator.comparing(User::getId)) // You can change the sort order
                    .map(User::getId)
                    .collect(Collectors.toList());
        }
    
        public void onToolUse(User user) {
            TeleportTooldata data = storage.computeIfAbsent(user, x -> new TeleportTooldata(getApplicableUsers()));
            UUID next = data.next();
            if (next == null) {
                data.Update(getApplicableUsers());
                next = data.next();
                    if(next == null) {
                    storage.put(user, new TeleportTooldata(getApplicableUsers()));
                    next = data.next();
                }
            }
            user.teleportTo(next);
        }
    }
    

    一些变化。
  • 我们现在正在缓存排序,以便您在概念上也可以让用户向后浏览列表。
  • 我们正在使用 ListIterator。 ListIterator 是一个循环遍历列表的对象,并为您存储当前位置!就像您之前所做的那样,但没有索引。
  • 我们现在有可能更新数据,以防玩家迟到或有人消失,如果他们不在列表中,他们将被放在列表的后面。
  • 当我们用完用户时,我们尝试更新,如果我们真的用完了,我们会重新开始一个全新的列表。 (请注意,这并不能保证每次都具有相同的顺序(如果之前已附加,人们将在更新时“正确”排序,但对于此用例来说已经足够接近了)

  • 然而!我们仍然需要注意内存泄漏。使用 UUID 而不是玩家或用户,意味着这个类是非常轻量级的,我们应该非常安全地避免 UUID 列表中的内存泄漏,因为 TeleportTooldata 不会存活太久。

    您可以将 TeleportTooldata 的 Map 替换为缓存(可能来自 Guava?),以便在管理员离开游戏一段时间后移除数据。

    如果 TeleportTooldata 预计是长期存在的,我们会认真考虑从历史记录中删除 UUID。

    此外,在我的示例中未处理的是用户在缓存订单后离线的可能性。

    为了解决这个问题,在传送玩家之前,检查 uuid 是否在线,否则转到“下一步”并再次遵循所有相同的逻辑。

    关于java - 传送到下一个玩家,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50731940/

    相关文章:

    python -/: 'unicode' and 'int' 不支持的操作数类型

    java - Bukkit 前缀和后缀

    java - 删除IntelliJ中的 "Project SDK"配置

    java - bukkit 插件《我的世界》箱子用列表中的 int 填充并从类构建

    java - 将照片上传到 Google Photos API 不返回上传 token

    java - T 扩展 SomeClass 的意义何在?

    java - 在 Bukkit 中使用多个类作为命令?

    java - Minecraft Bukkit Java 执行命令时出错

    java - 如何从线程更新黑莓 UI 项目?

    Java 数树 For 循环