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