Configuration Résolu (Go direct à la page 4)Java : Check si un joueur est dans une zone

DiscowZombie

Développeur
Staff
Modérateur
Support
2 Mars 2017
2 659
1
931
298
Alsace
www.discowzombie.fr
Re :)

Après avoir réfléchis au code à tête reposé, je me suis rendu compte qu'il y avait plein de problème et d’incohérence un peu partout ! Voici du coup un code qui fonctionne à la perfection ;) (Il reste quelques optimisations possible, notamment au niveau des boucles for()). Je précise également que le code ne prends pas en compte le Y (les régions sont définit par défaut de 0 à 255), mais c'est très simple à ajouter si tu le souhaites !

Voici donc les 5 class :

Code:
package fr.discowzombie.axelfatta;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;

import fr.discowzombie.axelfatta.events.BlockBreak;

public class Main extends JavaPlugin {
 
    @Override
    public void onEnable() {
     
//Moi je boss avec la config par default (config.yml) mais toi, n'oublie pas de changer :D
        for (String s : getConfig().getConfigurationSection("Bases").getValues(false).keySet()) {
            if ((s != null) && (Bukkit.getWorld(getConfig().getString("Bases."+s+".world")) != null)) {
                new LocationManager(s).add(
                       new Location(Bukkit.getWorld(getConfig().getString("Bases."+s+".world")), getConfig().getInt("Bases."+s+".x1"), 0, getConfig().getInt("Bases."+s+".z1")),
                       new Location(Bukkit.getWorld(getConfig().getString("Bases."+s+".world")), getConfig().getInt("Bases."+s+".x2"), 255, getConfig().getInt("Bases."+s+".z2")));
            }
        }
     
        Bukkit.getServer().getPluginManager().registerEvents(new BlockBreak(), this);
     
        super.onEnable();
    }
 
 

}

Code:
package fr.discowzombie.axelfatta;

import org.bukkit.Location;

public class LocSaver {
 
    private Location loc1, loc2;
 
    public LocSaver(Location loc1, Location loc2){
        this.setLoc1(loc1);
        this.setLoc2(loc2);
    }

    public Location getLoc1() {
        return loc1;
    }

    public void setLoc1(Location loc1) {
        this.loc1 = loc1;
    }

    public Location getLoc2() {
        return loc2;
    }

    public void setLoc2(Location loc2) {
        this.loc2 = loc2;
    }
 
}

Code:
package fr.discowzombie.axelfatta;
import java.util.HashMap;

import org.bukkit.Location;

public class LocationManager {
 
    private static HashMap<String, LocSaver> bases = null;
   
    private String name;
 
    public LocationManager(String name){
        this.name = name;
    }
 
    public void add(Location loc1, Location loc2){
        if(bases == null || bases.size() == 0){
            bases = new HashMap<>();
        }
        bases.put(name, new LocSaver(loc1, loc2));
    }
 
    public LocSaver get(){
        if(bases.containsKey(name)){
            return bases.get(name);
        }
        return null;
    }
 
    public static HashMap<String, LocSaver> getRegions(){
        return bases;
    }

}

Code:
package fr.discowzombie.axelfatta;
import org.bukkit.Location;
import org.bukkit.block.Block;

public class CuboidManager {
 
    private int maxX, maxY, maxZ, minX, minY, minZ;

    public CuboidManager(Location l, Location l2) {
        if(l.getBlockX() > l2.getBlockX()){
            maxX = l.getBlockX();
            minX = l2.getBlockX();
        }else{
            maxX = l2.getBlockX();
            minX = l.getBlockX();
        }
        if(l.getBlockY() > l2.getBlockY()){
            maxY = l.getBlockY();
            minY = l2.getBlockY();
        }else{
            maxY = l2.getBlockY();
            minY = l.getBlockY();
        }
        if(l.getBlockZ() > l2.getBlockZ()){
             maxZ = l.getBlockZ();
             minZ = l2.getBlockZ();
        }else{
             maxZ = l2.getBlockZ();
             minZ = l.getBlockZ();
        }
    }

    public boolean isInCube(Location l) {
        Block b = l.getBlock();
        if (
                (b.getX() <= maxX) && (b.getX() >= minX) &&
                (b.getY() <= maxY) && (b.getY() >= minY) &&
                (b.getZ() <= maxZ) && (b.getZ() >= minZ))
            return true;
        return false;
    }

}

Pour mon exemple, j'ai fait le code pour le BlockBreak. Mais, si ce qui t'intéresse c'est d’empêcher le placement de bloc, tu as juste à changer BlockBreakEvent par BlockPlaceEvent.

