【问题标题】:Teleport to next player传送到下一个玩家
【发布时间】:2018-06-07 01:57:05
【问题描述】:

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

点击时我尝试将所有可能的用户添加到列表中

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 的 int,当他们登录时设置为 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 方法并使其高效。我也想这样做,所以一旦它击中最后一个玩家,它就会循环回到第一个玩家。

【问题讨论】:

  • 什么是用户?据我所知,Spigot 下不存在类,只有 SpongeAPI,除非它是您自己创建的类。
  • @RyanTheLeach 这是一个自定义对象类,它扩展了 Player 以在这种情况下存储额外的数据,如果用户消失了,一个 int 来保留他们的位置。
  • 我假设您实际上并不是指继承中的“扩展”播放器。
  • @RyanTheLeach 哦,是的,我的意思是在创建对象时它会保存播放器,然后我使用该播放器对象的方法。

标签: java bukkit


【解决方案1】:

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

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,否则会引发异常。

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

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

如果用户是有序的,并且您存储的是上次访问的用户,而不是索引,那么您存储的数据对更改更具弹性,并且更符合您想要的行为。

为了更紧密地满足您的需求,您提出了这样的要求。

  1. 生成一个可预测的有序用户列表,其中不包括管理员或其他任何已消失的用户,以帮助管理员预测他们的去向。

  2. 通过右键单击工具在此列表中旋转,(注意这是异步的,因此需要保存所有状态)

  3. 确保在重复序列之前访问了所有访问过的用户。

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);
    }
}

一些变化。

  1. 我们现在正在缓存排序,因此您在概念上也可以让用户在列表中倒退。
  2. 我们正在使用 ListIterator。 ListIterator 是一个遍历列表的对象,并为您存储当前位置!与您之前所做的非常相似,但没有索引。
  3. 我们现在可以更新数据,以防万一玩家加入较晚或有人消失,如果他们尚未在列表中,他们将被排在列表的最后。
  4. 当我们用完用户时,我们会尝试更新,如果我们真的用完了,我们会从一个全新的列表重新开始。 (请注意,这并不能保证每次都使用相同的顺序(如果之前添加了人员,则在更新时会“正确”排序,但对于这个用例来说已经足够接近了)

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

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

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

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

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

【讨论】:

  • 编辑:不小心留在了visitedUsers中,即使它不再被使用,并且最初从未填充过。
猜你喜欢
  • 1970-01-01
  • 2022-10-18
  • 1970-01-01
  • 1970-01-01
  • 2022-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多