[1.7.10] Работа с приватами WorldGuard из мода

162
1
9
Пилю проверку приватов WorldGuard по координате блока и хочу поделиться процессом и кодом, а также услышать критику и замечания, чтобы либо бросить эту затею, либо придти к наилучшему финалу.

Было скучно, и я решил добавить на свой старый сервер тропинки из новых версий. Одна идея породила другие, пока наконец я не сделал новое зачарование для ботинок - "Тропоход", подобное "современному" зачарованию "Ледоход", только протаптывает тропинку везде, где вы ходите. Как всё это работает - отдельная тема, а здесь я хочу заострить ваше внимание на проблеме, которую поднимали тут, я уверен, уже десятки раз - работе с приватами WorldGuard.

Если не ошибаюсь, для работы с модами у WorldGuard нету API, а мне позарез нужно как-то сделать, чтобы мои ботинки не протаптывали тропинки на чужих приватах.
Сначала я хотел парсить regions.yml "народными" средствами (SnakeYAML, Jackson), но когда погуглил, оказалось, что всё это лично для меня довольно муторно и непрозрачно. Поэтому было решено написать собственный плагин, который будет приводить список всех регионов на сервере в удобную для обработки форму - файл с желаемой структурой - что и было сделано. На данном этапе плагин работает, как по мне, отлично, хотя я не уверен в том, что так будет, если установить его на сервер с онлайном больше 10 человек (не мой случай). Далее я напишу для моего мода простейшие и эффективные методы, которые будут использовать содержимое этого файла для проверки регионов в нужной точке пространства. Код методов выложу тут по готовности.

Наверняка мой метод - полная шляпа, и если это так - не стесняйтесь, скажите об этом. Но если он будет выполнять ожидаемую функцию и я не найду ничего лучше - значит, я не зря потратил время на то, чтобы его придумать. Ну а если он пригодится вам - я буду только рад.

Код плагина, ретранслирующего регионы WorldGuard в один универсальный файл
Java:
private long regListRefresh = 200; // период проверки изменения списка регионов
        try {
            WGRegListGrabber.plugin = this;
            WGRegListGrabber.regGrabber(regListRefresh);
        } catch (Exception e) {log.info("RHA: Error #16 (WGRegListGrabber)."); e.printStackTrace();}

Java:
package ru.lao.plugins;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitTask;

import com.sk89q.worldedit.BlockVector2D;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;

import ru.lao.core.Logs;
import ru.lao.core.Main;

public class WGRegListGrabber {

    public static BukkitTask regionsgrabber = null;
    public static boolean debug = false;
    public static Main plugin;
    public static long[] worldsTime = {0, 0, 0, 0, 0, 0, 0}; // время изменения файлов region.yml, для семи миров, добавьте, сколько нужно
    public static List<String> tolist = new ArrayList<String>();

