Постоянная проверка радиуса

  • Автор темы Автор темы ilya50
  • Дата начала Дата начала

ilya50

Активный участник
Пользователь
Баллы
76
Как сделать так, что если к какому либо игрок подходит другой игрок в радиусе R, то происходит какое либо событие ? Те это нужно чекать постоянно, игрок может так же и выйти из его радиуса(это тоже надо обрабатывать). Как такое можно реализовать ?
 
execute @a ~ ~ ~ execute @a[r=10] ~ ~ ~ <команда>
Например так через командные блоки, где r -- радиус.
Ещё можно при помощи sheduler'а и перебора всех игроков или сущностей.
 
execute @a ~ ~ ~ execute @a[r=10] ~ ~ ~ <команда>
Например так через командные блоки, где r -- радиус.
Ещё можно при помощи sheduler'а и перебора всех игроков или сущностей.
Бесконечный поток не будет нагружать сервер ? И как часто его запускать, те с какой периодичностью ?
 
Бесконечный поток не будет нагружать сервер ? И как часто его запускать, те с какой периодичностью ?
С оптимальной. Можно запускать в отдельном цикле без задержек, тогда нагружать будет.
Всё зависит от задачи, количества игроков, действий...
 
нашёл код в интернете, если кому нибудь нужно будет пользуйтесь
int players = 0;

List<Entity> entities = player.getNearbyEntities(20,20,20);

//Iterate through the list and check if the entity is a player
for(Entity entity : entities) {
if(entity instanceof Player) {
players++;
}
}
 
нашёл код в интернете, если кому нибудь нужно будет пользуйтесь
int players = 0;

List<Entity> entities = player.getNearbyEntities(20,20,20);

//Iterate through the list and check if the entity is a player
for(Entity entity : entities) {
if(entity instanceof Player) {
players++;
}
}
Не лучше ли будет перебирать игроков, вместо сущностей, и сравнивать их локации? Просто не уверен, как именно работает метод getNearbyEntities, и если там просто некоторый перебор всех сущностей - ну такое.
Если условия задачи решать в лоб, то, навскидку, с моими "наипрямейшими" руками это вышло бы как-то так:
Java:
Map<UUID,List<UUID>> founded;
int radius;
//...
//В шедулере
List<Player> players=Bukkit.getOnlinePlayers();
for(Player pl1:players) {
    Location loc=pl1.getLocation();
    UUID id=pl1.getUniqueId();
    List<UUID> ids=founded.containsKey(id)?founded.get(id):new ArrayList<>();
    for(Player pl2:players) {
        if (pl1==pl2) continue;
        id=pl2.getUniqueId();
        if (loc.distanceSquared(pl2.getLocation())>radius) {
            if(ids.contains(id)) {
                ids.remove(id);
                // Игрок вышел из радиуса игрока
            }
        } else
        if (!ids.contains(id)) {
            ids.add(id);
            // Игрок вошел в радиус игрока
        }
    }
}
radius - изначальный радиус во второй степени.
Не учитывал выход игрока с сервера.
 
Сделал немного эффективнее, чем предыдущие варианты и с максимальными обьяснениями

P.S. Код содержит ошибки и неточности. Исправленный код сообщениями ниже.
PHP:
// Радиус лучше держать в квадрате, чтобы каждый раз не извлекать корень при подсчете расстояния
// UUID в хеш коллекциях работает быстрее чем String
public static double radius = 10000; // реальный радиус = 100
public static Map<UUID, Set<UUID>> closeDistanceMap = new HashMap<>();

