diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java index b0ef7fff..5e19218a 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java @@ -65,6 +65,7 @@ import com.sk89q.worldguard.protection.managers.migration.DriverMigration; import com.sk89q.worldguard.protection.managers.migration.MigrationException; import com.sk89q.worldguard.protection.managers.migration.UUIDMigration; +import com.sk89q.worldguard.protection.managers.migration.WorldHeightMigration; import com.sk89q.worldguard.protection.managers.storage.DriverType; import com.sk89q.worldguard.protection.managers.storage.RegionDriver; import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; @@ -1068,6 +1069,62 @@ public void migrateUuid(CommandContext args, Actor sender) throws CommandExcepti } } + + /** + * Migrate regions that went from 0-255 to new world heights. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"migrateheights"}, + usage = "[world]", max = 1, + flags = "yw:", + desc = "Migrate regions from old height limits to new height limits") + public void migrateHeights(CommandContext args, Actor sender) throws CommandException { + // Check permissions + if (!getPermissionModel(sender).mayMigrateRegionHeights()) { + throw new CommandPermissionsException(); + } + + if (!args.hasFlag('y')) { + throw new CommandException("This command is potentially dangerous.\n" + + "Please ensure you have made a backup of your data, and then re-enter the command with -y tacked on at the end to proceed."); + } + + World world = null; + try { + world = checkWorld(args, sender, 'w'); + } catch (CommandException ignored) { + } + + LoggerToChatHandler handler = null; + Logger minecraftLogger = null; + + if (sender instanceof LocalPlayer) { + handler = new LoggerToChatHandler(sender); + handler.setLevel(Level.ALL); + minecraftLogger = Logger.getLogger("com.sk89q.worldguard"); + minecraftLogger.addHandler(handler); + } + + try { + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionDriver driver = container.getDriver(); + WorldHeightMigration migration = new WorldHeightMigration(driver, WorldGuard.getInstance().getFlagRegistry(), world); + container.migrate(migration); + sender.print("Migration complete!"); + } catch (MigrationException e) { + log.log(Level.WARNING, "Failed to migrate", e); + throw new CommandException("Error encountered while migrating: " + e.getMessage()); + } finally { + if (minecraftLogger != null) { + minecraftLogger.removeHandler(handler); + } + } + } + + /** * Teleport to a region * diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java b/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java index 4fc45ecb..d289e921 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java @@ -63,7 +63,11 @@ public boolean mayMigrateRegionStore() { public boolean mayMigrateRegionNames() { return hasPluginPermission("region.migrateuuid"); } - + + public boolean mayMigrateRegionHeights() { + return hasPluginPermission("region.migrateheights"); + } + public boolean mayDefine() { return hasPluginPermission("region.define"); } diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java index 22a62c41..cc5cb66e 100644 --- a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java @@ -72,7 +72,7 @@ public final void migrate() throws MigrationException { * @param store the region store * @throws MigrationException on migration error */ - protected abstract void migrate(RegionDatabase store)throws MigrationException; + protected abstract void migrate(RegionDatabase store) throws MigrationException; /** * Called after migration has successfully completed. diff --git a/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/WorldHeightMigration.java b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/WorldHeightMigration.java new file mode 100644 index 00000000..dcd3859e --- /dev/null +++ b/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/WorldHeightMigration.java @@ -0,0 +1,114 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.migration; + +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class WorldHeightMigration extends AbstractMigration { + private static final Logger log = Logger.getLogger(WorldHeightMigration.class.getCanonicalName()); + + private static final Field minField; + private static final Field maxField; + static { + try { + minField = ProtectedRegion.class.getDeclaredField("min"); + minField.setAccessible(true); + maxField = ProtectedRegion.class.getDeclaredField("max"); + maxField.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError(new MigrationException("Migrator broke.", e)); + } + } + + private final FlagRegistry flagRegistry; + private final World world; + private int changed = 0; + + public WorldHeightMigration(RegionDriver driver, FlagRegistry flagRegistry, @Nullable World world) { + super(driver); + this.flagRegistry = flagRegistry; + this.world = world; + } + + @Override + protected void migrate(RegionDatabase store) throws MigrationException { + if (world != null && !store.getName().equals(world.getName())) return; + + log.log(Level.INFO, "Migrating regions in '" + store.getName() + "' to new height limits..."); + + Set regions; + + try { + regions = store.loadAll(flagRegistry); + } catch (StorageException e) { + throw new MigrationException("Failed to load region data for the world '" + store.getName() + "'", e); + } + + int min = -64; + int max = 319; + World world = WorldGuard.getInstance().getPlatform().getMatcher().getWorldByName(store.getName()); + if (world != null) { + min = world.getMinY(); + max = world.getMaxY(); + // in theory someone could run a data pack that keeps their world height + // at the old defaults...? either way if this is the case there are no changes to make. + if (min == 0 && max == 255) return; + } + for (ProtectedRegion region : regions) { + if (region.getMinimumPoint().getBlockY() <= 0 + && region.getMaximumPoint().getBlockY() >= 255) { + expand(region, min, max); + changed++; + } + } + try { + store.saveAll(regions); + } catch (StorageException e) { + throw new MigrationException("Failed to save region data after migration of the world '" + store.getName() + "'", e); + } + } + + private static void expand(ProtectedRegion region, int min, int max) throws MigrationException { + try { + minField.set(region, region.getMinimumPoint().withY(min)); + maxField.set(region, region.getMaximumPoint().withY(max)); + region.setDirty(true); + } catch (IllegalAccessException e) { + throw new MigrationException("Migrator broke.", e); + } + } + + @Override + protected void postMigration() { + log.log(Level.INFO, "A total of " + changed + " top-to-bottom regions were vertically expanded."); + } +}