    // обновляет список регионов, если он обновился
       public static void regGrabber(long update){  // update - период проверки в тиках, у меня стоит 200, то есть 10 секунд
           regionsgrabber = Bukkit.getScheduler().runTaskTimer(plugin, new Runnable(){
            public void run(){
                // берем список миров
                List<World> worlds = Bukkit.getWorlds();
                // проверяем даты редактирования списков регионов
                boolean need4update = false;
                int counter = 0;
                for (World w:worlds){
                    // берём файл с регионами очередного мира
                    File regionlist = new File("plugins/WorldGuard/worlds/" + w.getName() + "/regions.yml");
                    // если он не существует - что-то не так, пишем ошибку в консоль, а программисту даём по попе ботинком
                    if(!regionlist.exists() && debug){Logs.con("wgrg", "File " + regionlist + " not found. Maybe WorldGuard isn't exist?");return;}
                    long lastmod = regionlist.lastModified(); // когда файл был модифицирован в последний раз
                    if(worldsTime[counter] < lastmod){ // сравнивая, узнаём, изменилось ли время с последней проверки
                        need4update = true; // изменился? значит, кто-то добавил/удалил/изменил какой-то регион, нужно создать новый список
                        worldsTime[counter] = lastmod;
                        if(debug)Logs.con("wgrg", "Regions of world " + w.getName() + " was changed. Parsing scheduled.");
                    }
                    counter++;
                }
                if(!need4update)return; // дата и время изменения файлов с прошлой проверки не изменилась, уходим
                // "парсим" все регионы всех миров
                tolist.clear(); // очищаем список, в конце этот список полностью запишем в итоговый файл
                for (World w:worlds){ // для каждого мира
                    RegionManager regionManager = Main.WGPlugin.getRegionManager(w);
                    Map<String, ProtectedRegion> regions = regionManager.getRegions();
                    String worldname = w.getName() + ":::"; // имя мира
                    for(String region:regions.keySet()){
                        // собираем строку для одного региона (начало) -----------------------------
                        ProtectedRegion reg = regions.get(region);
                        if(reg.getId().equalsIgnoreCase("[B]global[/B]")){continue;} // глобальный регион нам не нужен
                        String regname = reg.getId(); // имя региона
                        String playerlist = ":::"; // создаем список овнеров и мемберов
                        Set<String> owners = reg.getOwners().getPlayers(); // овнеры
                        for(String owner:owners){
                            playerlist += owner + "::";
                        }
                        Set<String> members = reg.getMembers().getPlayers(); // мемберы
                        for(String member:members){
                            playerlist += member + "::";
                        }
                        playerlist += ":"; // на выходе - :::овнер::овнер::мембер::мембер::мембер:::
                        String type = reg.getTypeName();
                        String points = ":::";

                        if(type.equalsIgnoreCase("cuboid")){ // для кубоида создаем список координат в виде минX::минY::минZ::максX::максY::максZ
                            int minX = reg.getMinimumPoint().getBlockX(); points += minX + "::";
                            int minY = reg.getMinimumPoint().getBlockY(); points += minY + "::";
                            int minZ = reg.getMinimumPoint().getBlockZ(); points += minZ + "::";
                            int maxX = reg.getMaximumPoint().getBlockX(); points += maxX + "::";
                            int maxY = reg.getMaximumPoint().getBlockY(); points += maxY + "::";
                            int maxZ = reg.getMaximumPoint().getBlockZ(); points += maxZ;
                        } else if(type.equalsIgnoreCase("polygon")){ // для полигона создаем список координат в виде минY::максY::X1::Z1::X2::Z2::X3::Z3::X4::Z4::X5::Z5
                            points += reg.getMinimumPoint().getBlockY() + "::";; // нижний Y
                            points += reg.getMaximumPoint().getBlockY() + "::";; // верхний Y
                            List<BlockVector2D> pointlist = reg.getPoints(); // вершины многоугольника
                            int i = 1;
                            int number = pointlist.size();
                            for(BlockVector2D point:pointlist){ // заливаем X и Z всех вершин
                                points += ((int)point.getX()) + "::";
                                points += ((int)point.getZ());
                                if(i < number) points += "::";
                                i++;
                            }
                        } else {continue;} // тут можно добавить обработку других типов регионов - лично мне это не нужно, но я знаю примерно, как это сделать
                        // на выходе - :::X::Y::Z::X::Y::Z либо :::X::Z::X::Z::X::Z::X::Z
                    
                        String finalstring = worldname + regname + playerlist + type + points;
                        tolist.add(finalstring);
                        // результирующая строка будет такой:
                        //         кубоид:   название[I]мира:::название региона:::овнер::овнер::мембер::мембер::мембер:::тип[/I]формы_региона:::minX::minY::minZ::maxX::maxY::maxZ
                        //        полигон:   название[I]мира:::название региона:::овнер::овнер::мембер::мембер::мембер:::тип[/I]формы_региона:::X::Z::X::Z::X::Z::X::Z::X::Z
                        // собираем строку для одного региона (конец) -----------------------------
                    }
                }

                if(tolist.size() > 0){ // список набран (или не набран), записываем файл
                    File fullreglist = new File("fullreglist.txt");
                    try {
                        if(fullreglist.exists()){ // удаляем старый файл, создаём новый
                            fullreglist.delete();
                            fullreglist.createNewFile();
                        }
                        BufferedOutputStream quoteOut = new BufferedOutputStream(new FileOutputStream(fullreglist, true));
                        for(String str:tolist){ // заливаем наши строчки в новый файл
                            str += "\n";
                            quoteOut.write(str.getBytes("UTF-8"));
                        }
                        quoteOut.flush();
                        quoteOut.close();
                    } catch (IOException e) {Logs.con("wgrg", "Eror while access to fullreglist file."); e.printStackTrace(); return;}
                    if(debug)Logs.con("wgrg", "Region list created.");
                
                }            
            }
           }, 0, update);
        }
}