Code:
package fr.discowzombie.axelfatta.events;

import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;

import fr.discowzombie.axelfatta.CuboidManager;
import fr.discowzombie.axelfatta.LocSaver;
import fr.discowzombie.axelfatta.LocationManager;

public class BlockBreak implements Listener {
 
   //Ajoute ici les blocks qui ne sont pas soumis aux regions
   Material[] bypass = { Material.TORCH, Material.LEVER };
 
   //J'ai fait le code pour le BlockBreak mais si tu veut que les gens ne puissent pas posser de block, change le BlockBreakEvent par BlockPlaceEvent.
   @EventHandler
   public void onBreak(BlockBreakEvent e){
       Location l = e.getBlock().getLocation();
    
       for(Material m : bypass){
           if(m.equals(e.getBlock().getType())){
               System.out.println("Ok -> bypass");
               return;
           }
       }
    
       for(LocSaver ls : LocationManager.getRegions().values()){
           if(!new CuboidManager(ls.getLoc1(), ls.getLoc2()).isInCube(l)){
               System.out.println("Hors d'un cuboid -> desctruction interdite");
               e.setCancelled(true);
               break;
           }
       }
    
   }

}

Je n'explique pas le code car je l'ai juste remastériser, et que tu devrait assez facilement le comprendre ; mais si tu veut des explications sur un point précis, n’hésite pas :D

En espérant que ce soit bon cette fois,
Amicalement, Mathéo.
 
Dernière édition:

Alex Fatta

Commandant de la Flotte et de la Horde
13 Août 2014
1 391
1
191
187
Bonjour !

Oh ça fonctionnais pas au début puis j'ai reset tous les fichiers de config, et là ca fonctionne enfin ! Jamais passé autant de tant sur une étape de plugin xDD Mais au moins j'ai beaucoup appris grâce à toi :p Merci encore, il me reste plus que à gérer les parties des jours (PvP, dimensions etc...) et j'aurai fini ce foutu FK xD Et bien merci encore à toi @DiscowZombie et à bientôt :p !!

AlexFatta
 
  • J'aime
Reactions: DiscowZombie

Alex Fatta

Commandant de la Flotte et de la Horde
13 Août 2014
1 391
1
191
187
Bonjour !

... Oui... Je sais... ca date de 2017... Mais j'en ai besoin xD Puis c'est mon topic x)

Bon ! C'est très simple. Je met un spoiler de contexte pour les nouveaux arrivants, puis le problème juste après.

L'idée de base était de créer un système permettant de vérifier si un joueur est ou non dans une zone lorsqu'il pose un bloc. Après de longs jours, @DiscowZombie à réussi à trouver la solution aux problèmes. La région était créée et la vérification marche très bien à ce stade.

Là où est le problème, quand il y a une zone à vérifier ca fonctionne à la perfection et rapidement. Mais quand il y a deux zones à vérifier....le plugin fait un AVC. Je suis allé prendre une douche, je me suis dit "Bah garçon, fait une boucle qui récupère tes régions et vérifies les !". Je sors de la douche, je regarde le code et là, je vois qu'il y a déjà une boucle for "censé" vérifier toutes les zones...

Java:
for (LocSaver ls : LocationManager.getRegions().values()) {
                    if ( !new CuboidManager(ls.getLoc1(), ls.getLoc2()).isInCube(l) ) {
                        player.sendMessage(ChatColor.RED + "Erreur : tu ne peux pas poser ce block : type=" + e.getBlock().getType());
                        e.setCancelled(true);
                        break;
                    }

                }

LocSaver est un simple objet qui réuni 2 locations, et LocationManager s'occupe de lier un nom de zone et un LocSaver dans une HashMap.

CuboidManager sert à vérifier si le bloc posé est dans une zone ou non : a ce jour, c'est toujours exactement la même class que en haut.

