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