parsedregions.png

Пояснения:
22:01:46 сервер загрузился и сразу создал свежий лист
22:02:11 зашел игрок
22:03:06 игрок изменил флаг своего региона
22:03:07 при очередной проверке плагин определил, что произошли изменения и создал новый файл

work.png



Как видите, на выходе мы получаем строки, которые можно в нашем моде через String.split("::") и String.split(":::") очень легко разбирать на массивы и извлекать при необходимости из региона название мира, региона, имена игроков (тут я не делал различий между овнерами и мемберами, так как мне это не нужно, но это тоже можно запилить, добавив одну строчку) в нём, и - самое важное - координаты всех вершин для решения изначально установленной задачи.

Задавайте вопросы, пишите замечания и предложения.

Update:
Итак, работа окончена, плагин и мод работают. Большое спасибо за замечания - они наверняка резонны, но для меня мой "велосипед" оказался проще в понимании.

Исправил код в листинге - "BlockVector2D.getX()" и "BlockVector2D.getZ()" выдавали числа double, что излишне и к тому же потом Integer.valueOf(String) ругается на числа с точкой.

Для любителей велосипедов привожу методы из мода, как и обещал:
Java:
if (Functions.checkRegAccess(p.getCommandSenderName(), w.getWorldInfo().getWorldName(), x, y, z)) {
    // (ваш код)
}