Et donc le problème survient que peu importe dans quel zone je me trouve (ou en dehors d'une zone aussi), je ne peux poser aucun bloc, c'est toujours "false" qui est renvoyé de la méthode "isInCube" lorsqu'il y a 2 zones, alors que avec une seule tout fonctionne très bien.

Auriez-vous une quelconque idée sur la questions ? :D

Merci à tous !

AlexFatta

EDIT : petite précision qui a son importance, il y a zéro erreur dans les logs, juste le message comme quoi je ne peux pas poser de bloc.
 

DiscowZombie

Développeur
Staff
Modérateur
Support
2 Mars 2017
2 659
1
931
298
Alsace
www.discowzombie.fr
Salut !

J'ai vu flou quand j'ai reçu la notif, m'appretant à sanctionner à toute allure le membre... Bref, trêves de plaisanterie. Je ne vais pas m'attarder sur le code qui est d'une qualité discutable - clairement ça se voit qu'il a 2+ ans et il aurait bien besoin d'une refonte. Ceci étant dit, cette boucle for me semble bizarre. Elle trouve la première région qui satisfasse que tu ne sois pas dedans, et, si une telle région existe, elle t'empêche d'y placer (détruire ?) un bloc. Le problème c'est qu'avec deux régions dont l'intersection est vide, cette condition sera toujours fausse, il existera toujours une région dans laquelle tu ne seras pas et donc tu ne pourras placer nulle part. Clairement, ce code est adapté si et seulement si tu ne dois pouvoir placer/casser des blocs que dans l'intersection de N régions (N étant ton nombre total de régions enregistrées). À mon avis, ce n'est pas ce que tu essaies de faire et il faudrait donc sûrement changer cette partie.
 
  • J'aime
Reactions: Alex Fatta

Alex Fatta

Commandant de la Flotte et de la Horde
13 Août 2014
1 391
1
191
187
Bonjour !

Oui j'imagine ta réaction .. x) D'accord ok je comprends mieux ! J'avais pensé à autre chose sinon : faire pour chaque zone une liste de joueurs qui ont le droit de poser dedans. Comme ça au lieu de tester toutes les zones, j'ai juste à récupérer depuis le fichier yml la zone depuis laquelle le joueur a le droit de poser. Il va de soit que chaque joueur est une seule zone. Tu en penses quoi ?

Merci de ta réponse en tout cas ! Un oeil exterieur est toujours le bienvenue ^^

AlexFatta
 

DiscowZombie

Développeur
Staff
Modérateur
Support
2 Mars 2017
2 659
1
931
298
Alsace
www.discowzombie.fr
Salut,

oui ça me semble légal, surtout si ça respecte bien tes contraintes. Par contre il n'est pas envisageable de faire de l'I/O à chaque event Spigot, il faudra mettre en cache les données des zones et des joueurs (dans une Map par exemple). :)
 

Alex Fatta

Commandant de la Flotte et de la Horde
13 Août 2014
1 391
1
191
187
Bonjour à tous !

Je suis donc bien parti sur l'idée évoquée juste au dessus. Pour ceux qui passerait par là, j'ai créé une class "Base" qui contient les joueurs appartenant à cette base, ses limites, et son nom :

Java:
public class Base {

    private ArrayList<String> playerList;
    private ArrayList<Location> limits = new ArrayList<>();

    private String name;

    public Base(ArrayList<String> playerList, ArrayList<Location> limits, String name) {
        this.playerList = playerList;
        this.limits = limits;
        this.name = name;
    }

    public Base(Location location1, Location location2, String name, ArrayList<String> playerList) {
        this.name = name;
        limits.add(location1);
        limits.add(location2);
        this.playerList = playerList;
    }
}

(Je vous fais grasse de tous les getter et setter)

j'ai gardé la classe Cuboides et j'ai modifié la classe LocationManager pour l'adapter aux nouvelles données. Tout est ensuite inscrit dans un fichier .yml sous forme d'arrayList. C'est pas le code le plus propre ni le plus opti mais ca reste pour du privé donc pas trop grave :p

Voici la partie qui gère la vérification (toute proposition d'optimisation est la bienvenue ;) ) :
Java:
boolean checkBase = false;
                String baseName = "";

                for (String uuid : BasesManager.getPlayerPerBase().keySet()) {
                    if (uuid.equals(player.getUniqueId().toString())) {
                        checkBase = true;
                        baseName = BasesManager.getPlayerPerBase().get(uuid);
                        break;
                    }
                }

                if (checkBase) {
                    Base baseToTest = null;
                    for (int i = 0; i < BasesManager.getBases().size(); i++) {
                        if (BasesManager.getBases().get(i).getName().equalsIgnoreCase(baseName)) {
                            baseToTest = BasesManager.getBases().get(i);
                            break;
                        }
                    }

                    Location l1 = baseToTest.getLimits().get(0);
                    Location l2 = baseToTest.getLimits().get(1);

                    if ( !new CuboidManager(l1, l2).isInCube(l) ) {
                        player.sendMessage(ChatColor.RED + "Erreur : tu ne peux pas poser ce block : type=" + e.getBlock().getType());
                        e.setCancelled(true);
                    }

                }

Tant que le joueur est enregistré sur une seule base, tout fonctionne :D

Et oui @DiscowZombie comme tu l'as dit, toutes les données sont chargées en ArrayList et HashMap lors de la mise en route du plugin ;)

Merci beaucoup en tout cas ! Je pense que cette fois-ci le sujet est clos ;)