@EventHandler(ignoreCancelled = true)
public void onMove(PlayerMoveEvent e) {
    UUID playerUUID = e.getPlayer().getUniqueId();
    Location to = e.getTo();
    World world = to.getWorld();

    // Опционально - фильтр, чтобы реагировать только тогда,
    // когда игрок проходит целый блок
    Location from = e.getFrom();
    if (to.getBlockX() == from.getBlockX()
            && to.getBlockY() == from.getBlockY()
            && to.getBlockZ() == from.getBlockZ()) return;

    // Проверяем всех игроков только в нужном мире
    for (Player target : world.getPlayers()) {
        UUID targetUUID = target.getUniqueId();
        // Игрок player и target в радиусе?
        if (radius <= target.getLocation().distanceSquared(to)) {
            // Были ли в радиусе раньше? (Повторений же нам не надо)
            Set<UUID> closeTargetsToPlayer = closeDistanceMap.getOrDefault(playerUUID, new HashSet<>());
            boolean wasClose = closeTargetsToPlayer.contains(targetUUID);
            // Если не были, значит добавляем, будут :)
            if (!wasClose) {
                closeTargetsToPlayer.add(targetUUID);
                closeDistanceMap.put(playerUUID, closeTargetsToPlayer);
         
                // <== ЗДЕСЬ же выполняем что нам нужно (приближение)
            }
        }
        // Игрок player и target НЕ в радиусе?
        else {
            // Были ли в радиусе раньше? (Повторений же нам опять таки не надо)
            Set<UUID> closeTargetsToPlayer = closeDistanceMap.getOrDefault(playerUUID, new HashSet<>());
            boolean wasClose = closeTargetsToPlayer.contains(targetUUID);
            // Если были, значит убираем
            if (wasClose) {
                closeTargetsToPlayer.remove(targetUUID);
                closeDistanceMap.put(playerUUID, closeTargetsToPlayer);
         
                // <== ЗДЕСЬ же выполняем что нам нужно (отдаление)
            }
        }
    }
}
 
Последнее редактирование:
Сделал немного эффективнее, чем предыдущие варианты и с максимальными обьяснениями
PHP:
// Радиус лучше держать в квадрате, чтобы каждый раз не извлекать корень при подсчете расстояния
// UUID в хеш коллекциях работает быстрее чем String
public static double radius = 10000; // реальный радиус = 100
public static Map<UUID, Set<UUID>> closeDistanceMap = new HashMap<>();

@EventHandler(ignoreCancelled = true)
public void onMove(PlayerMoveEvent e) {
    UUID playerUUID = e.getPlayer().getUniqueId();
    Location to = e.getTo();
    World world = to.getWorld();

    // Опционально - фильтр, чтобы реагировать только тогда,
    // когда игрок проходит целый блок
    Location from = e.getFrom();
    if (to.getBlockX() == from.getBlockX()
            && to.getBlockY() == from.getBlockY()
            && to.getBlockZ() == from.getBlockZ()) return;

    // Проверяем всех игроков только в нужном мире
    for (Player target : world.getPlayers()) {
        UUID targetUUID = target.getUniqueId();
        // Игрок player и target в радиусе?
        if (radius <= target.getLocation().distanceSquared(to)) {
            // Были ли в радиусе раньше? (Повторений же нам не надо)
            Set<UUID> closeTargetsToPlayer = closeDistanceMap.getOrDefault(playerUUID, new HashSet<>());
            boolean wasClose = closeTargetsToPlayer.contains(targetUUID);
            // Если не были, значит добавляем, будут :)
            if (!wasClose) {
                closeTargetsToPlayer.add(targetUUID);
                closeDistanceMap.put(playerUUID, closeTargetsToPlayer);
           
                // <== ЗДЕСЬ же выполняем что нам нужно (приближение)
            }
        }
        // Игрок player и target НЕ в радиусе?
        else {
            // Были ли в радиусе раньше? (Повторений же нам опять таки не надо)
            Set<UUID> closeTargetsToPlayer = closeDistanceMap.getOrDefault(playerUUID, new HashSet<>());
            boolean wasClose = closeTargetsToPlayer.contains(targetUUID);
            // Если были, значит убираем
            if (wasClose) {
                closeTargetsToPlayer.remove(targetUUID);
                closeDistanceMap.put(playerUUID, closeTargetsToPlayer);
           
                // <== ЗДЕСЬ же выполняем что нам нужно (отдаление)
            }
        }
    }
}
А что будет в случае, если один игрок стоит, и уже после попадания в радиус другого(ходячего) игрока начнет двигаться? Думаю, стоит ещё "обновлять" в мапе список id для target, добавляя в/убирая из него ходячего игрока.
 
Последнее редактирование:
Еще пара советов:
Заменить getOrDefault на computeIfAbsent(k -> new HashSet<>()); и вынести из обеих веток до условия. put в мапу ненужен.
Убрать полность .contains(). Оба метода, add и remove, уже возвращают нужные булеаны.
Причем в твоем коде они даже станут ненужны, а если править set у другого игрока, то нужны
 
