001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core; 020 021import com.plotsquared.core.configuration.ConfigurationSection; 022import com.plotsquared.core.configuration.ConfigurationUtil; 023import com.plotsquared.core.configuration.MemorySection; 024import com.plotsquared.core.configuration.Settings; 025import com.plotsquared.core.configuration.Storage; 026import com.plotsquared.core.configuration.caption.CaptionMap; 027import com.plotsquared.core.configuration.caption.DummyCaptionMap; 028import com.plotsquared.core.configuration.caption.TranslatableCaption; 029import com.plotsquared.core.configuration.caption.load.CaptionLoader; 030import com.plotsquared.core.configuration.caption.load.DefaultCaptionProvider; 031import com.plotsquared.core.configuration.file.YamlConfiguration; 032import com.plotsquared.core.configuration.serialization.ConfigurationSerialization; 033import com.plotsquared.core.database.DBFunc; 034import com.plotsquared.core.database.Database; 035import com.plotsquared.core.database.MySQL; 036import com.plotsquared.core.database.SQLManager; 037import com.plotsquared.core.database.SQLite; 038import com.plotsquared.core.generator.GeneratorWrapper; 039import com.plotsquared.core.generator.HybridPlotWorld; 040import com.plotsquared.core.generator.HybridUtils; 041import com.plotsquared.core.generator.IndependentPlotGenerator; 042import com.plotsquared.core.inject.factory.HybridPlotWorldFactory; 043import com.plotsquared.core.listener.PlotListener; 044import com.plotsquared.core.location.Location; 045import com.plotsquared.core.player.PlayerMetaDataKeys; 046import com.plotsquared.core.plot.BlockBucket; 047import com.plotsquared.core.plot.Plot; 048import com.plotsquared.core.plot.PlotArea; 049import com.plotsquared.core.plot.PlotAreaTerrainType; 050import com.plotsquared.core.plot.PlotAreaType; 051import com.plotsquared.core.plot.PlotCluster; 052import com.plotsquared.core.plot.PlotId; 053import com.plotsquared.core.plot.PlotManager; 054import com.plotsquared.core.plot.expiration.ExpireManager; 055import com.plotsquared.core.plot.expiration.ExpiryTask; 056import com.plotsquared.core.plot.flag.GlobalFlagContainer; 057import com.plotsquared.core.plot.world.PlotAreaManager; 058import com.plotsquared.core.plot.world.SinglePlotArea; 059import com.plotsquared.core.plot.world.SinglePlotAreaManager; 060import com.plotsquared.core.util.EventDispatcher; 061import com.plotsquared.core.util.FileUtils; 062import com.plotsquared.core.util.LegacyConverter; 063import com.plotsquared.core.util.MathMan; 064import com.plotsquared.core.util.ReflectionUtils; 065import com.plotsquared.core.util.task.TaskManager; 066import com.plotsquared.core.uuid.UUIDPipeline; 067import com.sk89q.worldedit.WorldEdit; 068import com.sk89q.worldedit.event.platform.PlatformReadyEvent; 069import com.sk89q.worldedit.math.BlockVector2; 070import com.sk89q.worldedit.util.eventbus.EventHandler; 071import com.sk89q.worldedit.util.eventbus.Subscribe; 072import org.apache.logging.log4j.LogManager; 073import org.apache.logging.log4j.Logger; 074import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 075import org.checkerframework.checker.nullness.qual.NonNull; 076import org.checkerframework.checker.nullness.qual.Nullable; 077 078import java.io.BufferedReader; 079import java.io.File; 080import java.io.FileInputStream; 081import java.io.FileOutputStream; 082import java.io.IOException; 083import java.io.InputStream; 084import java.io.InputStreamReader; 085import java.io.ObjectInputStream; 086import java.io.ObjectOutputStream; 087import java.net.MalformedURLException; 088import java.net.URISyntaxException; 089import java.net.URL; 090import java.nio.file.Files; 091import java.nio.file.StandardOpenOption; 092import java.sql.SQLException; 093import java.util.ArrayDeque; 094import java.util.ArrayList; 095import java.util.Arrays; 096import java.util.Collection; 097import java.util.Collections; 098import java.util.Comparator; 099import java.util.HashMap; 100import java.util.HashSet; 101import java.util.Iterator; 102import java.util.List; 103import java.util.Locale; 104import java.util.Map; 105import java.util.Map.Entry; 106import java.util.Objects; 107import java.util.Set; 108import java.util.concurrent.Executors; 109import java.util.function.Consumer; 110import java.util.regex.Pattern; 111import java.util.zip.ZipEntry; 112import java.util.zip.ZipInputStream; 113 114/** 115 * An implementation of the core, with a static getter for easy access. 116 */ 117@SuppressWarnings({"WeakerAccess"}) 118public class PlotSquared { 119 120 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PlotSquared.class.getSimpleName()); 121 private static @MonotonicNonNull PlotSquared instance; 122 123 // Implementation 124 private final PlotPlatform<?> platform; 125 // Current thread 126 private final Thread thread; 127 // UUID pipelines 128 private final UUIDPipeline impromptuUUIDPipeline = 129 new UUIDPipeline(Executors.newCachedThreadPool()); 130 private final UUIDPipeline backgroundUUIDPipeline = 131 new UUIDPipeline(Executors.newSingleThreadExecutor()); 132 // Localization 133 private final Map<String, CaptionMap> captionMaps = new HashMap<>(); 134 public HashMap<String, HashMap<PlotId, Plot>> plots_tmp; 135 private CaptionLoader captionLoader; 136 // WorldEdit instance 137 private WorldEdit worldedit; 138 private File configFile; 139 private File worldsFile; 140 private YamlConfiguration worldConfiguration; 141 // Temporary hold the plots/clusters before the worlds load 142 private HashMap<String, Set<PlotCluster>> clustersTmp; 143 private YamlConfiguration config; 144 // Platform / Version / Update URL 145 private PlotVersion version; 146 // Files and configuration 147 private File jarFile = null; // This file 148 private File storageFile; 149 private EventDispatcher eventDispatcher; 150 private PlotListener plotListener; 151 152 private boolean weInitialised; 153 154 /** 155 * Initialize PlotSquared with the desired Implementation class. 156 * 157 * @param iPlotMain Implementation of {@link PlotPlatform} used 158 * @param platform The platform being used 159 */ 160 public PlotSquared( 161 final @NonNull PlotPlatform<?> iPlotMain, 162 final @NonNull String platform 163 ) { 164 if (instance != null) { 165 throw new IllegalStateException("Cannot re-initialize the PlotSquared singleton"); 166 } 167 instance = this; 168 169 this.thread = Thread.currentThread(); 170 this.platform = iPlotMain; 171 Settings.PLATFORM = platform; 172 173 // Initialize the class 174 PlayerMetaDataKeys.load(); 175 176 // 177 // Register configuration serializable classes 178 // 179 ConfigurationSerialization.registerClass(BlockBucket.class, "BlockBucket"); 180 181 // load configs before reading from settings 182 if (!setupConfigs()) { 183 return; 184 } 185 186 this.captionLoader = CaptionLoader.of( 187 Locale.ENGLISH, 188 CaptionLoader.patternExtractor(Pattern.compile("messages_(.*)\\.json")), 189 DefaultCaptionProvider.forClassLoaderFormatString( 190 this.getClass().getClassLoader(), 191 "lang/messages_%s.json" // the path in our jar file 192 ), 193 TranslatableCaption.DEFAULT_NAMESPACE 194 ); 195 // Load caption map 196 try { 197 this.loadCaptionMap(); 198 } catch (final Exception e) { 199 LOGGER.error("Failed to load caption map", e); 200 LOGGER.error("Shutting down server to prevent further issues"); 201 this.platform.shutdownServer(); 202 throw new RuntimeException("Abort loading PlotSquared"); 203 } 204 205 // Setup the global flag container 206 GlobalFlagContainer.setup(); 207 208 try { 209 new ReflectionUtils(this.platform.serverNativePackage()); 210 try { 211 URL logurl = PlotSquared.class.getProtectionDomain().getCodeSource().getLocation(); 212 this.jarFile = new File( 213 new URL(logurl.toURI().toString().split("\\!")[0].replaceAll("jar:file", "file")) 214 .toURI().getPath()); 215 } catch (MalformedURLException | URISyntaxException | SecurityException e) { 216 e.printStackTrace(); 217 this.jarFile = new File(this.platform.getDirectory().getParentFile(), "PlotSquared.jar"); 218 if (!this.jarFile.exists()) { 219 this.jarFile = new File( 220 this.platform.getDirectory().getParentFile(), 221 "PlotSquared-" + platform + ".jar" 222 ); 223 } 224 } 225 226 this.worldedit = WorldEdit.getInstance(); 227 WorldEdit.getInstance().getEventBus().register(new WEPlatformReadyListener()); 228 229 // Create Event utility class 230 this.eventDispatcher = new EventDispatcher(this.worldedit); 231 // Create plot listener 232 this.plotListener = new PlotListener(this.eventDispatcher); 233 234 // Copy files 235 copyFile("town.template", Settings.Paths.TEMPLATES); 236 copyFile("bridge.template", Settings.Paths.TEMPLATES); 237 copyFile("skyblock.template", Settings.Paths.TEMPLATES); 238 showDebug(); 239 } catch (Throwable e) { 240 e.printStackTrace(); 241 } 242 } 243 244 /** 245 * Gets an instance of PlotSquared. 246 * 247 * @return instance of PlotSquared 248 */ 249 public static @NonNull PlotSquared get() { 250 return PlotSquared.instance; 251 } 252 253 /** 254 * Get the platform specific implementation of PlotSquared 255 * 256 * @return Platform implementation 257 */ 258 public static @NonNull PlotPlatform<?> platform() { 259 if (instance != null && instance.platform != null) { 260 return instance.platform; 261 } 262 throw new IllegalStateException("Plot platform implementation is missing"); 263 } 264 265 public void loadCaptionMap() throws Exception { 266 this.platform.copyCaptionMaps(); 267 // Setup localization 268 CaptionMap captionMap; 269 if (Settings.Enabled_Components.PER_USER_LOCALE) { 270 captionMap = this.captionLoader.loadAll(this.platform.getDirectory().toPath().resolve("lang")); 271 } else { 272 String fileName = "messages_" + Settings.Enabled_Components.DEFAULT_LOCALE + ".json"; 273 captionMap = this.captionLoader.loadOrCreateSingle(this.platform 274 .getDirectory() 275 .toPath() 276 .resolve("lang") 277 .resolve(fileName)); 278 } 279 this.captionMaps.put(TranslatableCaption.DEFAULT_NAMESPACE, captionMap); 280 LOGGER.info( 281 "Loaded caption map for namespace 'plotsquared': {}", 282 this.captionMaps.get(TranslatableCaption.DEFAULT_NAMESPACE).getClass().getCanonicalName() 283 ); 284 } 285 286 /** 287 * Get the platform specific {@link PlotAreaManager} instance 288 * 289 * @return Plot area manager 290 */ 291 public @NonNull PlotAreaManager getPlotAreaManager() { 292 return this.platform.plotAreaManager(); 293 } 294 295 public void startExpiryTasks() { 296 if (Settings.Enabled_Components.PLOT_EXPIRY) { 297 ExpireManager expireManager = PlotSquared.platform().expireManager(); 298 expireManager.runAutomatedTask(); 299 for (Settings.Auto_Clear settings : Settings.AUTO_CLEAR.getInstances()) { 300 ExpiryTask task = new ExpiryTask(settings, this.getPlotAreaManager()); 301 expireManager.addTask(task); 302 } 303 } 304 } 305 306 public boolean isMainThread(final @NonNull Thread thread) { 307 return this.thread == thread; 308 } 309 310 /** 311 * Check if `version` is >= `version2`. 312 * 313 * @param version First version 314 * @param version2 Second version 315 * @return {@code true} if `version` is >= `version2` 316 */ 317 public boolean checkVersion( 318 final int[] version, 319 final int... version2 320 ) { 321 return version[0] > version2[0] || version[0] == version2[0] && version[1] > version2[1] 322 || version[0] == version2[0] && version[1] == version2[1] && version[2] >= version2[2]; 323 } 324 325 /** 326 * Gets the current PlotSquared version. 327 * 328 * @return current version in config or null 329 */ 330 public @NonNull PlotVersion getVersion() { 331 return this.version; 332 } 333 334 /** 335 * Gets the server platform this plugin is running on this is running on. 336 * 337 * <p>This will be either <b>Bukkit</b> or <b>Sponge</b></p> 338 * 339 * @return the server implementation 340 */ 341 public @NonNull String getPlatform() { 342 return Settings.PLATFORM; 343 } 344 345 /** 346 * Add a global reference to a plot world. 347 * <p> 348 * You can remove the reference by calling {@link #removePlotArea(PlotArea)} 349 * </p> 350 * 351 * @param plotArea the {@link PlotArea} to add. 352 */ 353 @SuppressWarnings("unchecked") 354 public void addPlotArea(final @NonNull PlotArea plotArea) { 355 HashMap<PlotId, Plot> plots; 356 if (plots_tmp == null || (plots = plots_tmp.remove(plotArea.toString())) == null) { 357 if (plotArea.getType() == PlotAreaType.PARTIAL) { 358 plots = this.plots_tmp != null ? this.plots_tmp.get(plotArea.getWorldName()) : null; 359 if (plots != null) { 360 Iterator<Entry<PlotId, Plot>> iterator = plots.entrySet().iterator(); 361 while (iterator.hasNext()) { 362 Entry<PlotId, Plot> next = iterator.next(); 363 PlotId id = next.getKey(); 364 if (plotArea.contains(id)) { 365 next.getValue().setArea(plotArea); 366 iterator.remove(); 367 } 368 } 369 } 370 } 371 } else { 372 for (Plot entry : plots.values()) { 373 entry.setArea(plotArea); 374 } 375 } 376 Set<PlotCluster> clusters; 377 if (clustersTmp == null || (clusters = clustersTmp.remove(plotArea.toString())) == null) { 378 if (plotArea.getType() == PlotAreaType.PARTIAL) { 379 clusters = this.clustersTmp != null ? 380 this.clustersTmp.get(plotArea.getWorldName()) : 381 null; 382 if (clusters != null) { 383 Iterator<PlotCluster> iterator = clusters.iterator(); 384 while (iterator.hasNext()) { 385 PlotCluster next = iterator.next(); 386 if (next.intersects(plotArea.getMin(), plotArea.getMax())) { 387 next.setArea(plotArea); 388 iterator.remove(); 389 } 390 } 391 } 392 } 393 } else { 394 for (PlotCluster cluster : clusters) { 395 cluster.setArea(plotArea); 396 } 397 } 398 getPlotAreaManager().addPlotArea(plotArea); 399 plotArea.setupBorder(); 400 if (!Settings.Enabled_Components.PERSISTENT_ROAD_REGEN) { 401 return; 402 } 403 File file = new File( 404 this.platform.getDirectory() + File.separator + "persistent_regen_data_" + plotArea.getId() 405 + "_" + plotArea.getWorldName()); 406 if (!file.exists()) { 407 return; 408 } 409 TaskManager.runTaskAsync(() -> { 410 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { 411 List<Object> list = (List<Object>) ois.readObject(); 412 ArrayList<int[]> regionInts = (ArrayList<int[]>) list.get(0); 413 ArrayList<int[]> chunkInts = (ArrayList<int[]>) list.get(1); 414 HashSet<BlockVector2> regions = new HashSet<>(); 415 Set<BlockVector2> chunks = new HashSet<>(); 416 regionInts.forEach(l -> regions.add(BlockVector2.at(l[0], l[1]))); 417 chunkInts.forEach(l -> chunks.add(BlockVector2.at(l[0], l[1]))); 418 int height = (int) list.get(2); 419 LOGGER.info( 420 "Incomplete road regeneration found. Restarting in world {} with height {}", 421 plotArea.getWorldName(), 422 height 423 ); 424 LOGGER.info("- Regions: {}", regions.size()); 425 LOGGER.info("- Chunks: {}", chunks.size()); 426 HybridUtils.UPDATE = true; 427 PlotSquared.platform().hybridUtils().scheduleRoadUpdate(plotArea, regions, height, chunks); 428 } catch (IOException | ClassNotFoundException e) { 429 LOGGER.error("Error restarting road regeneration", e); 430 } finally { 431 if (!file.delete()) { 432 LOGGER.error("Error deleting persistent_regen_data_{}. Please delete this file manually", plotArea.getId()); 433 } 434 } 435 }); 436 } 437 438 /** 439 * Remove a plot world reference. 440 * 441 * @param area the {@link PlotArea} to remove 442 */ 443 public void removePlotArea(final @NonNull PlotArea area) { 444 getPlotAreaManager().removePlotArea(area); 445 setPlotsTmp(area); 446 } 447 448 public void removePlotAreas(final @NonNull String world) { 449 for (final PlotArea area : this.getPlotAreaManager().getPlotAreasSet(world)) { 450 if (area.getWorldName().equals(world)) { 451 removePlotArea(area); 452 } 453 } 454 } 455 456 private void setPlotsTmp(final @NonNull PlotArea area) { 457 if (this.plots_tmp == null) { 458 this.plots_tmp = new HashMap<>(); 459 } 460 HashMap<PlotId, Plot> map = 461 this.plots_tmp.computeIfAbsent(area.toString(), k -> new HashMap<>()); 462 for (Plot plot : area.getPlots()) { 463 map.put(plot.getId(), plot); 464 } 465 if (this.clustersTmp == null) { 466 this.clustersTmp = new HashMap<>(); 467 } 468 this.clustersTmp.put(area.toString(), area.getClusters()); 469 } 470 471 public Set<PlotCluster> getClusters(final @NonNull String world) { 472 final Set<PlotCluster> set = new HashSet<>(); 473 for (final PlotArea area : this.getPlotAreaManager().getPlotAreasSet(world)) { 474 set.addAll(area.getClusters()); 475 } 476 return Collections.unmodifiableSet(set); 477 478 } 479 480 public List<Plot> sortPlotsByTemp(Collection<Plot> plots) { 481 int max = 0; 482 int overflowCount = 0; 483 for (Plot plot : plots) { 484 if (plot.temp > 0) { 485 if (plot.temp > max) { 486 max = plot.temp; 487 } 488 } else { 489 overflowCount++; 490 } 491 } 492 Plot[] array = new Plot[max + 1]; 493 List<Plot> overflow = new ArrayList<>(overflowCount); 494 for (Plot plot : plots) { 495 if (plot.temp <= 0) { 496 overflow.add(plot); 497 } else { 498 array[plot.temp] = plot; 499 } 500 } 501 ArrayList<Plot> result = new ArrayList<>(plots.size()); 502 for (Plot plot : array) { 503 if (plot != null) { 504 result.add(plot); 505 } 506 } 507 overflow.sort(Comparator.comparingInt(Plot::hashCode)); 508 result.addAll(overflow); 509 return result; 510 } 511 512 /** 513 * Sort plots by hashcode. 514 * 515 * @param plots the collection of plots to sort 516 * @return the sorted collection 517 */ 518 private ArrayList<Plot> sortPlotsByHash(Collection<Plot> plots) { 519 int hardmax = 256000; 520 int max = 0; 521 int overflowSize = 0; 522 for (Plot plot : plots) { 523 int hash = MathMan.getPositiveId(plot.hashCode()); 524 if (hash > max) { 525 if (hash >= hardmax) { 526 overflowSize++; 527 } else { 528 max = hash; 529 } 530 } 531 } 532 hardmax = Math.min(hardmax, max); 533 Plot[] cache = new Plot[hardmax + 1]; 534 List<Plot> overflow = new ArrayList<>(overflowSize); 535 ArrayList<Plot> extra = new ArrayList<>(); 536 for (Plot plot : plots) { 537 int hash = MathMan.getPositiveId(plot.hashCode()); 538 if (hash < hardmax) { 539 if (hash >= 0) { 540 cache[hash] = plot; 541 } else { 542 extra.add(plot); 543 } 544 } else if (Math.abs(plot.getId().getX()) > 15446 || Math.abs(plot.getId().getY()) > 15446) { 545 extra.add(plot); 546 } else { 547 overflow.add(plot); 548 } 549 } 550 Plot[] overflowArray = overflow.toArray(new Plot[0]); 551 sortPlotsByHash(overflowArray); 552 ArrayList<Plot> result = new ArrayList<>(cache.length + overflowArray.length); 553 for (Plot plot : cache) { 554 if (plot != null) { 555 result.add(plot); 556 } 557 } 558 Collections.addAll(result, overflowArray); 559 result.addAll(extra); 560 return result; 561 } 562 563 /** 564 * Unchecked, use {@link #sortPlots(Collection, SortType, PlotArea)} instead which will in turn call this. 565 * 566 * @param input an array of plots to sort 567 */ 568 @SuppressWarnings("unchecked") 569 private void sortPlotsByHash(final @NonNull Plot @NonNull [] input) { 570 List<Plot>[] bucket = new ArrayList[32]; 571 Arrays.fill(bucket, new ArrayList<>()); 572 boolean maxLength = false; 573 int placement = 1; 574 while (!maxLength) { 575 maxLength = true; 576 for (Plot plot : input) { 577 int tmp = MathMan.getPositiveId(plot.hashCode()) / placement; 578 bucket[tmp & 31].add(plot); 579 if (maxLength && tmp > 0) { 580 maxLength = false; 581 } 582 } 583 int a = 0; 584 for (int i = 0; i < 32; i++) { 585 for (Plot plot : bucket[i]) { 586 input[a++] = plot; 587 } 588 bucket[i].clear(); 589 } 590 placement *= 32; 591 } 592 } 593 594 private @NonNull List<Plot> sortPlotsByTimestamp(final @NonNull Collection<Plot> plots) { 595 int hardMax = 256000; 596 int max = 0; 597 int overflowSize = 0; 598 for (final Plot plot : plots) { 599 int hash = MathMan.getPositiveId(plot.hashCode()); 600 if (hash > max) { 601 if (hash >= hardMax) { 602 overflowSize++; 603 } else { 604 max = hash; 605 } 606 } 607 } 608 hardMax = Math.min(hardMax, max); 609 Plot[] cache = new Plot[hardMax + 1]; 610 List<Plot> overflow = new ArrayList<>(overflowSize); 611 ArrayList<Plot> extra = new ArrayList<>(); 612 for (Plot plot : plots) { 613 int hash = MathMan.getPositiveId(plot.hashCode()); 614 if (hash < hardMax) { 615 if (hash >= 0) { 616 cache[hash] = plot; 617 } else { 618 extra.add(plot); 619 } 620 } else if (Math.abs(plot.getId().getX()) > 15446 || Math.abs(plot.getId().getY()) > 15446) { 621 extra.add(plot); 622 } else { 623 overflow.add(plot); 624 } 625 } 626 Plot[] overflowArray = overflow.toArray(new Plot[0]); 627 sortPlotsByHash(overflowArray); 628 ArrayList<Plot> result = new ArrayList<>(cache.length + overflowArray.length); 629 for (Plot plot : cache) { 630 if (plot != null) { 631 result.add(plot); 632 } 633 } 634 Collections.addAll(result, overflowArray); 635 result.addAll(extra); 636 return result; 637 } 638 639 /** 640 * Sort plots by creation timestamp. 641 * 642 * @param input Plots to sort 643 * @return Sorted list 644 */ 645 private @NonNull List<Plot> sortPlotsByModified(final @NonNull Collection<Plot> input) { 646 List<Plot> list; 647 if (input instanceof List) { 648 list = (List<Plot>) input; 649 } else { 650 list = new ArrayList<>(input); 651 } 652 ExpireManager expireManager = PlotSquared.platform().expireManager(); 653 list.sort(Comparator.comparingLong(a -> expireManager.getTimestamp(a.getOwnerAbs()))); 654 return list; 655 } 656 657 /** 658 * Sort a collection of plots by world (with a priority world), then 659 * by hashcode. 660 * 661 * @param plots the plots to sort 662 * @param type The sorting method to use for each world (timestamp, or hash) 663 * @param priorityArea Use null, "world", or "gibberish" if you 664 * want default world order 665 * @return ArrayList of plot 666 */ 667 public @NonNull List<Plot> sortPlots( 668 final @NonNull Collection<Plot> plots, 669 final @NonNull SortType type, 670 final @Nullable PlotArea priorityArea 671 ) { 672 // group by world 673 // sort each 674 HashMap<PlotArea, Collection<Plot>> map = new HashMap<>(); 675 int totalSize = Arrays.stream(this.getPlotAreaManager().getAllPlotAreas()).mapToInt(PlotArea::getPlotCount).sum(); 676 if (plots.size() == totalSize) { 677 for (PlotArea area : getPlotAreaManager().getAllPlotAreas()) { 678 map.put(area, area.getPlots()); 679 } 680 } else { 681 for (PlotArea area : getPlotAreaManager().getAllPlotAreas()) { 682 map.put(area, new ArrayList<>(0)); 683 } 684 Collection<Plot> lastList = null; 685 PlotArea lastWorld = null; 686 for (Plot plot : plots) { 687 if (lastWorld == plot.getArea()) { 688 lastList.add(plot); 689 } else { 690 lastWorld = plot.getArea(); 691 lastList = map.get(lastWorld); 692 lastList.add(plot); 693 } 694 } 695 } 696 List<PlotArea> areas = Arrays.asList(getPlotAreaManager().getAllPlotAreas()); 697 areas.sort((a, b) -> { 698 if (priorityArea != null) { 699 if (a.equals(priorityArea)) { 700 return -1; 701 } else if (b.equals(priorityArea)) { 702 return 1; 703 } 704 } 705 return a.hashCode() - b.hashCode(); 706 }); 707 ArrayList<Plot> toReturn = new ArrayList<>(plots.size()); 708 for (PlotArea area : areas) { 709 switch (type) { 710 case CREATION_DATE -> toReturn.addAll(sortPlotsByTemp(map.get(area))); 711 case CREATION_DATE_TIMESTAMP -> toReturn.addAll(sortPlotsByTimestamp(map.get(area))); 712 case DISTANCE_FROM_ORIGIN -> toReturn.addAll(sortPlotsByHash(map.get(area))); 713 case LAST_MODIFIED -> toReturn.addAll(sortPlotsByModified(map.get(area))); 714 default -> { 715 } 716 } 717 } 718 return toReturn; 719 } 720 721 public void setPlots(final @NonNull Map<String, HashMap<PlotId, Plot>> plots) { 722 if (this.plots_tmp == null) { 723 this.plots_tmp = new HashMap<>(); 724 } 725 for (final Entry<String, HashMap<PlotId, Plot>> entry : plots.entrySet()) { 726 final String world = entry.getKey(); 727 final PlotArea plotArea = this.getPlotAreaManager().getPlotArea(world, null); 728 if (plotArea == null) { 729 Map<PlotId, Plot> map = this.plots_tmp.computeIfAbsent(world, k -> new HashMap<>()); 730 map.putAll(entry.getValue()); 731 } else { 732 for (Plot plot : entry.getValue().values()) { 733 plot.setArea(plotArea); 734 plotArea.addPlot(plot); 735 } 736 } 737 } 738 } 739 740 /** 741 * Unregisters a plot from local memory without calling the database. 742 * 743 * @param plot the plot to remove 744 * @param callEvent If to call an event about the plot being removed 745 * @return {@code true} if plot existed | {@code false} if it didn't 746 */ 747 public boolean removePlot( 748 final @NonNull Plot plot, 749 final boolean callEvent 750 ) { 751 if (plot == null) { 752 return false; 753 } 754 if (callEvent) { 755 eventDispatcher.callDelete(plot); 756 } 757 if (plot.getArea().removePlot(plot.getId())) { 758 PlotId last = (PlotId) plot.getArea().getMeta("lastPlot"); 759 int last_max = Math.max(Math.abs(last.getX()), Math.abs(last.getY())); 760 int this_max = Math.max(Math.abs(plot.getId().getX()), Math.abs(plot.getId().getY())); 761 if (this_max < last_max) { 762 plot.getArea().setMeta("lastPlot", plot.getId()); 763 } 764 if (callEvent) { 765 eventDispatcher.callPostDelete(plot); 766 } 767 return true; 768 } 769 return false; 770 } 771 772 /** 773 * This method is called by the PlotGenerator class normally. 774 * <ul> 775 * <li>Initializes the PlotArea and PlotManager classes 776 * <li>Registers the PlotArea and PlotManager classes 777 * <li>Loads (and/or generates) the PlotArea configuration 778 * <li>Sets up the world border if configured 779 * </ul> 780 * 781 * <p>If loading an augmented plot world: 782 * <ul> 783 * <li>Creates the AugmentedPopulator classes 784 * <li>Injects the AugmentedPopulator classes if required 785 * </ul> 786 * 787 * @param world the world to load 788 * @param baseGenerator The generator for that world, or null 789 */ 790 public void loadWorld( 791 final @NonNull String world, 792 final @Nullable GeneratorWrapper<?> baseGenerator 793 ) { 794 if (world.equals("CheckingPlotSquaredGenerator")) { 795 return; 796 } 797 if (!this.getPlotAreaManager().addWorld(world)) { 798 return; 799 } 800 Set<String> worlds; 801 if (this.worldConfiguration.contains("worlds")) { 802 worlds = this.worldConfiguration.getConfigurationSection("worlds").getKeys(false); 803 } else { 804 worlds = new HashSet<>(); 805 } 806 String path = "worlds." + world; 807 ConfigurationSection worldSection = this.worldConfiguration.getConfigurationSection(path); 808 PlotAreaType type; 809 if (worldSection != null) { 810 type = ConfigurationUtil.getType(worldSection); 811 } else { 812 type = PlotAreaType.NORMAL; 813 } 814 if (type == PlotAreaType.NORMAL) { 815 if (getPlotAreaManager().getPlotAreas(world, null).length != 0) { 816 return; 817 } 818 IndependentPlotGenerator plotGenerator; 819 if (baseGenerator != null && baseGenerator.isFull()) { 820 plotGenerator = baseGenerator.getPlotGenerator(); 821 } else if (worldSection != null) { 822 String secondaryGeneratorName = worldSection.getString("generator.plugin"); 823 GeneratorWrapper<?> secondaryGenerator = 824 this.platform.getGenerator(world, secondaryGeneratorName); 825 if (secondaryGenerator != null && secondaryGenerator.isFull()) { 826 plotGenerator = secondaryGenerator.getPlotGenerator(); 827 } else { 828 String primaryGeneratorName = worldSection.getString("generator.init"); 829 GeneratorWrapper<?> primaryGenerator = 830 this.platform.getGenerator(world, primaryGeneratorName); 831 if (primaryGenerator != null && primaryGenerator.isFull()) { 832 plotGenerator = primaryGenerator.getPlotGenerator(); 833 } else { 834 return; 835 } 836 } 837 } else { 838 return; 839 } 840 // Conventional plot generator 841 PlotArea plotArea = plotGenerator.getNewPlotArea(world, null, null, null); 842 PlotManager plotManager = plotArea.getPlotManager(); 843 LOGGER.info("Detected world load for '{}'", world); 844 LOGGER.info("- generator: {}>{}", baseGenerator, plotGenerator); 845 LOGGER.info("- plot world: {}", plotArea.getClass().getCanonicalName()); 846 LOGGER.info("- plot area manager: {}", plotManager.getClass().getCanonicalName()); 847 if (!this.worldConfiguration.contains(path)) { 848 this.worldConfiguration.createSection(path); 849 worldSection = this.worldConfiguration.getConfigurationSection(path); 850 } 851 plotArea.saveConfiguration(worldSection); 852 plotArea.loadDefaultConfiguration(worldSection); 853 try { 854 this.worldConfiguration.save(this.worldsFile); 855 } catch (IOException e) { 856 e.printStackTrace(); 857 } 858 // Now add it 859 addPlotArea(plotArea); 860 plotGenerator.initialize(plotArea); 861 } else { 862 if (!worlds.contains(world)) { 863 return; 864 } 865 ConfigurationSection areasSection = worldSection.getConfigurationSection("areas"); 866 if (areasSection == null) { 867 if (getPlotAreaManager().getPlotAreas(world, null).length != 0) { 868 return; 869 } 870 LOGGER.info("Detected world load for '{}'", world); 871 String gen_string = worldSection.getString("generator.plugin", platform.pluginName()); 872 if (type == PlotAreaType.PARTIAL) { 873 Set<PlotCluster> clusters = 874 this.clustersTmp != null ? this.clustersTmp.get(world) : new HashSet<>(); 875 if (clusters == null) { 876 throw new IllegalArgumentException("No cluster exists for world: " + world); 877 } 878 ArrayDeque<PlotArea> toLoad = new ArrayDeque<>(); 879 for (PlotCluster cluster : clusters) { 880 PlotId pos1 = cluster.getP1(); // Cluster pos1 881 PlotId pos2 = cluster.getP2(); // Cluster pos2 882 String name = cluster.getName(); // Cluster name 883 String fullId = name + "-" + pos1 + "-" + pos2; 884 worldSection.createSection("areas." + fullId); 885 DBFunc.replaceWorld(world, world + ";" + name, pos1, pos2); // NPE 886 LOGGER.info("- {}-{}-{}", name, pos1, pos2); 887 GeneratorWrapper<?> areaGen = this.platform.getGenerator(world, gen_string); 888 if (areaGen == null) { 889 throw new IllegalArgumentException("Invalid Generator: " + gen_string); 890 } 891 PlotArea pa = 892 areaGen.getPlotGenerator().getNewPlotArea(world, name, pos1, pos2); 893 pa.saveConfiguration(worldSection); 894 pa.loadDefaultConfiguration(worldSection); 895 try { 896 this.worldConfiguration.save(this.worldsFile); 897 } catch (IOException e) { 898 e.printStackTrace(); 899 } 900 LOGGER.info("| generator: {}>{}", baseGenerator, areaGen); 901 LOGGER.info("| plot world: {}", pa.getClass().getCanonicalName()); 902 LOGGER.info("| manager: {}", pa.getPlotManager().getClass().getCanonicalName()); 903 LOGGER.info("Note: Area created for cluster '{}' (invalid or old configuration?)", name); 904 areaGen.getPlotGenerator().initialize(pa); 905 areaGen.augment(pa); 906 toLoad.add(pa); 907 } 908 for (PlotArea area : toLoad) { 909 addPlotArea(area); 910 } 911 return; 912 } 913 GeneratorWrapper<?> areaGen = this.platform.getGenerator(world, gen_string); 914 if (areaGen == null) { 915 throw new IllegalArgumentException("Invalid Generator: " + gen_string); 916 } 917 PlotArea pa = areaGen.getPlotGenerator().getNewPlotArea(world, null, null, null); 918 LOGGER.info("- generator: {}>{}", baseGenerator, areaGen); 919 LOGGER.info("- plot world: {}", pa.getClass().getCanonicalName()); 920 LOGGER.info("- plot area manager: {}", pa.getPlotManager().getClass().getCanonicalName()); 921 if (!this.worldConfiguration.contains(path)) { 922 this.worldConfiguration.createSection(path); 923 worldSection = this.worldConfiguration.getConfigurationSection(path); 924 } 925 pa.saveConfiguration(worldSection); 926 pa.loadDefaultConfiguration(worldSection); 927 try { 928 this.worldConfiguration.save(this.worldsFile); 929 } catch (IOException e) { 930 e.printStackTrace(); 931 } 932 areaGen.getPlotGenerator().initialize(pa); 933 areaGen.augment(pa); 934 addPlotArea(pa); 935 return; 936 } 937 if (type == PlotAreaType.AUGMENTED) { 938 throw new IllegalArgumentException( 939 "Invalid type for multi-area world. Expected `PARTIAL`, got `" 940 + PlotAreaType.AUGMENTED + "`"); 941 } 942 for (String areaId : areasSection.getKeys(false)) { 943 LOGGER.info("- {}", areaId); 944 String[] split = areaId.split("(?<=[^;-])-"); 945 if (split.length != 3) { 946 throw new IllegalArgumentException("Invalid Area identifier: " + areaId 947 + ". Expected form `<name>-<pos1>-<pos2>`"); 948 } 949 String name = split[0]; 950 PlotId pos1 = PlotId.fromString(split[1]); 951 PlotId pos2 = PlotId.fromString(split[2]); 952 if (name.isEmpty()) { 953 throw new IllegalArgumentException("Invalid Area identifier: " + areaId 954 + ". Expected form `<name>-<x1;z1>-<x2;z2>`"); 955 } 956 final PlotArea existing = this.getPlotAreaManager().getPlotArea(world, name); 957 if (existing != null && name.equals(existing.getId())) { 958 continue; 959 } 960 ConfigurationSection section = areasSection.getConfigurationSection(areaId); 961 YamlConfiguration clone = new YamlConfiguration(); 962 for (String key : section.getKeys(true)) { 963 if (section.get(key) instanceof MemorySection) { 964 continue; 965 } 966 if (!clone.contains(key)) { 967 clone.set(key, section.get(key)); 968 } 969 } 970 for (String key : worldSection.getKeys(true)) { 971 if (worldSection.get(key) instanceof MemorySection) { 972 continue; 973 } 974 if (!key.startsWith("areas") && !clone.contains(key)) { 975 clone.set(key, worldSection.get(key)); 976 } 977 } 978 String gen_string = clone.getString("generator.plugin", platform.pluginName()); 979 GeneratorWrapper<?> areaGen = this.platform.getGenerator(world, gen_string); 980 if (areaGen == null) { 981 throw new IllegalArgumentException("Invalid Generator: " + gen_string); 982 } 983 PlotArea pa = areaGen.getPlotGenerator().getNewPlotArea(world, name, pos1, pos2); 984 pa.saveConfiguration(clone); 985 // netSections is the combination of 986 for (String key : clone.getKeys(true)) { 987 if (clone.get(key) instanceof MemorySection) { 988 continue; 989 } 990 if (!worldSection.contains(key)) { 991 worldSection.set(key, clone.get(key)); 992 } else { 993 Object value = worldSection.get(key); 994 if (!Objects.equals(value, clone.get(key))) { 995 section.set(key, clone.get(key)); 996 } 997 } 998 } 999 pa.loadDefaultConfiguration(clone); 1000 try { 1001 this.worldConfiguration.save(this.worldsFile); 1002 } catch (IOException e) { 1003 e.printStackTrace(); 1004 } 1005 LOGGER.info("Detected area load for '{}'", world); 1006 LOGGER.info("| generator: {}>{}", baseGenerator, areaGen); 1007 LOGGER.info("| plot world: {}", pa); 1008 LOGGER.info("| manager: {}", pa.getPlotManager()); 1009 areaGen.getPlotGenerator().initialize(pa); 1010 areaGen.augment(pa); 1011 addPlotArea(pa); 1012 } 1013 } 1014 } 1015 1016 /** 1017 * Setup the configuration for a plot world based on world arguments. 1018 * <p> 1019 * 1020 * <i>e.g. /mv create <world> normal -g PlotSquared:<args></i> 1021 * 1022 * @param world The name of the world 1023 * @param args The arguments 1024 * @param generator the plot generator 1025 * @return boolean | if valid arguments were provided 1026 */ 1027 public boolean setupPlotWorld( 1028 final @NonNull String world, 1029 final @Nullable String args, 1030 final @NonNull IndependentPlotGenerator generator 1031 ) { 1032 if (args != null && !args.isEmpty()) { 1033 // save configuration 1034 1035 final List<String> validArguments = Arrays 1036 .asList("s=", "size=", "g=", "gap=", "h=", "height=", "minh=", "minheight=", "maxh=", "maxheight=", 1037 "f=", "floor=", "m=", "main=", "w=", "wall=", "b=", "border=" 1038 ); 1039 1040 // Calculate the number of expected arguments 1041 int expected = (int) validArguments.stream() 1042 .filter(validArgument -> args.toLowerCase(Locale.ENGLISH).contains(validArgument)) 1043 .count(); 1044 1045 String[] split = args.toLowerCase(Locale.ENGLISH).split(",(?![^\\(\\[]*[\\]\\)])"); 1046 1047 if (split.length > expected) { 1048 // This means we have multi-block block buckets 1049 String[] combinedArgs = new String[expected]; 1050 int index = 0; 1051 1052 StringBuilder argBuilder = new StringBuilder(); 1053 outer: 1054 for (final String string : split) { 1055 for (final String validArgument : validArguments) { 1056 if (string.contains(validArgument)) { 1057 if (!argBuilder.toString().isEmpty()) { 1058 combinedArgs[index++] = argBuilder.toString(); 1059 argBuilder = new StringBuilder(); 1060 } 1061 argBuilder.append(string); 1062 continue outer; 1063 } 1064 } 1065 if (argBuilder.toString().charAt(argBuilder.length() - 1) != '=') { 1066 argBuilder.append(","); 1067 } 1068 argBuilder.append(string); 1069 } 1070 1071 if (!argBuilder.toString().isEmpty()) { 1072 combinedArgs[index] = argBuilder.toString(); 1073 } 1074 1075 split = combinedArgs; 1076 } 1077 1078 final HybridPlotWorldFactory hybridPlotWorldFactory = this.platform 1079 .injector() 1080 .getInstance(HybridPlotWorldFactory.class); 1081 final HybridPlotWorld plotWorld = hybridPlotWorldFactory.create(world, null, generator, null, null); 1082 1083 for (String element : split) { 1084 String[] pair = element.split("="); 1085 if (pair.length != 2) { 1086 LOGGER.error("No value provided for '{}'", element); 1087 return false; 1088 } 1089 String key = pair[0].toLowerCase(); 1090 String value = pair[1]; 1091 try { 1092 String base = "worlds." + world + "."; 1093 switch (key) { 1094 case "s", "size" -> this.worldConfiguration.set( 1095 base + "plot.size", 1096 ConfigurationUtil.INTEGER.parseString(value).shortValue() 1097 ); 1098 case "g", "gap" -> this.worldConfiguration.set( 1099 base + "road.width", 1100 ConfigurationUtil.INTEGER.parseString(value).shortValue() 1101 ); 1102 case "h", "height" -> { 1103 this.worldConfiguration.set( 1104 base + "road.height", 1105 ConfigurationUtil.INTEGER.parseString(value).shortValue() 1106 ); 1107 this.worldConfiguration.set( 1108 base + "plot.height", 1109 ConfigurationUtil.INTEGER.parseString(value).shortValue() 1110 ); 1111 this.worldConfiguration.set( 1112 base + "wall.height", 1113 ConfigurationUtil.INTEGER.parseString(value).shortValue() 1114 ); 1115 } 1116 case "minh", "minheight" -> this.worldConfiguration.set( 1117 base + "world.min_gen_height", 1118 ConfigurationUtil.INTEGER.parseString(value).shortValue() 1119 ); 1120 case "maxh", "maxheight" -> this.worldConfiguration.set( 1121 base + "world.max_gen_height", 1122 ConfigurationUtil.INTEGER.parseString(value).shortValue() 1123 ); 1124 case "f", "floor" -> this.worldConfiguration.set( 1125 base + "plot.floor", 1126 ConfigurationUtil.BLOCK_BUCKET.parseString(value).toString() 1127 ); 1128 case "m", "main" -> this.worldConfiguration.set( 1129 base + "plot.filling", 1130 ConfigurationUtil.BLOCK_BUCKET.parseString(value).toString() 1131 ); 1132 case "w", "wall" -> this.worldConfiguration.set( 1133 base + "wall.filling", 1134 ConfigurationUtil.BLOCK_BUCKET.parseString(value).toString() 1135 ); 1136 case "b", "border" -> this.worldConfiguration.set( 1137 base + "wall.block", 1138 ConfigurationUtil.BLOCK_BUCKET.parseString(value).toString() 1139 ); 1140 default -> { 1141 LOGGER.error("Key not found: {}", element); 1142 return false; 1143 } 1144 } 1145 } catch (Exception e) { 1146 LOGGER.error("Invalid value '{}' for arg '{}'", value, element); 1147 e.printStackTrace(); 1148 return false; 1149 } 1150 } 1151 try { 1152 ConfigurationSection section = 1153 this.worldConfiguration.getConfigurationSection("worlds." + world); 1154 plotWorld.saveConfiguration(section); 1155 plotWorld.loadDefaultConfiguration(section); 1156 this.worldConfiguration.save(this.worldsFile); 1157 } catch (IOException e) { 1158 e.printStackTrace(); 1159 } 1160 } 1161 return true; 1162 } 1163 1164 /** 1165 * Copies a file from inside the jar to a location 1166 * 1167 * @param file Name of the file inside PlotSquared.jar 1168 * @param folder The output location relative to /plugins/PlotSquared/ 1169 */ 1170 public void copyFile( 1171 final @NonNull String file, 1172 final @NonNull String folder 1173 ) { 1174 try { 1175 File output = this.platform.getDirectory(); 1176 if (!output.exists()) { 1177 output.mkdirs(); 1178 } 1179 File newFile = FileUtils.getFile(output, folder + File.separator + file); 1180 if (newFile.exists()) { 1181 return; 1182 } 1183 try (InputStream stream = this.platform.getClass().getResourceAsStream(file)) { 1184 byte[] buffer = new byte[2048]; 1185 if (stream == null) { 1186 try (ZipInputStream zis = new ZipInputStream( 1187 new FileInputStream(this.jarFile))) { 1188 ZipEntry ze = zis.getNextEntry(); 1189 while (ze != null) { 1190 String name = ze.getName(); 1191 if (name.equals(file)) { 1192 new File(newFile.getParent()).mkdirs(); 1193 try (FileOutputStream fos = new FileOutputStream(newFile)) { 1194 int len; 1195 while ((len = zis.read(buffer)) > 0) { 1196 fos.write(buffer, 0, len); 1197 } 1198 } 1199 ze = null; 1200 } else { 1201 ze = zis.getNextEntry(); 1202 } 1203 } 1204 zis.closeEntry(); 1205 } 1206 return; 1207 } 1208 newFile.createNewFile(); 1209 try (FileOutputStream fos = new FileOutputStream(newFile)) { 1210 int len; 1211 while ((len = stream.read(buffer)) > 0) { 1212 fos.write(buffer, 0, len); 1213 } 1214 } 1215 } 1216 } catch (IOException e) { 1217 LOGGER.error("Could not save {}", file); 1218 e.printStackTrace(); 1219 } 1220 } 1221 1222 /** 1223 * Safely closes the database connection. 1224 */ 1225 public void disable() { 1226 try { 1227 eventDispatcher.unregisterAll(); 1228 checkRoadRegenPersistence(); 1229 // Validate that all data in the db is correct 1230 final HashSet<Plot> plots = new HashSet<>(); 1231 try { 1232 forEachPlotRaw(plots::add); 1233 } catch (final Exception ignored) { 1234 } 1235 DBFunc.validatePlots(plots); 1236 1237 // Close the connection 1238 DBFunc.close(); 1239 } catch (NullPointerException throwable) { 1240 LOGGER.error("Could not close database connection", throwable); 1241 throwable.printStackTrace(); 1242 } 1243 } 1244 1245 /** 1246 * Handle road regen persistence 1247 */ 1248 private void checkRoadRegenPersistence() { 1249 if (!HybridUtils.UPDATE || !Settings.Enabled_Components.PERSISTENT_ROAD_REGEN || ( 1250 HybridUtils.regions.isEmpty() && HybridUtils.chunks.isEmpty())) { 1251 return; 1252 } 1253 LOGGER.info("Road regeneration incomplete. Saving incomplete regions to disk"); 1254 LOGGER.info("- regions: {}", HybridUtils.regions.size()); 1255 LOGGER.info("- chunks: {}", HybridUtils.chunks.size()); 1256 ArrayList<int[]> regions = new ArrayList<>(); 1257 ArrayList<int[]> chunks = new ArrayList<>(); 1258 for (BlockVector2 r : HybridUtils.regions) { 1259 regions.add(new int[]{r.getBlockX(), r.getBlockZ()}); 1260 } 1261 for (BlockVector2 c : HybridUtils.chunks) { 1262 chunks.add(new int[]{c.getBlockX(), c.getBlockZ()}); 1263 } 1264 List<Object> list = new ArrayList<>(); 1265 list.add(regions); 1266 list.add(chunks); 1267 list.add(HybridUtils.height); 1268 File file = new File( 1269 this.platform.getDirectory() + File.separator + "persistent_regen_data_" + HybridUtils.area 1270 .getId() + "_" + HybridUtils.area.getWorldName()); 1271 if (file.exists() && !file.delete()) { 1272 LOGGER.error("persistent_regene_data file already exists and could not be deleted"); 1273 return; 1274 } 1275 try (ObjectOutputStream oos = new ObjectOutputStream( 1276 Files.newOutputStream(file.toPath(), StandardOpenOption.CREATE_NEW))) { 1277 oos.writeObject(list); 1278 } catch (IOException e) { 1279 LOGGER.error("Error creating persistent_region_data file", e); 1280 } 1281 } 1282 1283 /** 1284 * Setup the database connection. 1285 */ 1286 public void setupDatabase() { 1287 try { 1288 if (DBFunc.dbManager != null) { 1289 DBFunc.dbManager.close(); 1290 } 1291 Database database; 1292 if (Storage.MySQL.USE) { 1293 database = new MySQL(Storage.MySQL.HOST, Storage.MySQL.PORT, Storage.MySQL.DATABASE, 1294 Storage.MySQL.USER, Storage.MySQL.PASSWORD 1295 ); 1296 } else if (Storage.SQLite.USE) { 1297 File file = FileUtils.getFile(platform.getDirectory(), Storage.SQLite.DB + ".db"); 1298 database = new SQLite(file); 1299 } else { 1300 LOGGER.error("No storage type is set. Disabling PlotSquared"); 1301 this.platform.shutdown(); //shutdown used instead of disable because no database is set 1302 return; 1303 } 1304 DBFunc.dbManager = new SQLManager( 1305 database, 1306 Storage.PREFIX, 1307 this.eventDispatcher, 1308 this.plotListener, 1309 this.worldConfiguration 1310 ); 1311 this.plots_tmp = DBFunc.getPlots(); 1312 if (getPlotAreaManager() instanceof SinglePlotAreaManager) { 1313 SinglePlotArea area = ((SinglePlotAreaManager) getPlotAreaManager()).getArea(); 1314 addPlotArea(area); 1315 ConfigurationSection section = worldConfiguration.getConfigurationSection("worlds.*"); 1316 if (section == null) { 1317 section = worldConfiguration.createSection("worlds.*"); 1318 } 1319 area.saveConfiguration(section); 1320 area.loadDefaultConfiguration(section); 1321 } 1322 this.clustersTmp = DBFunc.getClusters(); 1323 LOGGER.info("Connection to database established. Type: {}", Storage.MySQL.USE ? "MySQL" : "SQLite"); 1324 } catch (ClassNotFoundException | SQLException e) { 1325 LOGGER.error( 1326 "Failed to open database connection ({}). Disabling PlotSquared", 1327 Storage.MySQL.USE ? "MySQL" : "SQLite" 1328 ); 1329 LOGGER.error("==== Here is an ugly stacktrace, if you are interested in those things ==="); 1330 e.printStackTrace(); 1331 LOGGER.error("==== End of stacktrace ===="); 1332 LOGGER.error( 1333 "Please go to the {} 'storage.yml' and configure the database correctly", 1334 platform.pluginName() 1335 ); 1336 this.platform.shutdown(); //shutdown used instead of disable because of database error 1337 } 1338 } 1339 1340 /** 1341 * Setup the default configuration. 1342 */ 1343 public void setupConfig() { 1344 String lastVersionString = this.getConfig().getString("version"); 1345 if (lastVersionString != null) { 1346 String[] split = lastVersionString.split("\\."); 1347 int[] lastVersion = new int[]{Integer.parseInt(split[0]), Integer.parseInt(split[1]), 1348 Integer.parseInt(split[2])}; 1349 if (checkVersion(new int[]{3, 4, 0}, lastVersion)) { 1350 Settings.convertLegacy(configFile); 1351 if (getConfig().contains("worlds")) { 1352 ConfigurationSection worldSection = 1353 getConfig().getConfigurationSection("worlds"); 1354 worldConfiguration.set("worlds", worldSection); 1355 try { 1356 worldConfiguration.save(worldsFile); 1357 } catch (IOException e) { 1358 LOGGER.error("Failed to save worlds.yml", e); 1359 e.printStackTrace(); 1360 } 1361 } 1362 Settings.save(configFile); 1363 } 1364 } 1365 Settings.load(configFile); 1366 //Sets the version information for the settings.yml file 1367 try (InputStream stream = getClass().getResourceAsStream("/plugin.properties")) { 1368 try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) { 1369 String versionString = br.readLine(); 1370 String commitString = br.readLine(); 1371 String dateString = br.readLine(); 1372 this.version = PlotVersion.tryParse(versionString, commitString, dateString); 1373 } 1374 } catch (IOException throwable) { 1375 throwable.printStackTrace(); 1376 } 1377 Settings.save(configFile); 1378 config = YamlConfiguration.loadConfiguration(configFile); 1379 } 1380 1381 /** 1382 * Setup all configuration files<br> 1383 * - Config: settings.yml<br> 1384 * - Storage: storage.yml<br> 1385 * 1386 * @return success or not 1387 */ 1388 public boolean setupConfigs() { 1389 File folder = new File(this.platform.getDirectory(), "config"); 1390 if (!folder.exists() && !folder.mkdirs()) { 1391 LOGGER.error("Failed to create the {} config folder. Please create it manually", this.platform.getDirectory()); 1392 } 1393 try { 1394 this.worldsFile = new File(folder, "worlds.yml"); 1395 if (!this.worldsFile.exists() && !this.worldsFile.createNewFile()) { 1396 LOGGER.error("Could not create the worlds file. Please create 'worlds.yml' manually"); 1397 } 1398 this.worldConfiguration = YamlConfiguration.loadConfiguration(this.worldsFile); 1399 1400 if (this.worldConfiguration.contains("worlds")) { 1401 if (!this.worldConfiguration.contains("configuration_version") || ( 1402 !this.worldConfiguration.getString("configuration_version") 1403 .equalsIgnoreCase(LegacyConverter.CONFIGURATION_VERSION) && !this.worldConfiguration 1404 .getString("configuration_version").equalsIgnoreCase("v5"))) { 1405 // Conversion needed 1406 LOGGER.info("A legacy configuration file was detected. Conversion will be attempted."); 1407 try { 1408 com.google.common.io.Files 1409 .copy(this.worldsFile, new File(folder, "worlds.yml.old")); 1410 LOGGER.info("A copy of worlds.yml has been saved in the file worlds.yml.old"); 1411 final ConfigurationSection worlds = 1412 this.worldConfiguration.getConfigurationSection("worlds"); 1413 final LegacyConverter converter = new LegacyConverter(worlds); 1414 converter.convert(); 1415 this.worldConfiguration.set("worlds", worlds); 1416 this.setConfigurationVersion(LegacyConverter.CONFIGURATION_VERSION); 1417 LOGGER.info( 1418 "The conversion has finished. PlotSquared will now be disabled and the new configuration file will be used at next startup. Please review the new worlds.yml file. Please note that schematics will not be converted, as we are now using WorldEdit to handle schematics. You need to re-generate the schematics."); 1419 } catch (final Exception e) { 1420 LOGGER.error("Failed to convert the legacy configuration file. See stack trace for information.", e); 1421 } 1422 // Disable plugin 1423 this.platform.shutdown(); 1424 return false; 1425 } 1426 } else { 1427 this.worldConfiguration.set("configuration_version", LegacyConverter.CONFIGURATION_VERSION); 1428 } 1429 } catch (IOException ignored) { 1430 LOGGER.error("Failed to save worlds.yml"); 1431 } 1432 try { 1433 this.configFile = new File(folder, "settings.yml"); 1434 if (!this.configFile.exists() && !this.configFile.createNewFile()) { 1435 LOGGER.error("Could not create the settings file. Please create 'settings.yml' manually"); 1436 } 1437 this.config = YamlConfiguration.loadConfiguration(this.configFile); 1438 setupConfig(); 1439 } catch (IOException ignored) { 1440 LOGGER.error("Failed to save settings.yml"); 1441 } 1442 try { 1443 this.storageFile = new File(folder, "storage.yml"); 1444 if (!this.storageFile.exists() && !this.storageFile.createNewFile()) { 1445 LOGGER.error("Could not create the storage settings file. Please create 'storage.yml' manually"); 1446 } 1447 YamlConfiguration.loadConfiguration(this.storageFile); 1448 setupStorage(); 1449 } catch (IOException ignored) { 1450 LOGGER.error("Failed to save storage.yml"); 1451 } 1452 return true; 1453 } 1454 1455 public @NonNull String getConfigurationVersion() { 1456 return this.worldConfiguration.get("configuration_version", LegacyConverter.CONFIGURATION_VERSION) 1457 .toString(); 1458 } 1459 1460 public void setConfigurationVersion(final @NonNull String newVersion) throws IOException { 1461 this.worldConfiguration.set("configuration_version", newVersion); 1462 this.worldConfiguration.save(this.worldsFile); 1463 } 1464 1465 /** 1466 * Setup the storage file (load + save missing nodes). 1467 */ 1468 private void setupStorage() { 1469 Storage.load(storageFile); 1470 Storage.save(storageFile); 1471 YamlConfiguration.loadConfiguration(storageFile); 1472 } 1473 1474 /** 1475 * Show startup debug information. 1476 */ 1477 private void showDebug() { 1478 if (Settings.DEBUG) { 1479 Map<String, Object> components = Settings.getFields(Settings.Enabled_Components.class); 1480 for (Entry<String, Object> component : components.entrySet()) { 1481 LOGGER.info("Key: {} | Value: {}", component.getKey(), component.getValue()); 1482 } 1483 } 1484 } 1485 1486 public void forEachPlotRaw(final @NonNull Consumer<Plot> consumer) { 1487 for (final PlotArea area : this.getPlotAreaManager().getAllPlotAreas()) { 1488 area.getPlots().forEach(consumer); 1489 } 1490 if (this.plots_tmp != null) { 1491 for (final HashMap<PlotId, Plot> entry : this.plots_tmp.values()) { 1492 entry.values().forEach(consumer); 1493 } 1494 } 1495 } 1496 1497 /** 1498 * Check if the chunk uses vanilla/non-PlotSquared generation 1499 * 1500 * @param world World name 1501 * @param chunkCoordinates Chunk coordinates 1502 * @return {@code true} if the chunk uses non-standard generation, {@code false} if not 1503 */ 1504 public boolean isNonStandardGeneration( 1505 final @NonNull String world, 1506 final @NonNull BlockVector2 chunkCoordinates 1507 ) { 1508 final Location location = Location.at(world, chunkCoordinates.getBlockX() << 4, 64, chunkCoordinates.getBlockZ() << 4); 1509 final PlotArea area = getPlotAreaManager().getApplicablePlotArea(location); 1510 if (area == null) { 1511 return true; 1512 } 1513 return area.getTerrain() != PlotAreaTerrainType.NONE; 1514 } 1515 1516 public @NonNull YamlConfiguration getConfig() { 1517 return config; 1518 } 1519 1520 public @NonNull UUIDPipeline getImpromptuUUIDPipeline() { 1521 return this.impromptuUUIDPipeline; 1522 } 1523 1524 public @NonNull UUIDPipeline getBackgroundUUIDPipeline() { 1525 return this.backgroundUUIDPipeline; 1526 } 1527 1528 public @NonNull WorldEdit getWorldEdit() { 1529 return this.worldedit; 1530 } 1531 1532 public @NonNull File getConfigFile() { 1533 return this.configFile; 1534 } 1535 1536 public @NonNull File getWorldsFile() { 1537 return this.worldsFile; 1538 } 1539 1540 public @NonNull YamlConfiguration getWorldConfiguration() { 1541 return this.worldConfiguration; 1542 } 1543 1544 /** 1545 * Get the caption map belonging to a namespace. If none exists, a dummy 1546 * caption map will be returned. 1547 * <p> 1548 * You can register a caption map by calling {@link #registerCaptionMap(String, CaptionMap)} 1549 * </p> 1550 * 1551 * @param namespace Namespace 1552 * @return Map instance 1553 */ 1554 public @NonNull CaptionMap getCaptionMap(final @NonNull String namespace) { 1555 return this.captionMaps.computeIfAbsent( 1556 namespace.toLowerCase(Locale.ENGLISH), 1557 missingNamespace -> new DummyCaptionMap() 1558 ); 1559 } 1560 1561 /** 1562 * Register a caption map. The namespace needs to be equal to the namespace used for 1563 * the {@link TranslatableCaption}s inside the map. 1564 * 1565 * @param namespace Namespace 1566 * @param captionMap Map instance 1567 */ 1568 public void registerCaptionMap( 1569 final @NonNull String namespace, 1570 final @NonNull CaptionMap captionMap 1571 ) { 1572 if (namespace.equalsIgnoreCase(TranslatableCaption.DEFAULT_NAMESPACE)) { 1573 throw new IllegalArgumentException("Cannot replace default caption map"); 1574 } 1575 this.captionMaps.put(namespace.toLowerCase(Locale.ENGLISH), captionMap); 1576 } 1577 1578 public @NonNull EventDispatcher getEventDispatcher() { 1579 return this.eventDispatcher; 1580 } 1581 1582 public @NonNull PlotListener getPlotListener() { 1583 return this.plotListener; 1584 } 1585 1586 /** 1587 * Get if the {@link PlatformReadyEvent} has been sent by WorldEdit. There is no way to query this within WorldEdit itself. 1588 */ 1589 public boolean isWeInitialised() { 1590 return weInitialised; 1591 } 1592 1593 /** 1594 * Different ways of sorting {@link Plot plots} 1595 */ 1596 public enum SortType { 1597 /** 1598 * Sort plots by their creation, using their index in the database 1599 */ 1600 CREATION_DATE, 1601 /** 1602 * Sort plots by their creation timestamp 1603 */ 1604 CREATION_DATE_TIMESTAMP, 1605 /** 1606 * Sort plots by when they were last modified 1607 */ 1608 LAST_MODIFIED, 1609 /** 1610 * Sort plots based on their distance from the origin of the world 1611 */ 1612 DISTANCE_FROM_ORIGIN 1613 } 1614 1615 private final class WEPlatformReadyListener { 1616 1617 @SuppressWarnings("unused") 1618 @Subscribe(priority = EventHandler.Priority.VERY_EARLY) 1619 public void onPlatformReady(PlatformReadyEvent event) { 1620 weInitialised = true; 1621 WorldEdit.getInstance().getEventBus().unregister(WEPlatformReadyListener.this); 1622 } 1623 1624 } 1625 1626}