Bonne soirée !!

AlexFatta
 

DiscowZombie

Développeur
Staff
Modérateur
Support
2 Mars 2017
2 659
1
931
298
Alsace
www.discowzombie.fr
Salut,

je ne peux pas te laisser écrire ce code en 2020 donc permet moi quelques conseils pour tout ça.
Pour le premier bloc de code, trois points :
  • Pensé à la finalité des variables ;
  • Usage de l'interface et non de l’implémentation. Ce n'est pas normal de voir des ArrayList (qui est une implémentation) plutôt que son interface List. Ça rend ton code difficile à faire évoluer et difficile également pour les personnes qui vont l'utiliser.
  • Usage d' UUID au lieu de pseudonyme (qui peuvent changer, donc rendre ton stockage faussé)
Ces quelques modifications faites, on arrive à ça - qui me semble déjà plus court, plus sécuritaire et plus facile à comprendre :
Java:
public class Base {

    private final List<UUID> playerList;
    private final List<Location> limits;
    private final String name;

    public Base(String name, List<UUID> playerList, List<Location> limits) {
        this.playerList = playerList;
        this.limits = limits;
        this.name = name;
    }

    public Base(String name, List<UUID> playerList, Location location1, Location location2) {
        this(name, playerList, new ArrayList<>(Arrays.asList(location1, location2)));
    }
}


Pour le deuxième code, je regrette l'absence de Stream. Bien que ces derniers aient des performances un peu moindres (ce qui me semble négligeable vu ton use-case), ils rendent ton code drastiquement plus lisible, ce qui n'est pas le cas de tes trois boucles for. En passant en fonctionnel, on arrive à ça :
Java:
// Trouver le nom de la base depuis le nom du joueur
final Optional<String> baseName = BasesManager.getPlayerPerBase().entrySet().stream()
        .filter(e -> e.getKey().equals(player.getUniqueId()))
        .map(Map.Entry::getValue)
        .findFirst();

if (baseName.isPresent()) {
    final Optional<Base> base = BasesManager.getBases().stream()
            .filter(b -> b.getName().equalsIgnoreCase(baseName.get()))
            .findFirst();

    // Cette condition devrait toujours être vrai sauf si tes deux structure de données sont desync
    if (base.isPresent()) {
        final Location l1 = base.get().getLimits().get(0);
        final Location l2 = base.get().getLimits().get(1);

        if (!new CuboidManager(l1, l2).isInCube(l)) {
            player.sendMessage(ChatColor.RED + "Erreur : tu ne peux pas poser ce block : type=" + e.getBlock().getType());
            e.setCancelled(true);
        }
    }
}


Et on se rend donc compte que tu peux bien améliorer ce code si au lieu de stocker la combinaison Map<UUID, String> et List<Base> tu stockes simplement Map<UUID, Base>. Avec un tel stockage, qui associe donc un utilisateur par UUID à une base de manière unique, le code devient simplement :
Java:
final Base base = BasesManager.getBases().get(player.getUnique());

// Le joueur n'a pas de Base
if (base == null) {
   
}
// Le joueur a une base
else {
    final Location l1 = base.getLimits().get(0);
    final Location l2 = base.getLimits().get(1);

    if (!new CuboidManager(l1, l2).isInCube(l)) {
        player.sendMessage(ChatColor.RED + "Erreur : tu ne peux pas poser ce block : type=" + e.getBlock().getType());
        e.setCancelled(true);
    }
}
 

Detobel36

Créateur de plugins (PhoenixRebirth)
Support
17 Août 2012
10 530
24
2 247
347
27
Bruxelles - Belgique
www.phoenix-rebirth.fr
Salut,

Au passage, au lieu d'utiliser des listes, pourquoi ne pas faire des set ? Il n'y a pas d'ordre et a priori un joueur ne va pas se retrouver deux fois dans une base...
  • Usage de l'interface et non de l’implémentation. Ce n'est pas normal de voir des ArrayList (qui est une implémentation) plutôt que son interface List. Ça rend ton code difficile à faire évoluer et difficile également pour les personnes qui vont l'utiliser.
Donc du genre:
Java:
private final Collection<UUID> playerList;
// Et on initialise: playerList = new HashSet<UUID>();

Après, j'avoue que quand c'est du code uniquement pour toi, c'est pas un énorme problème de mettre le type générique... Si tu initialise un HashSet et que ton attribut est un HashSet ça va aller... Si par contre tu veux en faire une API là oui !
Si c'est un plugin qui va être utilisé par d'autres plugins également :D


Cordialement,
Detobel36