А что будет в случае, если один игрок стоит, и уже после попадания в радиус другого(ходячего) игрока начнет двигаться? Думаю, стоит ещё "обновлять" в мапе список id для target, добавляя в/убирая из него ходячего игрока.
Еще пара советов:
Заменить getOrDefault на computeIfAbsent(k -> new HashSet<>()); и вынести из обеих веток до условия. put в мапу ненужен.
Убрать полность .contains(). Оба метода, add и remove, уже возвращают нужные булеаны.
Причем в твоем коде они даже станут ненужны, а если править set у другого игрока, то нужны
Принял, обновил. Добавил новые события. Исправил еще некоторые детали.
PHP:
// Радиус лучше держать в квадрате, чтобы каждый раз не извлекать корень при подсчете расстояния
// UUID в хеш коллекциях работает быстрее чем String
public static double radius = 10000; // реальный радиус = 100
public static Map<UUID, HashSet<UUID>> closeDistanceMap = new HashMap<>();

@EventHandler(ignoreCancelled = true)
public void onMove(PlayerMoveEvent e) {
    Location to = e.getTo();
 
    // Опционально - фильтр, чтобы реагировать только тогда,
    // когда игрок проходит целый блок
    Location from = e.getFrom();
    if (to.getBlockX() == from.getBlockX()
            && to.getBlockY() == from.getBlockY()
            && to.getBlockZ() == from.getBlockZ()) return;

    updateCloseState(e.getPlayer(), to, false);
}

@EventHandler
public void onJoin(PlayerJoinEvent e) {
    Player player = e.getPlayer();
    updateCloseState(player, player.getLocation(), false);
}

@EventHandler
public void onQuit(PlayerQuitEvent e) {
    Player player = e.getPlayer();
    updateCloseState(player, player.getLocation(), true);
}

@EventHandler(ignoreCancelled = true)
public void onTeleport(PlayerTeleportEvent e) {
    updateCloseState(e.getPlayer(), e.getTo(), false);
}

private void updateCloseState(Player player, Location location, boolean isQuitting) {
    World world = location.getWorld();
    UUID playerUUID = player.getUniqueId();
 
    // Если игрок уходит из игры
    if (isQuitting) {
        for (Player target : world.getPlayers()) {
            UUID targetUUID = target.getUniqueId();
            if (removeClosePlayers(playerUUID, targetUUID)) {
                // <== ЗДЕСЬ же выполняем что нам нужно (отдаление)
            }
        }
        closeDistanceMap.remove(playerUUID);
        return;
    }
 
    // Проверяем всех игроков только в нужном мире
    for (Player target : world.getPlayers()) {
        UUID targetUUID = target.getUniqueId();
        // Игрок player и target в радиусе?
        if (radius <= target.getLocation().distanceSquared(location)) {
            if (addClosePlayers(playerUUID, targetUUID)) {
                // <== ЗДЕСЬ же выполняем что нам нужно (приближение)
            }
        }
        // Игрок player и target НЕ в радиусе?
        else {
            if (removeClosePlayers(playerUUID, targetUUID)) {
                // <== ЗДЕСЬ же выполняем что нам нужно (отдаление)
            }
        }
    }
}

// Возвращает true, если ранее они НЕ были в радиусе
private boolean addClosePlayers(UUID uuidA, UUID uuidB) {
    if (uuidA.equals(uuidB)) return false;
    closeDistanceMap.computeIfAbsent(uuidA, v -> new HashSet<>()).add(uuidB);
    return closeDistanceMap.computeIfAbsent(uuidB, v -> new HashSet<>()).add(uuidA);
}

// Возвращает true, если ранее они были в радиусе
private boolean removeClosePlayers(UUID uuidA, UUID uuidB) {
    if (uuidA.equals(uuidB)) return false;
    closeDistanceMap.computeIfAbsent(uuidA, v -> new HashSet<>()).remove(uuidB);
    return closeDistanceMap.computeIfAbsent(uuidB, v -> new HashSet<>()).remove(uuidA);
}
 
Пропускать самого себя лучше в самом начале цикла, а не где-то в глубине, и делать это по ссылке на игрока, а не uuid.
radius лучше переименовать в radiusSq, или что другое, отражающее суть переменной.
 
Назад
Верх