1. Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.
  2. Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.
    Скрыть объявление
Скрыть объявление
В преддверии глобального обновления, мы проводим исследования, которые помогут нам сделать опыт пользования форумом ещё удобнее. Помогите нам, примите участие!

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

Тема в разделе "Разработка плагинов для новичков", создана пользователем ilya50, 28 окт 2018.

  1. Автор темы
    ilya50

    ilya50 Активный участник Пользователь

    Баллы:
    76
    Как сделать так, что если к какому либо игрок подходит другой игрок в радиусе R, то происходит какое либо событие ? Те это нужно чекать постоянно, игрок может так же и выйти из его радиуса(это тоже надо обрабатывать). Как такое можно реализовать ?
     
  2. Ission

    Ission Старожил Девелопер Пользователь

    Баллы:
    173
    Skype:
    lokivava
    execute @a ~ ~ ~ execute @a[r=10] ~ ~ ~ <команда>
    Например так через командные блоки, где r -- радиус.
    Ещё можно при помощи sheduler'а и перебора всех игроков или сущностей.
     
  3. Автор темы
    ilya50

    ilya50 Активный участник Пользователь

    Баллы:
    76
    Бесконечный поток не будет нагружать сервер ? И как часто его запускать, те с какой периодичностью ?
     
  4. Ission

    Ission Старожил Девелопер Пользователь

    Баллы:
    173
    Skype:
    lokivava
    С оптимальной. Можно запускать в отдельном цикле без задержек, тогда нагружать будет.
    Всё зависит от задачи, количества игроков, действий...
     
  5. Автор темы
    ilya50

    ilya50 Активный участник Пользователь

    Баллы:
    76
    нашёл код в интернете, если кому нибудь нужно будет пользуйтесь
    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++;
    }
    }
     
  6. imDaniX

    imDaniX Активный участник Пользователь

    Баллы:
    96
    Имя в Minecraft:
    imDaniX
    Не лучше ли будет перебирать игроков, вместо сущностей, и сравнивать их локации? Просто не уверен, как именно работает метод getNearbyEntities, и если там просто некоторый перебор всех сущностей - ну такое.
    Если условия задачи решать в лоб, то, навскидку, с моими "наипрямейшими" руками это вышло бы как-то так:
    Код:
    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 - изначальный радиус во второй степени.
    Не учитывал выход игрока с сервера.
     
  7. LuckyZeeRo

    LuckyZeeRo Активный участник Пользователь

    Баллы:
    96
    Имя в Minecraft:
    i0xHeX
    Сделал немного эффективнее, чем предыдущие варианты и с максимальными обьяснениями

    P.S. Код содержит ошибки и неточности. Исправленный код сообщениями ниже.
    PHP:
    // Радиус лучше держать в квадрате, чтобы каждый раз не извлекать корень при подсчете расстояния
    // UUID в хеш коллекциях работает быстрее чем String
    public static double radius 10000// реальный радиус = 100
    public static Map<UUIDSet<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<UUIDcloseTargetsToPlayer closeDistanceMap.getOrDefault(playerUUID, new HashSet<>());
                
    boolean wasClose closeTargetsToPlayer.contains(targetUUID);
                
    // Если не были, значит добавляем, будут :)
                
    if (!wasClose) {
                    
    closeTargetsToPlayer.add(targetUUID);
                    
    closeDistanceMap.put(playerUUIDcloseTargetsToPlayer);
             
                    
    // <== ЗДЕСЬ же выполняем что нам нужно (приближение)
                
    }
            }
            
    // Игрок player и target НЕ в радиусе?
            
    else {
                
    // Были ли в радиусе раньше? (Повторений же нам опять таки не надо)
                
    Set<UUIDcloseTargetsToPlayer closeDistanceMap.getOrDefault(playerUUID, new HashSet<>());
                
    boolean wasClose closeTargetsToPlayer.contains(targetUUID);
                
    // Если были, значит убираем
                
    if (wasClose) {
                    
    closeTargetsToPlayer.remove(targetUUID);
                    
    closeDistanceMap.put(playerUUIDcloseTargetsToPlayer);
             
                    
    // <== ЗДЕСЬ же выполняем что нам нужно (отдаление)
                
    }
            }
        }
    }
     
    Последнее редактирование: 28 окт 2018
  8. imDaniX

    imDaniX Активный участник Пользователь

    Баллы:
    96
    Имя в Minecraft:
    imDaniX
    А что будет в случае, если один игрок стоит, и уже после попадания в радиус другого(ходячего) игрока начнет двигаться? Думаю, стоит ещё "обновлять" в мапе список id для target, добавляя в/убирая из него ходячего игрока.
     
    Последнее редактирование: 28 окт 2018
  9. Reality_SC

    Reality_SC Старожил Пользователь

    Баллы:
    123
    Имя в Minecraft:
    Reality_SC
    Еще пара советов:
    Заменить getOrDefault на computeIfAbsent(k -> new HashSet<>()); и вынести из обеих веток до условия. put в мапу ненужен.
    Убрать полность .contains(). Оба метода, add и remove, уже возвращают нужные булеаны.
    Причем в твоем коде они даже станут ненужны, а если править set у другого игрока, то нужны
     
  10. LuckyZeeRo

    LuckyZeeRo Активный участник Пользователь

    Баллы:
    96
    Имя в Minecraft:
    i0xHeX
    Принял, обновил. Добавил новые события. Исправил еще некоторые детали.
    PHP:
    // Радиус лучше держать в квадрате, чтобы каждый раз не извлекать корень при подсчете расстояния
    // UUID в хеш коллекциях работает быстрее чем String
    public static double radius 10000// реальный радиус = 100
    public static Map<UUIDHashSet<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(), tofalse);
    }

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

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

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

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

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

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

    Reality_SC Старожил Пользователь

    Баллы:
    123
    Имя в Minecraft:
    Reality_SC
    Пропускать самого себя лучше в самом начале цикла, а не где-то в глубине, и делать это по ссылке на игрока, а не uuid.
    radius лучше переименовать в radiusSq, или что другое, отражающее суть переменной.
     

Поделиться этой страницей