Java:
    public static long time;
    public static long ticks;
    public static int tpsinterval = 7;

    @SubscribeEvent
    public void onServerTick(ServerTickEvent e) {
        if(e.phase.equals(Phase.END) && e.side == Side.SERVER){ // возможно, это лишнее :)))
            long current = System.currentTimeMillis();
            long delta = current - time;
            if(delta <= tpsinterval * 1000){ticks++;} else { // проверяем раз в tpsinterval секунд
                time = System.currentTimeMillis();
                long tps = ticks/tpsinterval;
                if(ticks < tpsinterval * 15)System.out.println(ticks + "/" + (tpsinterval * 20) + " ticks. Average TPS for last " + tpsinterval + " seconds: [" + (ticks/(double)tpsinterval) + "]");
                ticks = 0L;
                // TODO updateRegions
                try {
                    Functions.updateRegions(); // собственно, это ОНО
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

Java:
    static boolean debug = false;
    private static List<String> regions = new ArrayList<String>();
    private static long lastRegionsModified = 0L;
   
    public static void updateRegions() throws IOException {
        File fullreglist = new File("fullreglist.txt");
        if(!fullreglist.exists()){Console.out().println("Regions list isn't found at " + fullreglist.getAbsolutePath()); return;}
        if(lastRegionsModified < fullreglist.lastModified()){
            lastRegionsModified = fullreglist.lastModified();
        } else return;
        regions.clear();
        BufferedReader quoteFile = new BufferedReader(new InputStreamReader(new FileInputStream(fullreglist), "UTF-8"));
        String str = "";
        do {
            str = quoteFile.readLine();
            if(str == null)break;
            if(str != ""){
                regions.add(str);
            }
        } while (str != null && str != "");
        quoteFile.close();
       
        if(debug){
            Console.out().println("--- Regions contents ---");
            for(String s:regions){Console.out().println(s);}
        }
       
    }

Java:
    public static boolean checkRegAccess(String player, String world, int x, int y, int z){
            // List<String> regions - список всех регионов
            for(String region:regions){
               
                String[] data = region.split(":::"); // берём строку и делим её на "название[I]мира", "название[/I]региона", "список[I]игроков", "тип[/I]региона", "координаты"
                if(data.length != 5){Console.out().println("Corrupted reglist (it must be 5 words): " + region); continue;} // // строка неправильная - пропускаем с уведомлением
                String worldname = data[0]; // имя мира
                if(!worldname.equalsIgnoreCase(world))continue; // если мы в другом мире - пропускаем
                String regionname = data[1]; // название региона (вообще-то оно нам не нужно)
                String type = data[3];
                String[] coords = data[4].split("::");
                boolean isReg = false;
                if(type.equalsIgnoreCase("cuboid")){
                    if(coords.length != 6){Console.out().println("Corrupted reglist (it must be 6 coords for cuboid): " + region); continue;} // // строка неправильная - пропускаем с уведомлением
                    int minX = Integer.valueOf(coords[0]);
                    int minY = Integer.valueOf(coords[1]);
                    int minZ = Integer.valueOf(coords[2]);
                    int maxX = Integer.valueOf(coords[3]);
                    int maxY = Integer.valueOf(coords[4]);
                    int maxZ = Integer.valueOf(coords[5]);
                    if(isPointInsideCuboid(x, y, z, minX, minY, minZ, maxX, maxY, maxZ)){isReg = true;} // !!! игрок внутри кубоидного региона !!!
                } else if(type.equalsIgnoreCase("polygon")){
                    if(coords.length % 2 != 0){Console.out().println("Corrupted reglist (coords number must be even): " + region); continue;} // строка неправильная - пропускаем с уведомлением
                    int vertexes = (coords.length - 2)/2;
                    int minY = Integer.valueOf(coords[0]);
                    int maxY = Integer.valueOf(coords[1]);
                    int[] xx = new int[vertexes];
                    int[] zz = new int[vertexes];
                    for(int i = 0; i < vertexes; i++){
                        xx[i] = Integer.valueOf(coords[2+i*2]);
                        zz[i] = Integer.valueOf(coords[3+i*2]);
                    }
                    if(isPointInsideRegion(x, y, z, minY, maxY, xx, zz)){isReg = true;}// !!! игрок внутри полигонального региона !!!    
                } else continue;
                if(isReg){
                   
                    if(regionname.equalsIgnoreCase("spawn") || // нельзя топтать спавн и так далее
                            regionname.equalsIgnoreCase("регион_исключения") ||
                                regionname.equalsIgnoreCase("и[I]так[/I]далее")){return false;}
                   
                    String[] owners = data[2].split("::"); // овнеры и мемберы
                    for(String owner:owners){ // листаем овнеров и мемберов
                        if(owner.equalsIgnoreCase(player)){return true;} // если нужно проверить накладывающиеся регионы - то continue
                    }
                    return false;
                }
            }
        return true;
    }

Ну это любой сделает, согласен.
Java:
    public static boolean isPointInsideCuboid(int x, int y, int z, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
        if(x < minX || x > maxX || y < minY || y > maxY || z < minZ || z > maxZ){return false;}
        return true;
    }

Эту процедуру я нагуглил, но в исходниках WorldGuard что-то очень похожее видел.
Java:
    /**
     * @param x - координата X проверяемой точки
     * @param y - координата Y проверяемой точки
     * @param xx - перечисление координат X всех вершин многоугольника
     * @param yy - перечисление координат Y всех вершин многоугольника
     * @return внутри ли точка
     */
    public static boolean isPointInsideRegion(int x, int y, int z, int minY, int maxY, int[] xx, int[] zz) {
        int size = xx.length;
        // в массиве только одна вершина
        if(size < 2)return false;
        // отсеиваем по высоте
        if(y < minY || y > maxY){return false;}
        // три и более вершин
        int j = size - 1;
        boolean c = false;
        for(int i = 0; i < size; i++){
            if(
                    (
                    ((zz[i] <= z) && (z < zz[j])) ||
                    ((zz[j] <= z) &&(z < zz[i]))
                    ) &&
                    (x > (xx[j] - xx[i]) * (z - zz[i]) / (zz[j] - zz[i]) + xx[i])
              ){c = !c;}
            j = i;
        }
        return c;
    }

Вроде всё. У меня работает - и для кубоидов, и для полигонов. Надеюсь, кому-то пригодится.
 
Последнее редактирование:

timaxa007

Модератор
5,827
409
657
LaoTheLizard написал(а):
протаптывает тропинку везде, где вы ходите.
А типа как тяпки сделана проверка на изменение блоков?
Java:
if (!player.canPlayerEdit(x, y, z, side, itemStack)) {
return false;
}
 

tox1cozZ

aka Agravaine
Модератор
7,536
486
2,348
А что, нельзя напрямую юзать классы ворлдэдита в моде?
В конце-концов - рефлексия.
Намутил ты вообще дичь, без обид)
 
162
1
9

tox1cozZ

aka Agravaine
Модератор
7,536
486
2,348
Как библиотеку подключаешь в среду и юзаешь
 
162
1
9
@timaxa007
Сделал. А как мой мод уже на сервере потом узнает, где находятся классы WorldEdit и WorldGuard? Автоматом найдет в папке plugins?
 

timaxa007

Модератор
5,827
409
657
@LaoTheLizard, а вот плагины в моде, не пробовал, но вроде как может сработать
 
162
1
9
Всё импортировал, прикрепил, вставил такой код (взял готовый из моего же плагина):
Java:
    /**
     * 5 - no regions, 4 - player is admin, 3 - player is owner, 2 - player is member, 1 - this is a spawn, 0 - player has no any rights here
     * @param p - player to check his rights in his location
     * @return - right level
     */
    public static int checkOwner(Player p) {
        Location loc = p.getLocation();
        World w = loc.getWorld();
        RegionManager regionManager = WGBukkit.getRegionManager(w);
        ApplicableRegionSet set = regionManager.getApplicableRegions(loc);
        if(set.size() == 0) return 5; // no regions - can set home
        if(p.isOp()) return 4; // operators can do everything
        for (ProtectedRegion pr : set) {
            if (pr.getOwners().contains(p.getName())){return 3;} // unlimited rights for owned region
            if (pr.getMembers().contains(p.getName())){return 2;} // limited rights for home region
            if (pr.getOwners().contains("Admin") || pr.getOwners().contains("admin")){return 1;} // this region is spawn
        }
        return 0; // region is here, but we are not a member or owner
    }

Здесь у меня игрок - org.bukkit.entity.Player
Тогда как в Forge у нас его, конечно же, нет.
Поэтому кастую так:
Java:
if (Functions.checkOwner((Player) p)  >= 2)

Сейчас буду пробовать.
 
162
1
9
@timaxa007
Упс.
Прикреплены: Bukkit, Craftbukkit, WG, WE.

 

timaxa007

Модератор
5,827
409
657
Ты bukkit и плагины добавил в build.gradle?
У меня в папке libs и он автоматически добавляет их.
// you may put jars on which you depend on in ./libs
 

timaxa007

Модератор
5,827
409
657
Не в eclipse, а в build.gradle1589840145069.png
 

timaxa007

Модератор
5,827
409
657
@LaoTheLizard, создай папку "libs" в корневой папке и туда ложи твои библиотеки.
1589840381215.png
У меня по-крайней мере, компилировался просто для bukkit'а.
 
162
1
9
меня по-крайней мере, компилировался просто для bukkit'а




Скомпилировалось, спасибо. В общем, зашел, надел боты, отключил креатив, встал на траву и вылетел. Сервер не упал, но... (см.скриншот) Изучаю.))
 

timaxa007

Модератор
5,827
409
657
Ну на скриншоте-же написано, что EntityPlayer из Minecraft, не может быть Player из Bukkit.
 
Сверху