- 210
- 1
- 19
Пилю проверку приватов WorldGuard по координате блока и хочу поделиться процессом и кодом, а также услышать критику и замечания, чтобы либо бросить эту затею, либо придти к наилучшему финалу.
Код плагина, ретранслирующего регионы WorldGuard в один универсальный файл
Как видите, на выходе мы получаем строки, которые можно в нашем моде через String.split("::") и String.split(":::") очень легко разбирать на массивы и извлекать при необходимости из региона название мира, региона, имена игроков (тут я не делал различий между овнерами и мемберами, так как мне это не нужно, но это тоже можно запилить, добавив одну строчку) в нём, и - самое важное - координаты всех вершин для решения изначально установленной задачи.
Задавайте вопросы, пишите замечания и предложения.
Update:
Итак, работа окончена, плагин и мод работают. Большое спасибо за замечания - они наверняка резонны, но для меня мой "велосипед" оказался проще в понимании.
Исправил код в листинге - "BlockVector2D.getX()" и "BlockVector2D.getZ()" выдавали числа double, что излишне и к тому же потом Integer.valueOf(String) ругается на числа с точкой.
Для любителей велосипедов привожу методы из мода, как и обещал:
Вроде всё. У меня работает - и для кубоидов, и для полигонов. Надеюсь, кому-то пригодится.
Было скучно, и я решил добавить на свой старый сервер тропинки из новых версий. Одна идея породила другие, пока наконец я не сделал новое зачарование для ботинок - "Тропоход", подобное "современному" зачарованию "Ледоход", только протаптывает тропинку везде, где вы ходите. Как всё это работает - отдельная тема, а здесь я хочу заострить ваше внимание на проблеме, которую поднимали тут, я уверен, уже десятки раз - работе с приватами WorldGuard.
Если не ошибаюсь, для работы с модами у WorldGuard нету API, а мне позарез нужно как-то сделать, чтобы мои ботинки не протаптывали тропинки на чужих приватах.
Сначала я хотел парсить regions.yml "народными" средствами (SnakeYAML, Jackson), но когда погуглил, оказалось, что всё это лично для меня довольно муторно и непрозрачно. Поэтому было решено написать собственный плагин, который будет приводить список всех регионов на сервере в удобную для обработки форму - файл с желаемой структурой - что и было сделано. На данном этапе плагин работает, как по мне, отлично, хотя я не уверен в том, что так будет, если установить его на сервер с онлайном больше 10 человек (не мой случай). Далее я напишу для моего мода простейшие и эффективные методы, которые будут использовать содержимое этого файла для проверки регионов в нужной точке пространства. Код методов выложу тут по готовности.
Наверняка мой метод - полная шляпа, и если это так - не стесняйтесь, скажите об этом. Но если он будет выполнять ожидаемую функцию и я не найду ничего лучше - значит, я не зря потратил время на то, чтобы его придумать. Ну а если он пригодится вам - я буду только рад.
Если не ошибаюсь, для работы с модами у 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);
}
}
Как видите, на выходе мы получаем строки, которые можно в нашем моде через 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;
}
Вроде всё. У меня работает - и для кубоидов, и для полигонов. Надеюсь, кому-то пригодится.
Последнее редактирование: