Как проитерировать весь мир?

Tryingtaste

Разработчик
Пользователь
Сообщения
120
Есть огромная карта 40000 на 40000. Хочу удалить все сундуки, печки, бочки и тд. на этой карте. Как это можно сделать и реально ли это за обозримое время?
 
Решение
Ну что ж, напишу мое решение. Которое более-менее пришло мне в голову и его прямую реализацию на практике. Я сперва подумал, что можно это сделать с помощью BukkitAPI. Но знаете, проитерировать 7 миллионов чанков довольно непростая задача. Хотя и простая, но мы же хотим сделать это в адекватное время. Конечно, итерировать по TileEntities гораздо проще, но вот в чем проблема - этот метод возвращает пустой список(или ошибку), если мы пытаемся выполнить этот метод в асинхронном режиме. То есть, нужно это делать в основном потоке, что снижает продуктивность в разы, в десятки раз. Поэтому... стандартные методы от баккита не подходят.

После этого провала, мне пришла еще одна идея. Можно же это сделать напрямую в файлах мира. И да, кажется...
В таких масштабах ты вряд ли что-то сделаешь без боли, а точней пожалуй наверное даже не сможешь.

Не буду умничать, скину похожую тему
 
Такие операции лучше делать при помощи ПО, которое удалит из твоих файлов мира все необходимые тебе блоки.
Вряд ли получится нормально решить эту задачу при помощи плагина.
 
запускай асинк таск, в котором будет три цикла for()
Далее достаешь блок по кордам и если он является сундуком, печкой или еще чем-то, то удаляй его.
Но учти, 40k * 40k * 256 = 409 млрд блоков.
 
запускай асинк таск, в котором будет три цикла for()
Далее достаешь блок по кордам и если он является сундуком, печкой или еще чем-то, то удаляй его.
Но учти, 40k * 40k * 256 = 409 млрд блоков.
Ну вообще, вроде как, с миром нельзя взаимодействовать в асинхронном потоке.
 
Ну вообще, вроде как, с миром нельзя взаимодействовать в асинхронном потоке.
Разве Paper не предоставляет такой возможности? Они вроде когда-то трогали палит контейнеры.
Но суть одна и та же - три цикла и дальше уже проходишь по миру
 
Ну вообще, вроде как, с миром нельзя взаимодействовать в асинхронном потоке.
Можно из асинхронного потока вызывать обычный BukkitTask, который будет выполняться синхронно.
Кроме того, сундуки и печки - это не просто блоки, это TileEntity. Что в разы упрощает процесс перебора.

Еще можно поделить весь нужный блок координат на количество доступных ядер и обрабатывать карту в несколько тасков / потоков параллельно, что сильно ускорит процесс.
 
Последнее редактирование:
Ну что ж, напишу мое решение. Которое более-менее пришло мне в голову и его прямую реализацию на практике. Я сперва подумал, что можно это сделать с помощью BukkitAPI. Но знаете, проитерировать 7 миллионов чанков довольно непростая задача. Хотя и простая, но мы же хотим сделать это в адекватное время. Конечно, итерировать по TileEntities гораздо проще, но вот в чем проблема - этот метод возвращает пустой список(или ошибку), если мы пытаемся выполнить этот метод в асинхронном режиме. То есть, нужно это делать в основном потоке, что снижает продуктивность в разы, в десятки раз. Поэтому... стандартные методы от баккита не подходят.

После этого провала, мне пришла еще одна идея. Можно же это сделать напрямую в файлах мира. И да, кажется это сработало, ведь там уже можно применить и асинхронность и мультипоточность и т.д и т.п. Есть одна библиотека, которое позволяет сделать это на Java. Вот она - . Ну вот в принципе теория и всё. Дальше идет написание кода. Наверное, самое сложное было разобраться и начать вникать в саму библиотеку. Как работает формат Anvil, NBT и т.д. Но разобравшись с этим я написал класс, а в последствии и плагин.

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

Вот и полный класс (я не стал делать асинхронность/мультипоток т.к не вижу в этом смысла, те, кому это интересно и сами сделают):
Java:
public class NBTCleaner {

    private File worldFolder;

    private final List<File> files = new ArrayList<>();
    private final List<String> replaceList;

    private int filesReplaced = 0;
    private int tilesReplaced = 0;

    public NBTCleaner(File worldFolder, List<String> replaceList) {

        this.worldFolder = worldFolder;
        this.replaceList = replaceList;

        File regionFolder = new File(worldFolder, "region");

        for (File file : regionFolder.listFiles()) {
            if (file.getPath().endsWith(".mca")) {
                files.add(file);
            }
        }

    }

    public void start() throws IOException {

        for (File rawFile : files) {
            try {
                MCAFile file = MCAUtil.read(rawFile);
                Chunk[] chunks = (Chunk[]) FieldUtils.readField(file, "chunks", true);
                for (Chunk chunk : chunks) {
                    if (chunk == null) {
                        continue;
                    }
                    replaceBlocksInChunk(chunk);
                }
                saveFile(file);
                Bukkit.getLogger().info(String.format("Region file %s/%s has been replaced and saved - %s",
                        filesReplaced, files.size(), rawFile.getName()));
            } catch (IllegalAccessException e) {
                System.out.println("Cannot get chunks from file");
            }

        }

        Bukkit.getLogger().info("World cleaner done.");
        Bukkit.getLogger().info("Total tiles replaced: " + tilesReplaced);

    }

    public void saveFile(MCAFile file) throws IllegalAccessException, IOException {

        Files.createDirectories(Paths.get("region"));

        int regionX = (int) FieldUtils.readField(file, "regionX", true);
        int regionZ = (int) FieldUtils.readField(file, "regionZ", true);


        File regionSaveFolder = new File(JavaPlugin.getPlugin(WorldIterator.class).getDataFolder(), String.format("worlds/%s/region", worldFolder.getName()));

        if (!regionSaveFolder.exists()) {
            regionSaveFolder.mkdirs();
        }

        File fileToSave = new File(regionSaveFolder, String.format("r.%s.%s.mca", regionX, regionZ));
        MCAUtil.write(file, fileToSave);

        filesReplaced ++;

    }

    private void replaceBlocksInChunk(Chunk chunk) throws IllegalAccessException {

        for (CompoundTag tileEntity : chunk.getTileEntities()) {

            String id = tileEntity.get("id").valueToString();
            int x = Integer.parseInt(tileEntity.get("x").valueToString());
            int y = Integer.parseInt(tileEntity.get("y").valueToString());
            int z = Integer.parseInt(tileEntity.get("z").valueToString());

            id = id.replaceAll("\"", "");
            if ((!replaceList.isEmpty()) &&(!replaceList.contains(id))) {
                continue;
            }

            CompoundTag air = new CompoundTag();
            air.putString("Name", "minecraft:air");

            chunk.setBlockStateAt(x, y, z, air, false);
            tilesReplaced ++;

        }

    }

}

Прикрутить его к плагину не сложно и на выходе получается плагин со следующим конфигом:
YAML:
replace_list:
  - 'minecraft:juke_box'
  - 'minecraft:chest'
  - 'minecraft:ender_chest'
  - 'minecraft:anvil'
  - 'minecraft:chipped_anvil'
  - 'minecraft:damaged_anvil'
  - 'minecraft:shulker_box'
  - 'minecraft:barrel'
  - 'minecraft:furnace'

В этом конфиге список тайлов, которые плагин заменит на воздух. Если хотите, чтобы любой тайл заменялся на воздух - поставьте пустой список ( replace_list: [] ).

Дальше команды:
- /worldcleaner <worldName>
- /worldcleaner reload
Думаю, тут понятно, начать очистку и перезагрузить конфиг.

Когда очистка начата, в консоль будут каждые пару секунд приходить сообщения о прогрессе, конце, и сколько тайлов было удалено в конце. Все сообщения на английском, уж простите, но мне так удобнее.

После окончания очистки (которая, кстати никак не нагружает сервер т.к запускается в асинхронном потоке) в папке плагина будет папка worlds в ней будут папки с очищенными мирами. Но там будет не весь мир, а только папка region. Соответственно, выключаем сервер копируем что в папке region (или саму папку мира). И вставляем её туда, куда нужно (если скопировали что в region, вставляем в region мира, если целую папку, то в корень сервера) заменяем и запускаем сервер. При запуске сервера появятся ошибку по типу "Не можем создать тайл т.к его нету", эти ошибки нормальные, их можно игнорировать (дальше их не будет).

В целом всё. Хочу сказать, что я не несу ответственности за то, что будет с вашим миром, поэтому делайте бэкап. Я проверял на версии 1.16.5, вроде работает. Кстати о скорости - мир весом 500мб очищается 2 минуты, что по мне неплохо. Конечно, всё зависит от процессора, от веса мира т .д.

Надеюсь, было интересно почитать. Вот сам плагин:
 

Вложения

  • WorldIterator-1.0.jar
    674.7 KB · Просмотры: 7
Назад
Сверху Снизу