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.plot; 020 021import com.google.common.collect.ImmutableSet; 022import com.google.common.collect.Lists; 023import com.google.common.collect.Sets; 024import com.google.inject.Inject; 025import com.plotsquared.core.PlotSquared; 026import com.plotsquared.core.command.Like; 027import com.plotsquared.core.configuration.Settings; 028import com.plotsquared.core.configuration.caption.Caption; 029import com.plotsquared.core.configuration.caption.CaptionUtility; 030import com.plotsquared.core.configuration.caption.StaticCaption; 031import com.plotsquared.core.configuration.caption.TranslatableCaption; 032import com.plotsquared.core.database.DBFunc; 033import com.plotsquared.core.events.Result; 034import com.plotsquared.core.events.TeleportCause; 035import com.plotsquared.core.generator.ClassicPlotWorld; 036import com.plotsquared.core.listener.PlotListener; 037import com.plotsquared.core.location.BlockLoc; 038import com.plotsquared.core.location.Direction; 039import com.plotsquared.core.location.Location; 040import com.plotsquared.core.permissions.Permission; 041import com.plotsquared.core.player.ConsolePlayer; 042import com.plotsquared.core.player.PlotPlayer; 043import com.plotsquared.core.plot.expiration.PlotAnalysis; 044import com.plotsquared.core.plot.flag.FlagContainer; 045import com.plotsquared.core.plot.flag.GlobalFlagContainer; 046import com.plotsquared.core.plot.flag.InternalFlag; 047import com.plotsquared.core.plot.flag.PlotFlag; 048import com.plotsquared.core.plot.flag.implementations.DescriptionFlag; 049import com.plotsquared.core.plot.flag.implementations.KeepFlag; 050import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; 051import com.plotsquared.core.plot.flag.types.DoubleFlag; 052import com.plotsquared.core.plot.schematic.Schematic; 053import com.plotsquared.core.plot.world.SinglePlotArea; 054import com.plotsquared.core.queue.QueueCoordinator; 055import com.plotsquared.core.util.EventDispatcher; 056import com.plotsquared.core.util.MathMan; 057import com.plotsquared.core.util.PlayerManager; 058import com.plotsquared.core.util.RegionManager; 059import com.plotsquared.core.util.RegionUtil; 060import com.plotsquared.core.util.SchematicHandler; 061import com.plotsquared.core.util.TimeUtil; 062import com.plotsquared.core.util.WorldUtil; 063import com.plotsquared.core.util.query.PlotQuery; 064import com.plotsquared.core.util.task.RunnableVal; 065import com.plotsquared.core.util.task.TaskManager; 066import com.plotsquared.core.util.task.TaskTime; 067import com.sk89q.worldedit.math.BlockVector3; 068import com.sk89q.worldedit.regions.CuboidRegion; 069import com.sk89q.worldedit.world.biome.BiomeType; 070import net.kyori.adventure.text.Component; 071import net.kyori.adventure.text.TextComponent; 072import net.kyori.adventure.text.minimessage.MiniMessage; 073import net.kyori.adventure.text.minimessage.Template; 074import org.apache.logging.log4j.LogManager; 075import org.apache.logging.log4j.Logger; 076import org.checkerframework.checker.nullness.qual.NonNull; 077import org.checkerframework.checker.nullness.qual.Nullable; 078 079import java.lang.ref.Cleaner; 080import java.text.DecimalFormat; 081import java.text.SimpleDateFormat; 082import java.util.ArrayDeque; 083import java.util.ArrayList; 084import java.util.Collection; 085import java.util.Collections; 086import java.util.HashMap; 087import java.util.HashSet; 088import java.util.List; 089import java.util.Map; 090import java.util.Map.Entry; 091import java.util.Objects; 092import java.util.Set; 093import java.util.TimeZone; 094import java.util.UUID; 095import java.util.concurrent.CompletableFuture; 096import java.util.concurrent.ConcurrentHashMap; 097import java.util.function.Consumer; 098 099import static com.plotsquared.core.util.entity.EntityCategories.CAP_ANIMAL; 100import static com.plotsquared.core.util.entity.EntityCategories.CAP_ENTITY; 101import static com.plotsquared.core.util.entity.EntityCategories.CAP_MISC; 102import static com.plotsquared.core.util.entity.EntityCategories.CAP_MOB; 103import static com.plotsquared.core.util.entity.EntityCategories.CAP_MONSTER; 104import static com.plotsquared.core.util.entity.EntityCategories.CAP_VEHICLE; 105 106/** 107 * The plot class<br> 108 * [IMPORTANT] 109 * - Unclaimed plots will not have persistent information. 110 * - Any information set/modified in an unclaimed object may not be reflected in other instances 111 * - Using the `new` operator will create an unclaimed plot instance 112 * - Use the methods from the PlotArea/PS/Location etc to get existing plots 113 */ 114public class Plot { 115 116 @Deprecated(forRemoval = true, since = "6.6.0") 117 public static final int MAX_HEIGHT = 256; 118 119 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Plot.class.getSimpleName()); 120 private static final DecimalFormat FLAG_DECIMAL_FORMAT = new DecimalFormat("0"); 121 private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build(); 122 private static final Cleaner CLEANER = Cleaner.create(); 123 124 static { 125 FLAG_DECIMAL_FORMAT.setMaximumFractionDigits(340); 126 } 127 128 /** 129 * Plot flag container 130 */ 131 private final FlagContainer flagContainer = new FlagContainer(null); 132 /** 133 * Utility used to manage plot comments 134 */ 135 private final PlotCommentContainer plotCommentContainer = new PlotCommentContainer(this); 136 /** 137 * Utility used to modify the plot 138 */ 139 private final PlotModificationManager plotModificationManager = new PlotModificationManager(this); 140 /** 141 * Represents whatever the database manager needs it to: <br> 142 * - A value of -1 usually indicates the plot will not be stored in the DB<br> 143 * - A value of 0 usually indicates that the DB manager hasn't set a value<br> 144 * 145 * @deprecated magical 146 */ 147 @Deprecated 148 public int temp; 149 /** 150 * List of trusted (with plot permissions). 151 */ 152 HashSet<UUID> trusted; 153 /** 154 * List of members users (with plot permissions). 155 */ 156 HashSet<UUID> members; 157 /** 158 * List of denied players. 159 */ 160 HashSet<UUID> denied; 161 /** 162 * External settings class. 163 * - Please favor the methods over direct access to this class<br> 164 * - The methods are more likely to be left unchanged from version changes<br> 165 */ 166 PlotSettings settings; 167 @NonNull 168 private PlotId id; 169 // These will be injected 170 @Inject 171 private EventDispatcher eventDispatcher; 172 @Inject 173 private PlotListener plotListener; 174 @Inject 175 private RegionManager regionManager; 176 @Inject 177 private WorldUtil worldUtil; 178 @Inject 179 private SchematicHandler schematicHandler; 180 /** 181 * plot owner 182 * (Merged plots can have multiple owners) 183 * Direct access is Deprecated: use getOwners() 184 * 185 * @deprecated 186 */ 187 private UUID owner; 188 /** 189 * Plot creation timestamp (not accurate if the plot was created before this was implemented)<br> 190 * - Milliseconds since the epoch<br> 191 */ 192 private long timestamp; 193 private PlotArea area; 194 /** 195 * Session only plot metadata (session is until the server stops)<br> 196 * <br> 197 * For persistent metadata use the flag system 198 */ 199 private ConcurrentHashMap<String, Object> meta; 200 /** 201 * The cached origin plot. 202 * - The origin plot is used for plot grouping and relational data 203 */ 204 private Plot origin; 205 206 private Set<Plot> connectedCache; 207 208 /** 209 * Constructor for a new plot. 210 * (Only changes after plot.create() will be properly set in the database) 211 * 212 * <p> 213 * See {@link Plot#getPlot(Location)} for existing plots 214 * </p> 215 * 216 * @param area the PlotArea where the plot is located 217 * @param id the plot id 218 * @param owner the plot owner 219 */ 220 public Plot(final PlotArea area, final @NonNull PlotId id, final UUID owner) { 221 this(area, id, owner, 0); 222 } 223 224 /** 225 * Constructor for an unowned plot. 226 * (Only changes after plot.create() will be properly set in the database) 227 * 228 * <p> 229 * See {@link Plot#getPlot(Location)} for existing plots 230 * </p> 231 * 232 * @param area the PlotArea where the plot is located 233 * @param id the plot id 234 */ 235 public Plot(final @NonNull PlotArea area, final @NonNull PlotId id) { 236 this(area, id, null, 0); 237 } 238 239 /** 240 * Constructor for a temporary plot (use -1 for temp)<br> 241 * The database will ignore any queries regarding temporary plots. 242 * Please note that some bulk plot management functions may still affect temporary plots (TODO: fix this) 243 * 244 * <p> 245 * See {@link Plot#getPlot(Location)} for existing plots 246 * </p> 247 * 248 * @param area the PlotArea where the plot is located 249 * @param id the plot id 250 * @param owner the owner of the plot 251 * @param temp Represents whatever the database manager needs it to 252 */ 253 public Plot(final PlotArea area, final @NonNull PlotId id, final UUID owner, final int temp) { 254 this.area = area; 255 this.id = id; 256 this.owner = owner; 257 this.temp = temp; 258 this.flagContainer.setParentContainer(area.getFlagContainer()); 259 PlotSquared.platform().injector().injectMembers(this); 260 // This is needed, because otherwise the Plot, the FlagContainer and its 261 // `this::handleUnknown` PlotFlagUpdateHandler won't get cleaned up ever 262 CLEANER.register(this, this.flagContainer.createCleanupHook()); 263 } 264 265 /** 266 * Constructor for a saved plots (Used by the database manager when plots are fetched) 267 * 268 * <p> 269 * See {@link Plot#getPlot(Location)} for existing plots 270 * </p> 271 * 272 * @param id the plot id 273 * @param owner the plot owner 274 * @param trusted the plot trusted players 275 * @param members the plot added players 276 * @param denied the plot denied players 277 * @param alias the plot's alias 278 * @param position plot home position 279 * @param flags the plot's flags 280 * @param area the plot's PlotArea 281 * @param merged an array giving merged plots 282 * @param timestamp when the plot was created 283 * @param temp value representing whatever DBManager needs to to. Do not touch tbh. 284 */ 285 public Plot( 286 @NonNull PlotId id, 287 UUID owner, 288 HashSet<UUID> trusted, 289 HashSet<UUID> members, 290 HashSet<UUID> denied, 291 String alias, 292 BlockLoc position, 293 Collection<PlotFlag<?, ?>> flags, 294 PlotArea area, 295 boolean[] merged, 296 long timestamp, 297 int temp 298 ) { 299 this.id = id; 300 this.area = area; 301 this.owner = owner; 302 this.settings = new PlotSettings(); 303 this.members = members; 304 this.trusted = trusted; 305 this.denied = denied; 306 this.settings.setAlias(alias); 307 this.settings.setPosition(position); 308 this.settings.setMerged(merged); 309 this.timestamp = timestamp; 310 this.temp = temp; 311 if (area != null) { 312 this.flagContainer.setParentContainer(area.getFlagContainer()); 313 if (flags != null) { 314 for (PlotFlag<?, ?> flag : flags) { 315 this.flagContainer.addFlag(flag); 316 } 317 } 318 } 319 PlotSquared.platform().injector().injectMembers(this); 320 } 321 322 /** 323 * Get the plot from a string. 324 * 325 * @param player Provides a context for what world to search in. Prefixing the term with 'world_name;' will override this context. 326 * @param arg The search term 327 * @param message If a message should be sent to the player if a plot cannot be found 328 * @return The plot if only 1 result is found, or null 329 */ 330 public static @Nullable Plot getPlotFromString( 331 final @Nullable PlotPlayer<?> player, 332 final @Nullable String arg, 333 final boolean message 334 ) { 335 if (arg == null) { 336 if (player == null) { 337 if (message) { 338 LOGGER.info("No plot area string was supplied"); 339 } 340 return null; 341 } 342 return player.getCurrentPlot(); 343 } 344 PlotArea area; 345 if (player != null) { 346 area = PlotSquared.get().getPlotAreaManager().getPlotAreaByString(arg); 347 if (area == null) { 348 area = player.getApplicablePlotArea(); 349 } 350 } else { 351 area = ConsolePlayer.getConsole().getApplicablePlotArea(); 352 } 353 String[] split = arg.split("[;,]"); 354 PlotId id; 355 if (split.length == 4) { 356 area = PlotSquared.get().getPlotAreaManager().getPlotAreaByString(split[0] + ';' + split[1]); 357 id = PlotId.fromString(split[2] + ';' + split[3]); 358 } else if (split.length == 3) { 359 area = PlotSquared.get().getPlotAreaManager().getPlotAreaByString(split[0]); 360 id = PlotId.fromString(split[1] + ';' + split[2]); 361 } else if (split.length == 2) { 362 id = PlotId.fromString(arg); 363 } else { 364 Collection<Plot> plots; 365 if (area == null) { 366 plots = PlotQuery.newQuery().allPlots().asList(); 367 } else { 368 plots = area.getPlots(); 369 } 370 for (Plot p : plots) { 371 String name = p.getAlias(); 372 if (!name.isEmpty() && name.equalsIgnoreCase(arg)) { 373 return p.getBasePlot(false); 374 } 375 } 376 if (message && player != null) { 377 player.sendMessage(TranslatableCaption.of("invalid.not_valid_plot_id")); 378 } 379 return null; 380 } 381 if (area == null) { 382 if (message && player != null) { 383 player.sendMessage(TranslatableCaption.of("errors.invalid_plot_world")); 384 } 385 return null; 386 } 387 return area.getPlotAbs(id); 388 } 389 390 /** 391 * Gets a plot from a string e.g. [area];[id] 392 * 393 * @param defaultArea if no area is specified 394 * @param string plot id/area + id 395 * @return New or existing plot object 396 */ 397 public static @Nullable Plot fromString(final @Nullable PlotArea defaultArea, final @NonNull String string) { 398 final String[] split = string.split("[;,]"); 399 if (split.length == 2) { 400 if (defaultArea != null) { 401 PlotId id = PlotId.fromString(split[0] + ';' + split[1]); 402 return defaultArea.getPlotAbs(id); 403 } 404 } else if (split.length == 3) { 405 PlotArea pa = PlotSquared.get().getPlotAreaManager().getPlotArea(split[0], null); 406 if (pa != null) { 407 PlotId id = PlotId.fromString(split[1] + ';' + split[2]); 408 return pa.getPlotAbs(id); 409 } 410 } else if (split.length == 4) { 411 PlotArea pa = PlotSquared.get().getPlotAreaManager().getPlotArea(split[0], split[1]); 412 if (pa != null) { 413 PlotId id = PlotId.fromString(split[1] + ';' + split[2]); 414 return pa.getPlotAbs(id); 415 } 416 } 417 return null; 418 } 419 420 /** 421 * Return a new/cached plot object at a given location. 422 * 423 * <p> 424 * Use {@link PlotPlayer#getCurrentPlot()} if a player is expected here. 425 * </p> 426 * 427 * @param location the location of the plot 428 * @return plot at location or null 429 */ 430 public static @Nullable Plot getPlot(final @NonNull Location location) { 431 final PlotArea pa = location.getPlotArea(); 432 if (pa != null) { 433 return pa.getPlot(location); 434 } 435 return null; 436 } 437 438 @NonNull 439 static Location[] getCorners(final @NonNull String world, final @NonNull CuboidRegion region) { 440 final BlockVector3 min = region.getMinimumPoint(); 441 final BlockVector3 max = region.getMaximumPoint(); 442 return new Location[]{Location.at(world, min), Location.at(world, max)}; 443 } 444 445 /** 446 * Get the owner of this exact plot, as it is 447 * stored in the database. 448 * <p> 449 * If the plot is a mega-plot, then the method returns 450 * the owner of this particular subplot. 451 * <p> 452 * Unlike {@link #getOwner()} this method does not 453 * consider factors such as {@link com.plotsquared.core.plot.flag.implementations.ServerPlotFlag} 454 * that could alter the de facto owner of the plot. 455 * 456 * @return The plot owner of this particular (sub-)plot 457 * as stored in the database, if one exists. Else, null. 458 */ 459 public @Nullable UUID getOwnerAbs() { 460 return this.owner; 461 } 462 463 /** 464 * Set the owner of this exact sub-plot. This does 465 * not update the database. 466 * 467 * @param owner The new owner of this particular sub-plot. 468 */ 469 public void setOwnerAbs(final @Nullable UUID owner) { 470 this.owner = owner; 471 } 472 473 /** 474 * Get the name of the world that the plot is in 475 * 476 * @return World name 477 */ 478 public @Nullable String getWorldName() { 479 return area.getWorldName(); 480 } 481 482 /** 483 * Session only plot metadata (session is until the server stops)<br> 484 * <br> 485 * For persistent metadata use the flag system 486 * 487 * @param key metadata key 488 * @param value metadata value 489 */ 490 public void setMeta(final @NonNull String key, final @NonNull Object value) { 491 if (this.meta == null) { 492 this.meta = new ConcurrentHashMap<>(); 493 } 494 this.meta.put(key, value); 495 } 496 497 /** 498 * Gets the metadata for a key<br> 499 * <br> 500 * For persistent metadata use the flag system 501 * 502 * @param key metadata key to get value for 503 * @return Object value 504 */ 505 public @Nullable Object getMeta(final @NonNull String key) { 506 if (this.meta != null) { 507 return this.meta.get(key); 508 } 509 return null; 510 } 511 512 /** 513 * Delete the metadata for a key<br> 514 * - metadata is session only 515 * - deleting other plugin's metadata may cause issues 516 * 517 * @param key key to delete 518 */ 519 public void deleteMeta(final @NonNull String key) { 520 if (this.meta != null) { 521 this.meta.remove(key); 522 } 523 } 524 525 /** 526 * Gets the cluster this plot is associated with 527 * 528 * @return the PlotCluster object, or null 529 */ 530 public @Nullable PlotCluster getCluster() { 531 if (this.getArea() == null) { 532 return null; 533 } 534 return this.getArea().getCluster(this.id); 535 } 536 537 /** 538 * Efficiently get the players currently inside this plot<br> 539 * - Will return an empty list if no players are in the plot<br> 540 * - Remember, you can cast a PlotPlayer to its respective implementation (BukkitPlayer, SpongePlayer) to obtain the player object 541 * 542 * @return list of PlotPlayer(s) or an empty list 543 */ 544 public @NonNull List<PlotPlayer<?>> getPlayersInPlot() { 545 final List<PlotPlayer<?>> players = new ArrayList<>(); 546 for (final PlotPlayer<?> player : PlotSquared.platform().playerManager().getPlayers()) { 547 if (this.equals(player.getCurrentPlot())) { 548 players.add(player); 549 } 550 } 551 return players; 552 } 553 554 /** 555 * Checks if the plot has an owner. 556 * 557 * @return {@code true} if there is an owner, else {@code false} 558 */ 559 public boolean hasOwner() { 560 return this.getOwnerAbs() != null; 561 } 562 563 /** 564 * Checks if a UUID is a plot owner (merged plots may have multiple owners) 565 * 566 * @param uuid Player UUID 567 * @return {@code true} if the provided uuid is the owner of the plot, else {@code false} 568 */ 569 public boolean isOwner(final @NonNull UUID uuid) { 570 if (uuid.equals(this.getOwner())) { 571 return true; 572 } 573 if (!isMerged()) { 574 return false; 575 } 576 final Set<Plot> connected = getConnectedPlots(); 577 for (Plot current : connected) { 578 // can skip ServerPlotFlag check in getOwner() 579 // as flags are synchronized between plots 580 if (uuid.equals(current.getOwnerAbs())) { 581 return true; 582 } 583 } 584 return false; 585 } 586 587 /** 588 * Checks if the given UUID is the owner of this specific plot 589 * 590 * @param uuid Player UUID 591 * @return {@code true} if the provided uuid is the owner of the plot, else {@code false} 592 */ 593 public boolean isOwnerAbs(final @Nullable UUID uuid) { 594 if (uuid == null) { 595 return false; 596 } 597 return uuid.equals(this.getOwner()); 598 } 599 600 /** 601 * Get the plot owner of this particular sub-plot. 602 * (Merged plots can have multiple owners) 603 * Direct access is discouraged: use {@link #getOwners()} 604 * 605 * <p> 606 * Use {@link #getOwnerAbs()} to get the owner as stored in the database 607 * </p> 608 * 609 * @return Server if ServerPlot flag set, else {@link #getOwnerAbs()} 610 */ 611 public @Nullable UUID getOwner() { 612 if (this.getFlag(ServerPlotFlag.class)) { 613 return DBFunc.SERVER; 614 } 615 return this.getOwnerAbs(); 616 } 617 618 /** 619 * Sets the plot owner (and update the database) 620 * 621 * @param owner uuid to set as owner 622 */ 623 public void setOwner(final @NonNull UUID owner) { 624 if (!hasOwner()) { 625 this.setOwnerAbs(owner); 626 this.getPlotModificationManager().create(); 627 return; 628 } 629 if (!isMerged()) { 630 if (!owner.equals(this.getOwnerAbs())) { 631 this.setOwnerAbs(owner); 632 DBFunc.setOwner(this, owner); 633 } 634 return; 635 } 636 for (final Plot current : getConnectedPlots()) { 637 if (!owner.equals(current.getOwnerAbs())) { 638 current.setOwnerAbs(owner); 639 DBFunc.setOwner(current, owner); 640 } 641 } 642 } 643 644 /** 645 * Gets a immutable set of owner UUIDs for a plot (supports multi-owner mega-plots). 646 * <p> 647 * This method cannot be used to add or remove owners from a plot. 648 * </p> 649 * 650 * @return Immutable view of plot owners 651 */ 652 public @NonNull Set<UUID> getOwners() { 653 if (this.getOwner() == null) { 654 return ImmutableSet.of(); 655 } 656 if (isMerged()) { 657 Set<Plot> plots = getConnectedPlots(); 658 Plot[] array = plots.toArray(new Plot[0]); 659 ImmutableSet.Builder<UUID> owners = ImmutableSet.builder(); 660 UUID last = this.getOwner(); 661 owners.add(this.getOwner()); 662 for (final Plot current : array) { 663 if (current.getOwner() == null) { 664 continue; 665 } 666 if (last == null || current.getOwner().getMostSignificantBits() != last.getMostSignificantBits()) { 667 owners.add(current.getOwner()); 668 last = current.getOwner(); 669 } 670 } 671 return owners.build(); 672 } 673 return ImmutableSet.of(this.getOwner()); 674 } 675 676 /** 677 * Checks if the player is either the owner or on the trusted/added list. 678 * 679 * @param uuid uuid to check 680 * @return {@code true} if the player is added/trusted or is the owner, else {@code false} 681 */ 682 public boolean isAdded(final @NonNull UUID uuid) { 683 if (!this.hasOwner() || getDenied().contains(uuid)) { 684 return false; 685 } 686 if (isOwner(uuid)) { 687 return true; 688 } 689 if (getMembers().contains(uuid)) { 690 return isOnline(); 691 } 692 if (getTrusted().contains(uuid) || getTrusted().contains(DBFunc.EVERYONE)) { 693 return true; 694 } 695 if (getMembers().contains(DBFunc.EVERYONE)) { 696 return isOnline(); 697 } 698 return false; 699 } 700 701 /** 702 * Checks if the player is not permitted on this plot. 703 * 704 * @param uuid uuid to check 705 * @return {@code false} if the player is allowed to enter the plot, else {@code true} 706 */ 707 public boolean isDenied(final @NonNull UUID uuid) { 708 return this.denied != null && (this.denied.contains(DBFunc.EVERYONE) && !this.isAdded(uuid) || !this.isAdded(uuid) && this.denied 709 .contains(uuid)); 710 } 711 712 /** 713 * Gets the {@link PlotId} of this plot. 714 * 715 * @return the PlotId for this plot 716 */ 717 public @NonNull PlotId getId() { 718 return this.id; 719 } 720 721 /** 722 * Change the plot ID 723 * 724 * @param id new plot ID 725 */ 726 public void setId(final @NonNull PlotId id) { 727 this.id = id; 728 } 729 730 /** 731 * Gets the plot world object for this plot<br> 732 * - The generic PlotArea object can be casted to its respective class for more control (e.g. HybridPlotWorld) 733 * 734 * @return PlotArea 735 */ 736 public @Nullable PlotArea getArea() { 737 return this.area; 738 } 739 740 /** 741 * Assigns this plot to a plot area.<br> 742 * (Mostly used during startup when worlds are being created)<br> 743 * <p> 744 * Do not use this unless you absolutely know what you are doing. 745 * </p> 746 * 747 * @param area area to assign to 748 */ 749 public void setArea(final @NonNull PlotArea area) { 750 if (this.getArea() == area) { 751 return; 752 } 753 if (this.getArea() != null) { 754 this.area.removePlot(this.id); 755 } 756 this.area = area; 757 area.addPlot(this); 758 this.flagContainer.setParentContainer(area.getFlagContainer()); 759 } 760 761 /** 762 * Gets the plot manager object for this plot<br> 763 * - The generic PlotManager object can be casted to its respective class for more control (e.g. HybridPlotManager) 764 * 765 * @return PlotManager 766 */ 767 public @NonNull PlotManager getManager() { 768 return this.area.getPlotManager(); 769 } 770 771 /** 772 * Gets or create plot settings. 773 * 774 * @return PlotSettings 775 */ 776 public @NonNull PlotSettings getSettings() { 777 if (this.settings == null) { 778 this.settings = new PlotSettings(); 779 } 780 return this.settings; 781 } 782 783 /** 784 * Returns true if the plot is not merged, or it is the base 785 * plot of multiple merged plots. 786 * 787 * @return Boolean 788 */ 789 public boolean isBasePlot() { 790 return !this.isMerged() || this.equals(this.getBasePlot(false)); 791 } 792 793 /** 794 * The base plot is an arbitrary but specific connected plot. It is useful for the following:<br> 795 * - Merged plots need to be treated as a single plot for most purposes<br> 796 * - Some data such as home location needs to be associated with the group rather than each plot<br> 797 * - If the plot is not merged it will return itself.<br> 798 * - The result is cached locally 799 * 800 * @param recalculate whether to recalculate the merged plots to find the origin 801 * @return base Plot 802 */ 803 public Plot getBasePlot(final boolean recalculate) { 804 if (this.origin != null && !recalculate) { 805 if (this.equals(this.origin)) { 806 return this; 807 } 808 return this.origin.getBasePlot(false); 809 } 810 if (!this.isMerged()) { 811 this.origin = this; 812 return this.origin; 813 } 814 this.origin = this; 815 PlotId min = this.id; 816 for (Plot plot : this.getConnectedPlots()) { 817 if (plot.id.getY() < min.getY() || plot.id.getY() == min.getY() && plot.id.getX() < min.getX()) { 818 this.origin = plot; 819 min = plot.id; 820 } 821 } 822 for (Plot plot : this.getConnectedPlots()) { 823 plot.origin = this.origin; 824 } 825 return this.origin; 826 } 827 828 /** 829 * Checks if this plot is merged in any direction. 830 * 831 * @return {@code true} if this plot is merged, otherwise {@code false} 832 */ 833 public boolean isMerged() { 834 return getSettings().getMerged(0) || getSettings().getMerged(2) || getSettings().getMerged(1) || getSettings().getMerged(3); 835 } 836 837 /** 838 * Gets the timestamp of when the plot was created (unreliable)<br> 839 * - not accurate if the plot was created before this was implemented<br> 840 * - Milliseconds since the epoch<br> 841 * 842 * @return the creation date of the plot 843 */ 844 public long getTimestamp() { 845 if (this.timestamp == 0) { 846 this.timestamp = System.currentTimeMillis(); 847 } 848 return this.timestamp; 849 } 850 851 /** 852 * Gets if the plot is merged in a direction<br> 853 * ------- Actual -------<br> 854 * 0 = north<br> 855 * 1 = east<br> 856 * 2 = south<br> 857 * 3 = west<br> 858 * ----- Artificial -----<br> 859 * 4 = north-east<br> 860 * 5 = south-east<br> 861 * 6 = south-west<br> 862 * 7 = north-west<br> 863 * ----------<br> 864 * <p> 865 * Note: A plot that is merged north and east will not be merged northeast if the northeast plot is not part of the same group<br> 866 * 867 * @param dir direction to check for merged plot 868 * @return {@code true} if merged in that direction, else {@code false} 869 */ 870 public boolean isMerged(final int dir) { 871 if (this.settings == null) { 872 return false; 873 } 874 switch (dir) { 875 case 0: 876 case 1: 877 case 2: 878 case 3: 879 return this.getSettings().getMerged(dir); 880 case 7: 881 int i = dir - 4; 882 int i2 = 0; 883 if (this.getSettings().getMerged(i2)) { 884 if (this.getSettings().getMerged(i)) { 885 if (Objects.requireNonNull( 886 this.area.getPlotAbs(this.id.getRelative(Direction.getFromIndex(i)))).isMerged(i2)) { 887 return Objects.requireNonNull(this.area 888 .getPlotAbs(this.id.getRelative(Direction.getFromIndex(i2)))).isMerged(i); 889 } 890 } 891 } 892 return false; 893 case 4: 894 case 5: 895 case 6: 896 i = dir - 4; 897 i2 = dir - 3; 898 return this.getSettings().getMerged(i2) && this.getSettings().getMerged(i) && Objects 899 .requireNonNull( 900 this.area.getPlotAbs(this.id.getRelative(Direction.getFromIndex(i)))).isMerged(i2) && Objects 901 .requireNonNull( 902 this.area.getPlotAbs(this.id.getRelative(Direction.getFromIndex(i2)))).isMerged(i); 903 904 } 905 return false; 906 } 907 908 /** 909 * Gets the denied users. 910 * 911 * @return a set of denied users 912 */ 913 public @NonNull HashSet<UUID> getDenied() { 914 if (this.denied == null) { 915 this.denied = new HashSet<>(); 916 } 917 return this.denied; 918 } 919 920 /** 921 * Sets the denied users for this plot. 922 * 923 * @param uuids uuids to deny 924 */ 925 public void setDenied(final @NonNull Set<UUID> uuids) { 926 boolean larger = uuids.size() > getDenied().size(); 927 HashSet<UUID> intersection; 928 if (larger) { 929 intersection = new HashSet<>(getDenied()); 930 } else { 931 intersection = new HashSet<>(uuids); 932 } 933 if (larger) { 934 intersection.retainAll(uuids); 935 } else { 936 intersection.retainAll(getDenied()); 937 } 938 uuids.removeAll(intersection); 939 HashSet<UUID> toRemove = new HashSet<>(getDenied()); 940 toRemove.removeAll(intersection); 941 for (UUID uuid : toRemove) { 942 removeDenied(uuid); 943 } 944 for (UUID uuid : uuids) { 945 addDenied(uuid); 946 } 947 } 948 949 /** 950 * Gets the trusted users. 951 * 952 * @return a set of trusted users 953 */ 954 public @NonNull HashSet<UUID> getTrusted() { 955 if (this.trusted == null) { 956 this.trusted = new HashSet<>(); 957 } 958 return this.trusted; 959 } 960 961 /** 962 * Sets the trusted users for this plot. 963 * 964 * @param uuids uuids to trust 965 */ 966 public void setTrusted(final @NonNull Set<UUID> uuids) { 967 boolean larger = uuids.size() > getTrusted().size(); 968 HashSet<UUID> intersection = new HashSet<>(larger ? getTrusted() : uuids); 969 intersection.retainAll(larger ? uuids : getTrusted()); 970 uuids.removeAll(intersection); 971 HashSet<UUID> toRemove = new HashSet<>(getTrusted()); 972 toRemove.removeAll(intersection); 973 for (UUID uuid : toRemove) { 974 removeTrusted(uuid); 975 } 976 for (UUID uuid : uuids) { 977 addTrusted(uuid); 978 } 979 } 980 981 /** 982 * Gets the members 983 * 984 * @return a set of members 985 */ 986 public @NonNull HashSet<UUID> getMembers() { 987 if (this.members == null) { 988 this.members = new HashSet<>(); 989 } 990 return this.members; 991 } 992 993 /** 994 * Sets the members for this plot. 995 * 996 * @param uuids uuids to set member status for 997 */ 998 public void setMembers(final @NonNull Set<UUID> uuids) { 999 boolean larger = uuids.size() > getMembers().size(); 1000 HashSet<UUID> intersection = new HashSet<>(larger ? getMembers() : uuids); 1001 intersection.retainAll(larger ? uuids : getMembers()); 1002 uuids.removeAll(intersection); 1003 HashSet<UUID> toRemove = new HashSet<>(getMembers()); 1004 toRemove.removeAll(intersection); 1005 for (UUID uuid : toRemove) { 1006 removeMember(uuid); 1007 } 1008 for (UUID uuid : uuids) { 1009 addMember(uuid); 1010 } 1011 } 1012 1013 /** 1014 * Denies a player from this plot. (updates database as well) 1015 * 1016 * @param uuid the uuid of the player to deny. 1017 */ 1018 public void addDenied(final @NonNull UUID uuid) { 1019 for (final Plot current : getConnectedPlots()) { 1020 if (current.getDenied().add(uuid)) { 1021 DBFunc.setDenied(current, uuid); 1022 } 1023 } 1024 } 1025 1026 /** 1027 * Add someone as a helper (updates database as well) 1028 * 1029 * @param uuid the uuid of the player to trust 1030 */ 1031 public void addTrusted(final @NonNull UUID uuid) { 1032 for (final Plot current : getConnectedPlots()) { 1033 if (current.getTrusted().add(uuid)) { 1034 DBFunc.setTrusted(current, uuid); 1035 } 1036 } 1037 } 1038 1039 /** 1040 * Add someone as a trusted user (updates database as well) 1041 * 1042 * @param uuid the uuid of the player to add as a member 1043 */ 1044 public void addMember(final @NonNull UUID uuid) { 1045 for (final Plot current : getConnectedPlots()) { 1046 if (current.getMembers().add(uuid)) { 1047 DBFunc.setMember(current, uuid); 1048 } 1049 } 1050 } 1051 1052 /** 1053 * Sets the plot owner (and update the database) 1054 * 1055 * @param owner uuid to set as owner 1056 * @param initiator player initiating set owner 1057 * @return boolean 1058 */ 1059 public boolean setOwner(UUID owner, PlotPlayer<?> initiator) { 1060 if (!hasOwner()) { 1061 this.setOwnerAbs(owner); 1062 this.getPlotModificationManager().create(); 1063 return true; 1064 } 1065 if (!isMerged()) { 1066 if (!owner.equals(this.getOwnerAbs())) { 1067 this.setOwnerAbs(owner); 1068 DBFunc.setOwner(this, owner); 1069 } 1070 return true; 1071 } 1072 for (final Plot current : getConnectedPlots()) { 1073 if (!owner.equals(current.getOwnerAbs())) { 1074 current.setOwnerAbs(owner); 1075 DBFunc.setOwner(current, owner); 1076 } 1077 } 1078 return true; 1079 } 1080 1081 public boolean isLoaded() { 1082 return this.worldUtil.isWorld(getWorldName()); 1083 } 1084 1085 /** 1086 * This will return null if the plot hasn't been analyzed 1087 * 1088 * @param settings The set of settings to obtain the analysis of 1089 * @return analysis of plot 1090 */ 1091 public PlotAnalysis getComplexity(Settings.Auto_Clear settings) { 1092 return PlotAnalysis.getAnalysis(this, settings); 1093 } 1094 1095 /** 1096 * Get an immutable view of all the flags associated with the plot. 1097 * 1098 * @return Immutable set containing the flags associated with the plot 1099 */ 1100 public Set<PlotFlag<?, ?>> getFlags() { 1101 return ImmutableSet.copyOf(flagContainer.getFlagMap().values()); 1102 } 1103 1104 /** 1105 * Sets a flag for the plot and stores it in the database. 1106 * 1107 * @param flag Flag to set 1108 * @param <V> flag value type 1109 * @return A boolean indicating whether or not the operation succeeded 1110 */ 1111 public <V> boolean setFlag(final @NonNull PlotFlag<V, ?> flag) { 1112 if (flag instanceof KeepFlag && PlotSquared.platform().expireManager() != null) { 1113 PlotSquared.platform().expireManager().updateExpired(this); 1114 } 1115 for (final Plot plot : this.getConnectedPlots()) { 1116 plot.getFlagContainer().addFlag(flag); 1117 plot.reEnter(); 1118 DBFunc.setFlag(plot, flag); 1119 } 1120 return true; 1121 } 1122 1123 /** 1124 * Parse the flag value into a flag instance based on the provided 1125 * flag class, and store it in the database. 1126 * 1127 * @param flag Flag type 1128 * @param value Flag value 1129 * @return A boolean indicating whether or not the operation succeeded 1130 */ 1131 public boolean setFlag(final @NonNull Class<?> flag, final @NonNull String value) { 1132 try { 1133 this.setFlag(GlobalFlagContainer.getInstance().getFlagErased(flag).parse(value)); 1134 } catch (final Exception e) { 1135 return false; 1136 } 1137 return true; 1138 } 1139 1140 /** 1141 * Remove a flag from this plot 1142 * 1143 * @param flag the flag to remove 1144 * @return success 1145 */ 1146 public boolean removeFlag(final @NonNull Class<? extends PlotFlag<?, ?>> flag) { 1147 return this.removeFlag(getFlagContainer().queryLocal(flag)); 1148 } 1149 1150 /** 1151 * Get flags associated with the plot. 1152 * 1153 * @param plotOnly Whether or not to only consider the plot. If this parameter is set to 1154 * true, the default values of the owning plot area will not be considered 1155 * @param ignorePluginFlags Whether or not to ignore {@link InternalFlag internal flags} 1156 * @return Collection containing all the flags that matched the given criteria 1157 */ 1158 public Collection<PlotFlag<?, ?>> getApplicableFlags(final boolean plotOnly, final boolean ignorePluginFlags) { 1159 if (!hasOwner()) { 1160 return Collections.emptyList(); 1161 } 1162 final Map<Class<?>, PlotFlag<?, ?>> flags = new HashMap<>(); 1163 if (!plotOnly && getArea() != null && !getArea().getFlagContainer().getFlagMap().isEmpty()) { 1164 final Map<Class<?>, PlotFlag<?, ?>> flagMap = getArea().getFlagContainer().getFlagMap(); 1165 flags.putAll(flagMap); 1166 } 1167 final Map<Class<?>, PlotFlag<?, ?>> flagMap = getFlagContainer().getFlagMap(); 1168 if (ignorePluginFlags) { 1169 for (final PlotFlag<?, ?> flag : flagMap.values()) { 1170 if (flag instanceof InternalFlag) { 1171 continue; 1172 } 1173 flags.put(flag.getClass(), flag); 1174 } 1175 } else { 1176 flags.putAll(flagMap); 1177 } 1178 return flags.values(); 1179 } 1180 1181 /** 1182 * Get flags associated with the plot and the plot area that contains it. 1183 * 1184 * @param ignorePluginFlags Whether or not to ignore {@link InternalFlag internal flags} 1185 * @return Collection containing all the flags that matched the given criteria 1186 */ 1187 public Collection<PlotFlag<?, ?>> getApplicableFlags(final boolean ignorePluginFlags) { 1188 return getApplicableFlags(false, ignorePluginFlags); 1189 } 1190 1191 /** 1192 * Remove a flag from this plot 1193 * 1194 * @param flag the flag to remove 1195 * @return success 1196 */ 1197 public boolean removeFlag(final @NonNull PlotFlag<?, ?> flag) { 1198 if (flag == null || origin == null) { 1199 return false; 1200 } 1201 boolean removed = false; 1202 for (final Plot plot : origin.getConnectedPlots()) { 1203 final Object value = plot.getFlagContainer().removeFlag(flag); 1204 if (value == null) { 1205 continue; 1206 } 1207 plot.reEnter(); 1208 DBFunc.removeFlag(plot, flag); 1209 removed = true; 1210 } 1211 return removed; 1212 } 1213 1214 /** 1215 * Count the entities in a plot 1216 * 1217 * @return array of entity counts 1218 * @see RegionManager#countEntities(Plot) 1219 */ 1220 public int[] countEntities() { 1221 int[] count = new int[6]; 1222 for (Plot current : this.getConnectedPlots()) { 1223 int[] result = this.regionManager.countEntities(current); 1224 count[CAP_ENTITY] += result[CAP_ENTITY]; 1225 count[CAP_ANIMAL] += result[CAP_ANIMAL]; 1226 count[CAP_MONSTER] += result[CAP_MONSTER]; 1227 count[CAP_MOB] += result[CAP_MOB]; 1228 count[CAP_VEHICLE] += result[CAP_VEHICLE]; 1229 count[CAP_MISC] += result[CAP_MISC]; 1230 } 1231 return count; 1232 } 1233 1234 /** 1235 * Returns true if a previous task was running 1236 * 1237 * @return {@code true} if a previous task is running 1238 */ 1239 public int addRunning() { 1240 int value = this.getRunning(); 1241 for (Plot plot : this.getConnectedPlots()) { 1242 plot.setMeta("running", value + 1); 1243 } 1244 return value; 1245 } 1246 1247 /** 1248 * Decrement the number of tracked tasks this plot is running<br> 1249 * - Used to track/limit the number of things a player can do on the plot at once 1250 * 1251 * @return previous number of tasks (int) 1252 */ 1253 public int removeRunning() { 1254 int value = this.getRunning(); 1255 if (value < 2) { 1256 for (Plot plot : this.getConnectedPlots()) { 1257 plot.deleteMeta("running"); 1258 } 1259 } else { 1260 for (Plot plot : this.getConnectedPlots()) { 1261 plot.setMeta("running", value - 1); 1262 } 1263 } 1264 return value; 1265 } 1266 1267 /** 1268 * Gets the number of tracked running tasks for this plot<br> 1269 * - Used to track/limit the number of things a player can do on the plot at once 1270 * 1271 * @return number of tasks (int) 1272 */ 1273 public int getRunning() { 1274 Integer value = (Integer) this.getMeta("running"); 1275 return value == null ? 0 : value; 1276 } 1277 1278 /** 1279 * Unclaim the plot (does not modify terrain). Changes made to this plot will not be reflected in unclaimed plot objects. 1280 * 1281 * @return {@code false} if the Plot has no owner, otherwise {@code true}. 1282 */ 1283 public boolean unclaim() { 1284 if (!this.hasOwner()) { 1285 return false; 1286 } 1287 for (Plot current : getConnectedPlots()) { 1288 List<PlotPlayer<?>> players = current.getPlayersInPlot(); 1289 for (PlotPlayer<?> pp : players) { 1290 this.plotListener.plotExit(pp, current); 1291 } 1292 1293 if (Settings.Backup.DELETE_ON_UNCLAIM) { 1294 // Destroy all backups when the plot is unclaimed 1295 Objects.requireNonNull(PlotSquared.platform()).backupManager().getProfile(current).destroy(); 1296 } 1297 1298 getArea().removePlot(getId()); 1299 DBFunc.delete(current); 1300 current.setOwnerAbs(null); 1301 current.settings = null; 1302 current.clearCache(); 1303 for (final PlotPlayer<?> pp : players) { 1304 this.plotListener.plotEntry(pp, current); 1305 } 1306 } 1307 return true; 1308 } 1309 1310 public void getCenter(final Consumer<Location> result) { 1311 Location[] corners = getCorners(); 1312 Location top = corners[0]; 1313 Location bot = corners[1]; 1314 Location location = Location.at( 1315 this.getWorldName(), 1316 MathMan.average(bot.getX(), top.getX()), 1317 MathMan.average(bot.getY(), top.getY()), 1318 MathMan.average(bot.getZ(), top.getZ()) 1319 ); 1320 this.worldUtil.getHighestBlock(getWorldName(), location.getX(), location.getZ(), y -> { 1321 int height = y; 1322 if (area.allowSigns()) { 1323 height = Math.max(y, getManager().getSignLoc(this).getY()); 1324 } 1325 result.accept(location.withY(1 + height)); 1326 }); 1327 } 1328 1329 /** 1330 * @return Location of center 1331 * @deprecated May cause synchronous chunk loads 1332 */ 1333 @Deprecated 1334 public Location getCenterSynchronous() { 1335 Location[] corners = getCorners(); 1336 Location top = corners[0]; 1337 Location bot = corners[1]; 1338 if (!isLoaded()) { 1339 return Location.at( 1340 "", 1341 0, 1342 this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 1343 0 1344 ); 1345 } 1346 Location location = Location.at( 1347 this.getWorldName(), 1348 MathMan.average(bot.getX(), top.getX()), 1349 MathMan.average(bot.getY(), top.getY()), 1350 MathMan.average(bot.getZ(), top.getZ()) 1351 ); 1352 int y = this.worldUtil.getHighestBlockSynchronous(getWorldName(), location.getX(), location.getZ()); 1353 if (area.allowSigns()) { 1354 y = Math.max(y, getManager().getSignLoc(this).getY()); 1355 } 1356 return location.withY(1 + y); 1357 } 1358 1359 /** 1360 * @return side where players should teleport to 1361 * @deprecated May cause synchronous chunk loads 1362 */ 1363 @Deprecated 1364 public Location getSideSynchronous() { 1365 CuboidRegion largest = getLargestRegion(); 1366 int x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest 1367 .getMinimumPoint() 1368 .getX(); 1369 int z = largest.getMinimumPoint().getZ() - 1; 1370 PlotManager manager = getManager(); 1371 int y = isLoaded() ? this.worldUtil.getHighestBlockSynchronous(getWorldName(), x, z) : 62; 1372 if (area.allowSigns() && (y <= area.getMinGenHeight() || y >= area.getMaxGenHeight())) { 1373 y = Math.max(y, manager.getSignLoc(this).getY() - 1); 1374 } 1375 return Location.at(getWorldName(), x, y + 1, z); 1376 } 1377 1378 public void getSide(Consumer<Location> result) { 1379 CuboidRegion largest = getLargestRegion(); 1380 int x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest 1381 .getMinimumPoint() 1382 .getX(); 1383 int z = largest.getMinimumPoint().getZ() - 1; 1384 PlotManager manager = getManager(); 1385 if (isLoaded()) { 1386 this.worldUtil.getHighestBlock(getWorldName(), x, z, y -> { 1387 int height = y; 1388 if (area.allowSigns() && (y <= area.getMinGenHeight() || y >= area.getMaxGenHeight())) { 1389 height = Math.max(y, manager.getSignLoc(this).getY() - 1); 1390 } 1391 result.accept(Location.at(getWorldName(), x, height + 1, z)); 1392 }); 1393 } else { 1394 int y = 62; 1395 if (area.allowSigns()) { 1396 y = Math.max(y, manager.getSignLoc(this).getY() - 1); 1397 } 1398 result.accept(Location.at(getWorldName(), x, y + 1, z)); 1399 } 1400 } 1401 1402 /** 1403 * @return the plot home location 1404 * @deprecated May cause synchronous chunk loading 1405 */ 1406 @Deprecated 1407 public Location getHomeSynchronous() { 1408 BlockLoc home = this.getPosition(); 1409 if (home == null || home.getX() == 0 && home.getZ() == 0) { 1410 return this.getDefaultHomeSynchronous(true); 1411 } else { 1412 Location bottom = this.getBottomAbs(); 1413 if (!isLoaded()) { 1414 return Location.at( 1415 "", 1416 0, 1417 this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 1418 0 1419 ); 1420 } 1421 Location location = toHomeLocation(bottom, home); 1422 if (!this.worldUtil.getBlockSynchronous(location).getBlockType().getMaterial().isAir()) { 1423 location = location.withY( 1424 Math.max(1 + this.worldUtil.getHighestBlockSynchronous( 1425 this.getWorldName(), 1426 location.getX(), 1427 location.getZ() 1428 ), bottom.getY())); 1429 } 1430 return location; 1431 } 1432 } 1433 1434 /** 1435 * Return the home location for the plot 1436 * 1437 * @param result consumer to pass location to when found 1438 */ 1439 public void getHome(final Consumer<Location> result) { 1440 BlockLoc home = this.getPosition(); 1441 if (home == null || home.getX() == 0 && home.getZ() == 0) { 1442 this.getDefaultHome(result); 1443 } else { 1444 if (!isLoaded()) { 1445 result.accept(Location.at( 1446 "", 1447 0, 1448 this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 1449 0 1450 )); 1451 return; 1452 } 1453 Location bottom = this.getBottomAbs(); 1454 Location location = toHomeLocation(bottom, home); 1455 this.worldUtil.getBlock(location, block -> { 1456 if (!block.getBlockType().getMaterial().isAir()) { 1457 this.worldUtil.getHighestBlock(this.getWorldName(), location.getX(), location.getZ(), 1458 y -> result.accept(location.withY(Math.max(1 + y, bottom.getY()))) 1459 ); 1460 } else { 1461 result.accept(location); 1462 } 1463 }); 1464 } 1465 } 1466 1467 private Location toHomeLocation(Location bottom, BlockLoc relativeHome) { 1468 return Location.at( 1469 bottom.getWorldName(), 1470 bottom.getX() + relativeHome.getX(), 1471 relativeHome.getY(), // y is absolute 1472 bottom.getZ() + relativeHome.getZ(), 1473 relativeHome.getYaw(), 1474 relativeHome.getPitch() 1475 ); 1476 } 1477 1478 /** 1479 * Sets the home location 1480 * 1481 * @param location location to set as home 1482 */ 1483 public void setHome(BlockLoc location) { 1484 Plot plot = this.getBasePlot(false); 1485 if (BlockLoc.ZERO.equals(location) || BlockLoc.MINY.equals(location)) { 1486 return; 1487 } 1488 plot.getSettings().setPosition(location); 1489 if (location != null) { 1490 DBFunc.setPosition(plot, plot.getSettings().getPosition().toString()); 1491 return; 1492 } 1493 DBFunc.setPosition(plot, null); 1494 } 1495 1496 /** 1497 * Gets the default home location for a plot<br> 1498 * - Ignores any home location set for that specific plot 1499 * 1500 * @param result consumer to pass location to when found 1501 */ 1502 public void getDefaultHome(Consumer<Location> result) { 1503 getDefaultHome(false, result); 1504 } 1505 1506 /** 1507 * @param member if to get the home for plot members 1508 * @return location of home for members or visitors 1509 * @deprecated May cause synchronous chunk loads 1510 */ 1511 @Deprecated 1512 public Location getDefaultHomeSynchronous(final boolean member) { 1513 Plot plot = this.getBasePlot(false); 1514 BlockLoc loc = member ? area.defaultHome() : area.nonmemberHome(); 1515 if (loc != null) { 1516 int x; 1517 int z; 1518 if (loc.getX() == Integer.MAX_VALUE && loc.getZ() == Integer.MAX_VALUE) { 1519 // center 1520 if (getArea() instanceof SinglePlotArea) { 1521 int y = loc.getY() == Integer.MIN_VALUE 1522 ? (isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), 0, 0) + 1 : 63) 1523 : loc.getY(); 1524 return Location.at(plot.getWorldName(), 0, y, 0, 0, 0); 1525 } 1526 CuboidRegion largest = plot.getLargestRegion(); 1527 x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest 1528 .getMinimumPoint() 1529 .getX(); 1530 z = (largest.getMaximumPoint().getZ() >> 1) - (largest.getMinimumPoint().getZ() >> 1) + largest 1531 .getMinimumPoint() 1532 .getZ(); 1533 } else { 1534 // specific 1535 Location bot = plot.getBottomAbs(); 1536 x = bot.getX() + loc.getX(); 1537 z = bot.getZ() + loc.getZ(); 1538 } 1539 int y = loc.getY() == Integer.MIN_VALUE 1540 ? (isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), x, z) + 1 : 63) 1541 : loc.getY(); 1542 return Location.at(plot.getWorldName(), x, y, z, loc.getYaw(), loc.getPitch()); 1543 } 1544 if (getArea() instanceof SinglePlotArea) { 1545 int y = isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), 0, 0) + 1 : 63; 1546 return Location.at(plot.getWorldName(), 0, y, 0, 0, 0); 1547 } 1548 // Side 1549 return plot.getSideSynchronous(); 1550 } 1551 1552 public void getDefaultHome(boolean member, Consumer<Location> result) { 1553 Plot plot = this.getBasePlot(false); 1554 if (!isLoaded()) { 1555 result.accept(Location.at( 1556 "", 1557 0, 1558 this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 4, 1559 0 1560 )); 1561 return; 1562 } 1563 BlockLoc loc = member ? area.defaultHome() : area.nonmemberHome(); 1564 if (loc != null) { 1565 int x; 1566 int z; 1567 if (loc.getX() == Integer.MAX_VALUE && loc.getZ() == Integer.MAX_VALUE) { 1568 // center 1569 if (getArea() instanceof SinglePlotArea) { 1570 x = 0; 1571 z = 0; 1572 } else { 1573 CuboidRegion largest = plot.getLargestRegion(); 1574 x = (largest.getMaximumPoint().getX() >> 1) - (largest.getMinimumPoint().getX() >> 1) + largest 1575 .getMinimumPoint() 1576 .getX(); 1577 z = (largest.getMaximumPoint().getZ() >> 1) - (largest.getMinimumPoint().getZ() >> 1) + largest 1578 .getMinimumPoint() 1579 .getZ(); 1580 } 1581 } else { 1582 // specific 1583 Location bot = plot.getBottomAbs(); 1584 x = bot.getX() + loc.getX(); 1585 z = bot.getZ() + loc.getZ(); 1586 } 1587 if (loc.getY() == Integer.MIN_VALUE) { 1588 if (isLoaded()) { 1589 this.worldUtil.getHighestBlock( 1590 plot.getWorldName(), 1591 x, 1592 z, 1593 y -> result.accept(Location.at(plot.getWorldName(), x, y + 1, z)) 1594 ); 1595 } else { 1596 int y = this.getArea() instanceof ClassicPlotWorld ? ((ClassicPlotWorld) this.getArea()).PLOT_HEIGHT + 1 : 63; 1597 result.accept(Location.at(plot.getWorldName(), x, y, z, loc.getYaw(), loc.getPitch())); 1598 } 1599 } else { 1600 result.accept(Location.at(plot.getWorldName(), x, loc.getY(), z, loc.getYaw(), loc.getPitch())); 1601 } 1602 return; 1603 } 1604 // Side 1605 if (getArea() instanceof SinglePlotArea) { 1606 int y = isLoaded() ? this.worldUtil.getHighestBlockSynchronous(plot.getWorldName(), 0, 0) + 1 : 63; 1607 result.accept(Location.at(plot.getWorldName(), 0, y, 0, 0, 0)); 1608 } 1609 plot.getSide(result); 1610 } 1611 1612 public double getVolume() { 1613 double count = 0; 1614 for (CuboidRegion region : getRegions()) { 1615 // CuboidRegion#getArea is deprecated and we want to ensure use of correct height 1616 count += region.getLength() * region.getWidth() * (area.getMaxGenHeight() - area.getMinGenHeight() + 1); 1617 } 1618 return count; 1619 } 1620 1621 /** 1622 * Gets the average rating of the plot. This is the value displayed in /plot info 1623 * 1624 * @return average rating as double, {@link Double#NaN} of no ratings exist 1625 */ 1626 public double getAverageRating() { 1627 Collection<Rating> ratings = this.getRatings().values(); 1628 double sum = ratings.stream().mapToDouble(Rating::getAverageRating).sum(); 1629 return sum / ratings.size(); 1630 } 1631 1632 /** 1633 * Sets a rating for a user<br> 1634 * - If the user has already rated, the following will return false 1635 * 1636 * @param uuid uuid of rater 1637 * @param rating rating 1638 * @return success 1639 */ 1640 public boolean addRating(UUID uuid, Rating rating) { 1641 Plot base = this.getBasePlot(false); 1642 PlotSettings baseSettings = base.getSettings(); 1643 if (baseSettings.getRatings().containsKey(uuid)) { 1644 return false; 1645 } 1646 int aggregate = rating.getAggregate(); 1647 baseSettings.getRatings().put(uuid, aggregate); 1648 DBFunc.setRating(base, uuid, aggregate); 1649 return true; 1650 } 1651 1652 /** 1653 * Clear the ratings/likes for this plot 1654 */ 1655 public void clearRatings() { 1656 Plot base = this.getBasePlot(false); 1657 PlotSettings baseSettings = base.getSettings(); 1658 if (baseSettings.getRatings() != null && !baseSettings.getRatings().isEmpty()) { 1659 DBFunc.deleteRatings(base); 1660 baseSettings.setRatings(null); 1661 } 1662 } 1663 1664 public Map<UUID, Boolean> getLikes() { 1665 final Map<UUID, Boolean> map = new HashMap<>(); 1666 final Map<UUID, Rating> ratings = this.getRatings(); 1667 ratings.forEach((uuid, rating) -> map.put(uuid, rating.getLike())); 1668 return map; 1669 } 1670 1671 /** 1672 * Gets the ratings associated with a plot<br> 1673 * - The rating object may contain multiple categories 1674 * 1675 * @return Map of user who rated to the rating 1676 */ 1677 public HashMap<UUID, Rating> getRatings() { 1678 Plot base = this.getBasePlot(false); 1679 HashMap<UUID, Rating> map = new HashMap<>(); 1680 if (!base.hasRatings()) { 1681 return map; 1682 } 1683 for (Entry<UUID, Integer> entry : base.getSettings().getRatings().entrySet()) { 1684 map.put(entry.getKey(), new Rating(entry.getValue())); 1685 } 1686 return map; 1687 } 1688 1689 public boolean hasRatings() { 1690 Plot base = this.getBasePlot(false); 1691 return base.settings != null && base.settings.getRatings() != null; 1692 } 1693 1694 @Deprecated(forRemoval = true, since = "6.1.0") 1695 public boolean claim(final @NonNull PlotPlayer<?> player, boolean teleport, String schematic) { 1696 if (!canClaim(player)) { 1697 return false; 1698 } 1699 return claim(player, teleport, schematic, true); 1700 } 1701 1702 @Deprecated(forRemoval = true, since = "6.1.0") 1703 public boolean claim(final @NonNull PlotPlayer<?> player, boolean teleport, String schematic, boolean updateDB) { 1704 return claim(player, teleport, schematic, updateDB, false); 1705 } 1706 1707 /** 1708 * Claim the plot 1709 * 1710 * @param player The player to set the owner to 1711 * @param teleport If the player should be teleported 1712 * @param schematic The schematic name to paste on the plot 1713 * @param updateDB If the database should be updated 1714 * @param auto If the plot is being claimed by a /plot auto 1715 * @return success 1716 * @since 6.1.0 1717 */ 1718 public boolean claim( 1719 final @NonNull PlotPlayer<?> player, boolean teleport, String schematic, boolean updateDB, 1720 boolean auto 1721 ) { 1722 this.eventDispatcher.callPlotClaimedNotify(this, auto); 1723 if (updateDB) { 1724 if (!this.getPlotModificationManager().create(player.getUUID(), true)) { 1725 LOGGER.error("Player {} attempted to claim plot {}, but the database failed to update", player.getName(), 1726 this.getId().toCommaSeparatedString() 1727 ); 1728 return false; 1729 } 1730 } else { 1731 area.addPlot(this); 1732 updateWorldBorder(); 1733 } 1734 player.sendMessage(TranslatableCaption.of("working.claimed"), Template.of("plot", this.getId().toString())); 1735 if (teleport) { 1736 if (!auto && Settings.Teleport.ON_CLAIM) { 1737 teleportPlayer(player, TeleportCause.COMMAND_CLAIM, result -> { 1738 }); 1739 } else if (auto && Settings.Teleport.ON_AUTO) { 1740 teleportPlayer(player, TeleportCause.COMMAND_AUTO, result -> { 1741 }); 1742 } 1743 } 1744 PlotArea plotworld = getArea(); 1745 if (plotworld.isSchematicOnClaim()) { 1746 Schematic sch; 1747 try { 1748 if (schematic == null || schematic.isEmpty()) { 1749 sch = schematicHandler.getSchematic(plotworld.getSchematicFile()); 1750 } else { 1751 sch = schematicHandler.getSchematic(schematic); 1752 if (sch == null) { 1753 sch = schematicHandler.getSchematic(plotworld.getSchematicFile()); 1754 } 1755 } 1756 } catch (SchematicHandler.UnsupportedFormatException e) { 1757 e.printStackTrace(); 1758 return true; 1759 } 1760 schematicHandler.paste( 1761 sch, 1762 this, 1763 0, 1764 getArea().getMinBuildHeight(), 1765 0, 1766 Settings.Schematics.PASTE_ON_TOP, 1767 player, 1768 new RunnableVal<>() { 1769 @Override 1770 public void run(Boolean value) { 1771 if (value) { 1772 player.sendMessage(TranslatableCaption.of("schematics.schematic_paste_success")); 1773 } else { 1774 player.sendMessage(TranslatableCaption.of("schematics.schematic_paste_failed")); 1775 } 1776 } 1777 } 1778 ); 1779 } 1780 plotworld.getPlotManager().claimPlot(this, null); 1781 this.getPlotModificationManager().setSign(player.getName()); 1782 return true; 1783 } 1784 1785 /** 1786 * Retrieve the biome of the plot. 1787 * 1788 * @param result consumer to pass biome to when found 1789 */ 1790 public void getBiome(Consumer<BiomeType> result) { 1791 this.getCenter(location -> this.worldUtil.getBiome(location.getWorldName(), location.getX(), location.getZ(), result)); 1792 } 1793 1794 //TODO Better documentation needed. 1795 1796 /** 1797 * @return biome at center of plot 1798 * @deprecated May cause synchronous chunk loads 1799 */ 1800 @Deprecated 1801 public BiomeType getBiomeSynchronous() { 1802 final Location location = this.getCenterSynchronous(); 1803 return this.worldUtil.getBiomeSynchronous(location.getWorldName(), location.getX(), location.getZ()); 1804 } 1805 1806 /** 1807 * Returns the top location for the plot. 1808 * 1809 * @return location of Absolute Top 1810 */ 1811 public Location getTopAbs() { 1812 return this.getManager().getPlotTopLocAbs(this.id).withWorld(this.getWorldName()); 1813 } 1814 1815 /** 1816 * Returns the bottom location for the plot. 1817 * 1818 * @return location of absolute bottom of plot 1819 */ 1820 public Location getBottomAbs() { 1821 return this.getManager().getPlotBottomLocAbs(this.id).withWorld(this.getWorldName()); 1822 } 1823 1824 /** 1825 * Swaps the settings for two plots. 1826 * 1827 * @param plot the plot to swap data with 1828 * @return Future containing the result 1829 */ 1830 public CompletableFuture<Boolean> swapData(Plot plot) { 1831 if (!this.hasOwner()) { 1832 if (plot != null && plot.hasOwner()) { 1833 plot.moveData(this, null); 1834 return CompletableFuture.completedFuture(true); 1835 } 1836 return CompletableFuture.completedFuture(false); 1837 } 1838 if (plot == null || plot.getOwner() == null) { 1839 this.moveData(plot, null); 1840 return CompletableFuture.completedFuture(true); 1841 } 1842 // Swap cached 1843 final PlotId temp = PlotId.of(this.getId().getX(), this.getId().getY()); 1844 this.id = plot.getId(); 1845 plot.id = temp; 1846 this.area.removePlot(this.getId()); 1847 plot.area.removePlot(plot.getId()); 1848 this.area.addPlotAbs(this); 1849 plot.area.addPlotAbs(plot); 1850 // Swap database 1851 return DBFunc.swapPlots(plot, this); 1852 } 1853 1854 /** 1855 * Moves the settings for a plot. 1856 * 1857 * @param plot the plot to move 1858 * @param whenDone task to run when settings have been moved 1859 * @return success or not 1860 */ 1861 public boolean moveData(Plot plot, Runnable whenDone) { 1862 if (!this.hasOwner()) { 1863 TaskManager.runTask(whenDone); 1864 return false; 1865 } 1866 if (plot.hasOwner()) { 1867 TaskManager.runTask(whenDone); 1868 return false; 1869 } 1870 this.area.removePlot(this.id); 1871 this.id = plot.getId(); 1872 this.area.addPlotAbs(this); 1873 clearCache(); 1874 DBFunc.movePlot(this, plot); 1875 TaskManager.runTaskLater(whenDone, TaskTime.ticks(1L)); 1876 return true; 1877 } 1878 1879 /** 1880 * Gets the top loc of a plot (if mega, returns top loc of that mega plot) - If you would like each plot treated as 1881 * a small plot use {@link #getTopAbs()} 1882 * 1883 * @return Location top of mega plot 1884 */ 1885 public Location getExtendedTopAbs() { 1886 Location top = this.getTopAbs(); 1887 if (!this.isMerged()) { 1888 return top; 1889 } 1890 if (this.isMerged(Direction.SOUTH)) { 1891 top = top.withZ(this.getRelative(Direction.SOUTH).getBottomAbs().getZ() - 1); 1892 } 1893 if (this.isMerged(Direction.EAST)) { 1894 top = top.withX(this.getRelative(Direction.EAST).getBottomAbs().getX() - 1); 1895 } 1896 return top; 1897 } 1898 1899 /** 1900 * Gets the bot loc of a plot (if mega, returns bot loc of that mega plot) - If you would like each plot treated as 1901 * a small plot use {@link #getBottomAbs()} 1902 * 1903 * @return Location bottom of mega plot 1904 */ 1905 public Location getExtendedBottomAbs() { 1906 Location bot = this.getBottomAbs(); 1907 if (!this.isMerged()) { 1908 return bot; 1909 } 1910 if (this.isMerged(Direction.NORTH)) { 1911 bot = bot.withZ(this.getRelative(Direction.NORTH).getTopAbs().getZ() + 1); 1912 } 1913 if (this.isMerged(Direction.WEST)) { 1914 bot = bot.withX(this.getRelative(Direction.WEST).getTopAbs().getX() + 1); 1915 } 1916 return bot; 1917 } 1918 1919 /** 1920 * Returns the top and bottom location.<br> 1921 * - If the plot is not connected, it will return its own corners<br> 1922 * - the returned locations will not necessarily correspond to claimed plots if the connected plots do not form a rectangular shape 1923 * 1924 * @return new Location[] { bottom, top } 1925 * @deprecated as merged plots no longer need to be rectangular 1926 */ 1927 @Deprecated 1928 public Location[] getCorners() { 1929 if (!this.isMerged()) { 1930 return new Location[]{this.getBottomAbs(), this.getTopAbs()}; 1931 } 1932 return RegionUtil.getCorners(this.getWorldName(), this.getRegions()); 1933 } 1934 1935 /** 1936 * @return bottom corner location 1937 * @deprecated in favor of getCorners()[0];<br> 1938 */ 1939 // Won't remove as suggestion also points to deprecated method 1940 @Deprecated 1941 public Location getBottom() { 1942 return this.getCorners()[0]; 1943 } 1944 1945 /** 1946 * @return the top corner of the plot 1947 * @deprecated in favor of getCorners()[1]; 1948 */ 1949 // Won't remove as suggestion also points to deprecated method 1950 @Deprecated 1951 public Location getTop() { 1952 return this.getCorners()[1]; 1953 } 1954 1955 /** 1956 * Gets plot display name. 1957 * 1958 * @return alias if set, else id 1959 */ 1960 @Override 1961 public String toString() { 1962 if (this.settings != null && this.settings.getAlias().length() > 1) { 1963 return this.settings.getAlias(); 1964 } 1965 return this.area + ";" + this.id; 1966 } 1967 1968 /** 1969 * Remove a denied player (use DBFunc as well)<br> 1970 * Using the * uuid will remove all users 1971 * 1972 * @param uuid uuid of player to remove from denied list 1973 * @return success or not 1974 */ 1975 public boolean removeDenied(UUID uuid) { 1976 if (uuid == DBFunc.EVERYONE && !denied.contains(uuid)) { 1977 boolean result = false; 1978 for (UUID other : new HashSet<>(getDenied())) { 1979 result = rmvDenied(other) || result; 1980 } 1981 return result; 1982 } 1983 return rmvDenied(uuid); 1984 } 1985 1986 private boolean rmvDenied(UUID uuid) { 1987 for (Plot current : this.getConnectedPlots()) { 1988 if (current.getDenied().remove(uuid)) { 1989 DBFunc.removeDenied(current, uuid); 1990 } else { 1991 return false; 1992 } 1993 } 1994 return true; 1995 } 1996 1997 /** 1998 * Remove a helper (use DBFunc as well)<br> 1999 * Using the * uuid will remove all users 2000 * 2001 * @param uuid uuid of trusted player to remove 2002 * @return success or not 2003 */ 2004 public boolean removeTrusted(UUID uuid) { 2005 if (uuid == DBFunc.EVERYONE && !trusted.contains(uuid)) { 2006 boolean result = false; 2007 for (UUID other : new HashSet<>(getTrusted())) { 2008 result = rmvTrusted(other) || result; 2009 } 2010 return result; 2011 } 2012 return rmvTrusted(uuid); 2013 } 2014 2015 private boolean rmvTrusted(UUID uuid) { 2016 for (Plot plot : this.getConnectedPlots()) { 2017 if (plot.getTrusted().remove(uuid)) { 2018 DBFunc.removeTrusted(plot, uuid); 2019 } else { 2020 return false; 2021 } 2022 } 2023 return true; 2024 } 2025 2026 /** 2027 * Remove a trusted user (use DBFunc as well)<br> 2028 * Using the * uuid will remove all users 2029 * 2030 * @param uuid uuid of player to remove 2031 * @return success or not 2032 */ 2033 public boolean removeMember(UUID uuid) { 2034 if (this.members == null) { 2035 return false; 2036 } 2037 if (uuid == DBFunc.EVERYONE && !members.contains(uuid)) { 2038 boolean result = false; 2039 for (UUID other : new HashSet<>(this.members)) { 2040 result = rmvMember(other) || result; 2041 } 2042 return result; 2043 } 2044 return rmvMember(uuid); 2045 } 2046 2047 private boolean rmvMember(UUID uuid) { 2048 for (Plot current : this.getConnectedPlots()) { 2049 if (current.getMembers().remove(uuid)) { 2050 DBFunc.removeMember(current, uuid); 2051 } else { 2052 return false; 2053 } 2054 } 2055 return true; 2056 } 2057 2058 @Override 2059 public boolean equals(Object obj) { 2060 if (this == obj) { 2061 return true; 2062 } 2063 if (obj == null) { 2064 return false; 2065 } 2066 if (this.getClass() != obj.getClass()) { 2067 return false; 2068 } 2069 Plot other = (Plot) obj; 2070 return this.hashCode() == other.hashCode() && this.id.equals(other.id) && this.area == other.area; 2071 } 2072 2073 /** 2074 * Gets the plot hashcode<br> 2075 * Note: The hashcode is unique if:<br> 2076 * - Plots are in the same world<br> 2077 * - The x,z coordinates are between Short.MIN_VALUE and Short.MAX_VALUE<br> 2078 * 2079 * @return integer. 2080 */ 2081 @Override 2082 public int hashCode() { 2083 return this.id.hashCode(); 2084 } 2085 2086 /** 2087 * Gets the plot alias. 2088 * - Returns an empty string if no alias is set 2089 * 2090 * @return The plot alias 2091 */ 2092 public @NonNull String getAlias() { 2093 if (this.settings == null) { 2094 return ""; 2095 } 2096 return this.settings.getAlias(); 2097 } 2098 2099 /** 2100 * Sets the plot alias. 2101 * 2102 * @param alias The alias 2103 */ 2104 public void setAlias(String alias) { 2105 for (Plot current : this.getConnectedPlots()) { 2106 String name = this.getSettings().getAlias(); 2107 if (alias == null) { 2108 alias = ""; 2109 } 2110 if (name.equals(alias)) { 2111 return; 2112 } 2113 current.getSettings().setAlias(alias); 2114 DBFunc.setAlias(current, alias); 2115 } 2116 } 2117 2118 /** 2119 * Sets the raw merge data<br> 2120 * - Updates DB<br> 2121 * - Does not modify terrain<br> 2122 * 2123 * @param direction direction to merge the plot in 2124 * @param value if the plot is merged or not 2125 */ 2126 public void setMerged(Direction direction, boolean value) { 2127 if (this.getSettings().setMerged(direction, value)) { 2128 if (value) { 2129 Plot other = this.getRelative(direction).getBasePlot(false); 2130 if (!other.equals(this.getBasePlot(false))) { 2131 Plot base = other.id.getY() < this.id.getY() || other.id.getY() == this.id.getY() && other.id.getX() < this.id 2132 .getX() ? 2133 other : 2134 this.origin; 2135 this.origin.origin = base; 2136 other.origin = base; 2137 this.origin = base; 2138 this.connectedCache = null; 2139 } 2140 } else { 2141 if (this.origin != null) { 2142 this.origin.origin = null; 2143 this.origin = null; 2144 } 2145 this.connectedCache = null; 2146 } 2147 DBFunc.setMerged(this, this.getSettings().getMerged()); 2148 } 2149 } 2150 2151 /** 2152 * Gets the merged array. 2153 * 2154 * @return boolean [ north, east, south, west ] 2155 */ 2156 public boolean[] getMerged() { 2157 return this.getSettings().getMerged(); 2158 } 2159 2160 /** 2161 * Sets the raw merge data<br> 2162 * - Updates DB<br> 2163 * - Does not modify terrain<br> 2164 * Gets if the plot is merged in a direction<br> 2165 * ----------<br> 2166 * 0 = north<br> 2167 * 1 = east<br> 2168 * 2 = south<br> 2169 * 3 = west<br> 2170 * ----------<br> 2171 * Note: Diagonal merging (4-7) must be done by merging the corresponding plots. 2172 * 2173 * @param merged set the plot's merged plots 2174 */ 2175 public void setMerged(boolean[] merged) { 2176 this.getSettings().setMerged(merged); 2177 DBFunc.setMerged(this, merged); 2178 clearCache(); 2179 } 2180 2181 public void clearCache() { 2182 this.connectedCache = null; 2183 if (this.origin != null) { 2184 this.origin.origin = null; 2185 this.origin = null; 2186 } 2187 } 2188 2189 /** 2190 * Gets the set home location or 0,Integer#MIN_VALUE,0 if no location is set<br> 2191 * - Does not take the default home location into account 2192 * - PlotSquared will internally find the correct place to teleport to if y = Integer#MIN_VALUE when teleporting to the plot. 2193 * 2194 * @return home location 2195 */ 2196 public BlockLoc getPosition() { 2197 return this.getSettings().getPosition(); 2198 } 2199 2200 /** 2201 * Check if a plot can be claimed by the provided player. 2202 * 2203 * @param player the claiming player 2204 * @return if the given player can claim the plot 2205 */ 2206 public boolean canClaim(@NonNull PlotPlayer<?> player) { 2207 PlotCluster cluster = this.getCluster(); 2208 if (cluster != null) { 2209 if (!cluster.isAdded(player.getUUID()) && !player.hasPermission("plots.admin.command.claim")) { 2210 return false; 2211 } 2212 } 2213 final UUID owner = this.getOwnerAbs(); 2214 if (owner != null) { 2215 return false; 2216 } 2217 return !isMerged(); 2218 } 2219 2220 /** 2221 * Merge the plot settings<br> 2222 * - Used when a plot is merged<br> 2223 * 2224 * @param plot plot to merge the data from 2225 */ 2226 public void mergeData(Plot plot) { 2227 final FlagContainer flagContainer1 = this.getFlagContainer(); 2228 final FlagContainer flagContainer2 = plot.getFlagContainer(); 2229 if (!flagContainer1.equals(flagContainer2)) { 2230 boolean greater = flagContainer1.getFlagMap().size() > flagContainer2.getFlagMap().size(); 2231 if (greater) { 2232 flagContainer1.addAll(flagContainer2.getFlagMap().values()); 2233 } else { 2234 flagContainer2.addAll(flagContainer1.getFlagMap().values()); 2235 } 2236 if (!greater) { 2237 this.flagContainer.clearLocal(); 2238 this.flagContainer.addAll(flagContainer2.getFlagMap().values()); 2239 } 2240 plot.flagContainer.clearLocal(); 2241 plot.flagContainer.addAll(this.flagContainer.getFlagMap().values()); 2242 } 2243 if (!this.getAlias().isEmpty()) { 2244 plot.setAlias(this.getAlias()); 2245 } else if (!plot.getAlias().isEmpty()) { 2246 this.setAlias(plot.getAlias()); 2247 } 2248 for (UUID uuid : this.getTrusted()) { 2249 plot.addTrusted(uuid); 2250 } 2251 for (UUID uuid : plot.getTrusted()) { 2252 this.addTrusted(uuid); 2253 } 2254 for (UUID uuid : this.getMembers()) { 2255 plot.addMember(uuid); 2256 } 2257 for (UUID uuid : plot.getMembers()) { 2258 this.addMember(uuid); 2259 } 2260 2261 for (UUID uuid : this.getDenied()) { 2262 plot.addDenied(uuid); 2263 } 2264 for (UUID uuid : plot.getDenied()) { 2265 this.addDenied(uuid); 2266 } 2267 } 2268 2269 /** 2270 * Gets the plot in a relative location<br> 2271 * Note: May be null if the partial plot area does not include the relative location 2272 * 2273 * @param x relative id X 2274 * @param y relative id Y 2275 * @return Plot 2276 */ 2277 public Plot getRelative(int x, int y) { 2278 return this.area.getPlotAbs(PlotId.of(this.id.getX() + x, this.id.getY() + y)); 2279 } 2280 2281 public Plot getRelative(PlotArea area, int x, int y) { 2282 return area.getPlotAbs(PlotId.of(this.id.getX() + x, this.id.getY() + y)); 2283 } 2284 2285 /** 2286 * Gets the plot in a relative direction 2287 * Note: May be null if the partial plot area does not include the relative location 2288 * 2289 * @param direction Direction 2290 * @return the plot relative to this one 2291 */ 2292 public @Nullable Plot getRelative(@NonNull Direction direction) { 2293 return this.area.getPlotAbs(this.id.getRelative(direction)); 2294 } 2295 2296 /** 2297 * Gets a set of plots connected (and including) this plot<br> 2298 * - This result is cached globally 2299 * 2300 * @return a Set of Plots connected to this Plot 2301 */ 2302 public Set<Plot> getConnectedPlots() { 2303 if (this.settings == null) { 2304 return Collections.singleton(this); 2305 } 2306 if (!this.isMerged()) { 2307 return Collections.singleton(this); 2308 } 2309 if (this.connectedCache != null && this.connectedCache.contains(this)) { 2310 return this.connectedCache; 2311 } 2312 2313 HashSet<Plot> tmpSet = new HashSet<>(); 2314 tmpSet.add(this); 2315 Plot tmp; 2316 HashSet<Object> queuecache = new HashSet<>(); 2317 ArrayDeque<Plot> frontier = new ArrayDeque<>(); 2318 if (this.isMerged(Direction.NORTH)) { 2319 tmp = this.area.getPlotAbs(this.id.getRelative(Direction.NORTH)); 2320 if (!tmp.isMerged(Direction.SOUTH)) { 2321 // invalid merge 2322 if (tmp.isOwnerAbs(this.getOwnerAbs())) { 2323 tmp.getSettings().setMerged(Direction.SOUTH, true); 2324 DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); 2325 } else { 2326 this.getSettings().setMerged(Direction.NORTH, false); 2327 DBFunc.setMerged(this, this.getSettings().getMerged()); 2328 } 2329 } 2330 queuecache.add(tmp); 2331 frontier.add(tmp); 2332 } 2333 if (this.isMerged(Direction.EAST)) { 2334 tmp = this.area.getPlotAbs(this.id.getRelative(Direction.EAST)); 2335 assert tmp != null; 2336 if (!tmp.isMerged(Direction.WEST)) { 2337 // invalid merge 2338 if (tmp.isOwnerAbs(this.getOwnerAbs())) { 2339 tmp.getSettings().setMerged(Direction.WEST, true); 2340 DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); 2341 } else { 2342 this.getSettings().setMerged(Direction.EAST, false); 2343 DBFunc.setMerged(this, this.getSettings().getMerged()); 2344 } 2345 } 2346 queuecache.add(tmp); 2347 frontier.add(tmp); 2348 } 2349 if (this.isMerged(Direction.SOUTH)) { 2350 tmp = this.area.getPlotAbs(this.id.getRelative(Direction.SOUTH)); 2351 assert tmp != null; 2352 if (!tmp.isMerged(Direction.NORTH)) { 2353 // invalid merge 2354 if (tmp.isOwnerAbs(this.getOwnerAbs())) { 2355 tmp.getSettings().setMerged(Direction.NORTH, true); 2356 DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); 2357 } else { 2358 this.getSettings().setMerged(Direction.SOUTH, false); 2359 DBFunc.setMerged(this, this.getSettings().getMerged()); 2360 } 2361 } 2362 queuecache.add(tmp); 2363 frontier.add(tmp); 2364 } 2365 if (this.isMerged(Direction.WEST)) { 2366 tmp = this.area.getPlotAbs(this.id.getRelative(Direction.WEST)); 2367 if (!tmp.isMerged(Direction.EAST)) { 2368 // invalid merge 2369 if (tmp.isOwnerAbs(this.getOwnerAbs())) { 2370 tmp.getSettings().setMerged(Direction.EAST, true); 2371 DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); 2372 } else { 2373 this.getSettings().setMerged(Direction.WEST, false); 2374 DBFunc.setMerged(this, this.getSettings().getMerged()); 2375 } 2376 } 2377 queuecache.add(tmp); 2378 frontier.add(tmp); 2379 } 2380 Plot current; 2381 while ((current = frontier.poll()) != null) { 2382 if (!current.hasOwner() || current.settings == null) { 2383 continue; 2384 } 2385 tmpSet.add(current); 2386 queuecache.remove(current); 2387 if (current.isMerged(Direction.NORTH)) { 2388 tmp = current.area.getPlotAbs(current.id.getRelative(Direction.NORTH)); 2389 if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { 2390 queuecache.add(tmp); 2391 frontier.add(tmp); 2392 } 2393 } 2394 if (current.isMerged(Direction.EAST)) { 2395 tmp = current.area.getPlotAbs(current.id.getRelative(Direction.EAST)); 2396 if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { 2397 queuecache.add(tmp); 2398 frontier.add(tmp); 2399 } 2400 } 2401 if (current.isMerged(Direction.SOUTH)) { 2402 tmp = current.area.getPlotAbs(current.id.getRelative(Direction.SOUTH)); 2403 if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { 2404 queuecache.add(tmp); 2405 frontier.add(tmp); 2406 } 2407 } 2408 if (current.isMerged(Direction.WEST)) { 2409 tmp = current.area.getPlotAbs(current.id.getRelative(Direction.WEST)); 2410 if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { 2411 queuecache.add(tmp); 2412 frontier.add(tmp); 2413 } 2414 } 2415 } 2416 this.connectedCache = tmpSet; 2417 return tmpSet; 2418 } 2419 2420 /** 2421 * This will combine each plot into effective rectangular regions<br> 2422 * - This result is cached globally<br> 2423 * - Useful for handling non rectangular shapes 2424 * 2425 * @return all regions within the plot 2426 */ 2427 public @NonNull Set<CuboidRegion> getRegions() { 2428 if (!this.isMerged()) { 2429 Location pos1 = this.getBottomAbs().withY(getArea().getMinBuildHeight()); 2430 Location pos2 = this.getTopAbs().withY(getArea().getMaxBuildHeight()); 2431 this.connectedCache = Sets.newHashSet(this); 2432 CuboidRegion rg = new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()); 2433 return Collections.singleton(rg); 2434 } 2435 Set<Plot> plots = this.getConnectedPlots(); 2436 Set<CuboidRegion> regions = new HashSet<>(); 2437 Set<PlotId> visited = new HashSet<>(); 2438 for (Plot current : plots) { 2439 if (visited.contains(current.getId())) { 2440 continue; 2441 } 2442 boolean merge = true; 2443 PlotId bot = current.getId(); 2444 PlotId top = current.getId(); 2445 while (merge) { 2446 merge = false; 2447 Iterable<PlotId> ids = PlotId.PlotRangeIterator.range( 2448 PlotId.of(bot.getX(), bot.getY() - 1), 2449 PlotId.of(top.getX(), bot.getY() - 1) 2450 ); 2451 boolean tmp = true; 2452 for (PlotId id : ids) { 2453 Plot plot = this.area.getPlotAbs(id); 2454 if (plot == null || !plot.isMerged(Direction.SOUTH) || visited.contains(plot.getId())) { 2455 tmp = false; 2456 } 2457 } 2458 if (tmp) { 2459 merge = true; 2460 bot = PlotId.of(bot.getX(), bot.getY() - 1); 2461 } 2462 ids = PlotId.PlotRangeIterator.range( 2463 PlotId.of(top.getX() + 1, bot.getY()), 2464 PlotId.of(top.getX() + 1, top.getY()) 2465 ); 2466 tmp = true; 2467 for (PlotId id : ids) { 2468 Plot plot = this.area.getPlotAbs(id); 2469 if (plot == null || !plot.isMerged(Direction.WEST) || visited.contains(plot.getId())) { 2470 tmp = false; 2471 } 2472 } 2473 if (tmp) { 2474 merge = true; 2475 top = PlotId.of(top.getX() + 1, top.getY()); 2476 } 2477 ids = PlotId.PlotRangeIterator.range( 2478 PlotId.of(bot.getX(), top.getY() + 1), 2479 PlotId.of(top.getX(), top.getY() + 1) 2480 ); 2481 tmp = true; 2482 for (PlotId id : ids) { 2483 Plot plot = this.area.getPlotAbs(id); 2484 if (plot == null || !plot.isMerged(Direction.NORTH) || visited.contains(plot.getId())) { 2485 tmp = false; 2486 } 2487 } 2488 if (tmp) { 2489 merge = true; 2490 top = PlotId.of(top.getX(), top.getY() + 1); 2491 } 2492 ids = PlotId.PlotRangeIterator.range( 2493 PlotId.of(bot.getX() - 1, bot.getY()), 2494 PlotId.of(bot.getX() - 1, top.getY()) 2495 ); 2496 tmp = true; 2497 for (PlotId id : ids) { 2498 Plot plot = this.area.getPlotAbs(id); 2499 if (plot == null || !plot.isMerged(Direction.EAST) || visited.contains(plot.getId())) { 2500 tmp = false; 2501 } 2502 } 2503 if (tmp) { 2504 merge = true; 2505 bot = PlotId.of(bot.getX() - 1, bot.getY()); 2506 } 2507 } 2508 int minHeight = getArea().getMinBuildHeight(); 2509 int maxHeight = getArea().getMaxBuildHeight() - 1; 2510 Location gtopabs = this.area.getPlotAbs(top).getTopAbs(); 2511 Location gbotabs = this.area.getPlotAbs(bot).getBottomAbs(); 2512 visited.addAll(Lists.newArrayList((Iterable<? extends PlotId>) PlotId.PlotRangeIterator.range(bot, top))); 2513 for (int x = bot.getX(); x <= top.getX(); x++) { 2514 Plot plot = this.area.getPlotAbs(PlotId.of(x, top.getY())); 2515 if (plot.isMerged(Direction.SOUTH)) { 2516 // south wedge 2517 Location toploc = plot.getExtendedTopAbs(); 2518 Location botabs = plot.getBottomAbs(); 2519 Location topabs = plot.getTopAbs(); 2520 BlockVector3 pos1 = BlockVector3.at(botabs.getX(), minHeight, topabs.getZ() + 1); 2521 BlockVector3 pos2 = BlockVector3.at(topabs.getX(), maxHeight, toploc.getZ()); 2522 regions.add(new CuboidRegion(pos1, pos2)); 2523 if (plot.isMerged(Direction.SOUTHEAST)) { 2524 pos1 = BlockVector3.at(topabs.getX() + 1, minHeight, topabs.getZ() + 1); 2525 pos2 = BlockVector3.at(toploc.getX(), maxHeight, toploc.getZ()); 2526 regions.add(new CuboidRegion(pos1, pos2)); 2527 // intersection 2528 } 2529 } 2530 } 2531 2532 for (int y = bot.getY(); y <= top.getY(); y++) { 2533 Plot plot = this.area.getPlotAbs(PlotId.of(top.getX(), y)); 2534 if (plot.isMerged(Direction.EAST)) { 2535 // east wedge 2536 Location toploc = plot.getExtendedTopAbs(); 2537 Location botabs = plot.getBottomAbs(); 2538 Location topabs = plot.getTopAbs(); 2539 BlockVector3 pos1 = BlockVector3.at(topabs.getX() + 1, minHeight, botabs.getZ()); 2540 BlockVector3 pos2 = BlockVector3.at(toploc.getX(), maxHeight, topabs.getZ()); 2541 regions.add(new CuboidRegion(pos1, pos2)); 2542 if (plot.isMerged(Direction.SOUTHEAST)) { 2543 pos1 = BlockVector3.at(topabs.getX() + 1, minHeight, topabs.getZ() + 1); 2544 pos2 = BlockVector3.at(toploc.getX(), maxHeight, toploc.getZ()); 2545 regions.add(new CuboidRegion(pos1, pos2)); 2546 // intersection 2547 } 2548 } 2549 } 2550 BlockVector3 pos1 = BlockVector3.at(gbotabs.getX(), minHeight, gbotabs.getZ()); 2551 BlockVector3 pos2 = BlockVector3.at(gtopabs.getX(), maxHeight, gtopabs.getZ()); 2552 regions.add(new CuboidRegion(pos1, pos2)); 2553 } 2554 return regions; 2555 } 2556 2557 /** 2558 * Attempt to find the largest rectangular region in a plot (as plots can form non rectangular shapes) 2559 * 2560 * @return the plot's largest CuboidRegion 2561 */ 2562 public CuboidRegion getLargestRegion() { 2563 Set<CuboidRegion> regions = this.getRegions(); 2564 CuboidRegion max = null; 2565 double area = Double.NEGATIVE_INFINITY; 2566 for (CuboidRegion region : regions) { 2567 double current = (region.getMaximumPoint().getX() - (double) region.getMinimumPoint().getX() + 1) * ( 2568 region.getMaximumPoint().getZ() - (double) region.getMinimumPoint().getZ() + 1); 2569 if (current > area) { 2570 max = region; 2571 area = current; 2572 } 2573 } 2574 return max; 2575 } 2576 2577 /** 2578 * Do the plot entry tasks for each player in the plot<br> 2579 * - Usually called when the plot state changes (unclaimed/claimed/flag change etc) 2580 */ 2581 public void reEnter() { 2582 TaskManager.runTaskLater(() -> { 2583 for (PlotPlayer<?> pp : Plot.this.getPlayersInPlot()) { 2584 this.plotListener.plotExit(pp, Plot.this); 2585 this.plotListener.plotEntry(pp, Plot.this); 2586 } 2587 }, TaskTime.ticks(1L)); 2588 } 2589 2590 public void debug(final @NonNull String message) { 2591 try { 2592 final Collection<PlotPlayer<?>> players = PlotPlayer.getDebugModePlayersInPlot(this); 2593 if (players.isEmpty()) { 2594 return; 2595 } 2596 Caption caption = TranslatableCaption.of("debug.plot_debug"); 2597 Template plotTemplate = Template.of("plot", this.toString()); 2598 Template messageTemplate = Template.of("message", message); 2599 for (final PlotPlayer<?> player : players) { 2600 if (isOwner(player.getUUID()) || player.hasPermission(Permission.PERMISSION_ADMIN_DEBUG_OTHER)) { 2601 player.sendMessage(caption, plotTemplate, messageTemplate); 2602 } 2603 } 2604 } catch (final Exception ignored) { 2605 } 2606 } 2607 2608 /** 2609 * Teleport a player to a plot and send them the teleport message. 2610 * 2611 * @param player the player 2612 * @param result Called with the result of the teleportation 2613 */ 2614 public void teleportPlayer(final PlotPlayer<?> player, Consumer<Boolean> result) { 2615 teleportPlayer(player, TeleportCause.PLUGIN, result); 2616 } 2617 2618 /** 2619 * Teleport a player to a plot and send them the teleport message. 2620 * 2621 * @param player the player 2622 * @param cause the cause of the teleport 2623 * @param resultConsumer Called with the result of the teleportation 2624 */ 2625 public void teleportPlayer(final PlotPlayer<?> player, TeleportCause cause, Consumer<Boolean> resultConsumer) { 2626 Plot plot = this.getBasePlot(false); 2627 Result result = this.eventDispatcher.callTeleport(player, player.getLocation(), plot, cause).getEventResult(); 2628 if (result == Result.DENY) { 2629 player.sendMessage( 2630 TranslatableCaption.of("events.event_denied"), 2631 Template.of("value", "Teleport") 2632 ); 2633 resultConsumer.accept(false); 2634 return; 2635 } 2636 final Consumer<Location> locationConsumer = location -> { 2637 if (Settings.Teleport.DELAY == 0 || player.hasPermission("plots.teleport.delay.bypass")) { 2638 player.sendMessage(TranslatableCaption.of("teleport.teleported_to_plot")); 2639 player.teleport(location, cause); 2640 resultConsumer.accept(true); 2641 return; 2642 } 2643 player.sendMessage( 2644 TranslatableCaption.of("teleport.teleport_in_seconds"), 2645 Template.of("amount", String.valueOf(Settings.Teleport.DELAY)) 2646 ); 2647 final String name = player.getName(); 2648 TaskManager.addToTeleportQueue(name); 2649 TaskManager.runTaskLater(() -> { 2650 if (!TaskManager.removeFromTeleportQueue(name)) { 2651 return; 2652 } 2653 try { 2654 player.sendMessage(TranslatableCaption.of("teleport.teleported_to_plot")); 2655 player.teleport(location, cause); 2656 } catch (final Exception ignored) { 2657 } 2658 }, TaskTime.seconds(Settings.Teleport.DELAY)); 2659 resultConsumer.accept(true); 2660 }; 2661 if (this.area.isHomeAllowNonmember() || plot.isAdded(player.getUUID())) { 2662 this.getHome(locationConsumer); 2663 } else { 2664 this.getDefaultHome(false, locationConsumer); 2665 } 2666 } 2667 2668 /** 2669 * Checks if the owner of this Plot is online. 2670 * 2671 * @return {@code true} if the owner of the Plot is online 2672 */ 2673 public boolean isOnline() { 2674 if (!this.hasOwner()) { 2675 return false; 2676 } 2677 if (!isMerged()) { 2678 return PlotSquared.platform().playerManager().getPlayerIfExists(Objects.requireNonNull(this.getOwnerAbs())) != null; 2679 } 2680 for (final Plot current : getConnectedPlots()) { 2681 if (current.hasOwner() 2682 && PlotSquared 2683 .platform() 2684 .playerManager() 2685 .getPlayerIfExists(Objects.requireNonNull(current.getOwnerAbs())) != null) { 2686 return true; 2687 } 2688 } 2689 return false; 2690 } 2691 2692 public int getDistanceFromOrigin() { 2693 Location bot = getManager().getPlotBottomLocAbs(id); 2694 Location top = getManager().getPlotTopLocAbs(id); 2695 return Math.max( 2696 Math.max(Math.abs(bot.getX()), Math.abs(bot.getZ())), 2697 Math.max(Math.abs(top.getX()), Math.abs(top.getZ())) 2698 ); 2699 } 2700 2701 /** 2702 * Expands the world border to include this plot if it is beyond the current border. 2703 */ 2704 public void updateWorldBorder() { 2705 int border = this.area.getBorder(); 2706 if (border == Integer.MAX_VALUE) { 2707 return; 2708 } 2709 int max = getDistanceFromOrigin(); 2710 if (max > border) { 2711 this.area.setMeta("worldBorder", max); 2712 } 2713 } 2714 2715 /** 2716 * Merges two plots. <br>- Assumes plots are directly next to each other <br> - saves to DB 2717 * 2718 * @param lesserPlot the plot to merge into this plot instance 2719 * @param removeRoads if roads should be removed during the merge 2720 * @param queue Nullable {@link QueueCoordinator}. If null, creates own queue and enqueues, 2721 * otherwise writes to the queue but does not enqueue. 2722 */ 2723 public void mergePlot(Plot lesserPlot, boolean removeRoads, @Nullable QueueCoordinator queue) { 2724 Plot greaterPlot = this; 2725 lesserPlot.getPlotModificationManager().removeSign(); 2726 if (lesserPlot.getId().getX() == greaterPlot.getId().getX()) { 2727 if (lesserPlot.getId().getY() > greaterPlot.getId().getY()) { 2728 Plot tmp = lesserPlot; 2729 lesserPlot = greaterPlot; 2730 greaterPlot = tmp; 2731 } 2732 if (!lesserPlot.isMerged(Direction.SOUTH)) { 2733 lesserPlot.clearRatings(); 2734 greaterPlot.clearRatings(); 2735 lesserPlot.setMerged(Direction.SOUTH, true); 2736 greaterPlot.setMerged(Direction.NORTH, true); 2737 lesserPlot.mergeData(greaterPlot); 2738 if (removeRoads) { 2739 //lesserPlot.removeSign(); 2740 lesserPlot.getPlotModificationManager().removeRoadSouth(queue); 2741 Plot diagonal = greaterPlot.getRelative(Direction.EAST); 2742 if (diagonal.isMerged(Direction.NORTHWEST)) { 2743 lesserPlot.plotModificationManager.removeRoadSouthEast(queue); 2744 } 2745 Plot below = greaterPlot.getRelative(Direction.WEST); 2746 if (below.isMerged(Direction.NORTHEAST)) { 2747 below.getRelative(Direction.NORTH).plotModificationManager.removeRoadSouthEast(queue); 2748 } 2749 } 2750 } 2751 } else { 2752 if (lesserPlot.getId().getX() > greaterPlot.getId().getX()) { 2753 Plot tmp = lesserPlot; 2754 lesserPlot = greaterPlot; 2755 greaterPlot = tmp; 2756 } 2757 if (!lesserPlot.isMerged(Direction.EAST)) { 2758 lesserPlot.clearRatings(); 2759 greaterPlot.clearRatings(); 2760 lesserPlot.setMerged(Direction.EAST, true); 2761 greaterPlot.setMerged(Direction.WEST, true); 2762 lesserPlot.mergeData(greaterPlot); 2763 if (removeRoads) { 2764 //lesserPlot.removeSign(); 2765 Plot diagonal = greaterPlot.getRelative(Direction.SOUTH); 2766 if (diagonal.isMerged(Direction.NORTHWEST)) { 2767 lesserPlot.plotModificationManager.removeRoadSouthEast(queue); 2768 } 2769 lesserPlot.plotModificationManager.removeRoadEast(queue); 2770 } 2771 Plot below = greaterPlot.getRelative(Direction.NORTH); 2772 if (below.isMerged(Direction.SOUTHWEST)) { 2773 below.getRelative(Direction.WEST).getPlotModificationManager().removeRoadSouthEast(queue); 2774 } 2775 } 2776 } 2777 } 2778 2779 /** 2780 * Check if the plot is merged in a given direction 2781 * 2782 * @param direction Direction 2783 * @return {@code true} if the plot is merged in the given direction 2784 */ 2785 public boolean isMerged(final @NonNull Direction direction) { 2786 return isMerged(direction.getIndex()); 2787 } 2788 2789 /** 2790 * Get the value associated with the specified flag. This will first look at plot 2791 * specific flag values, then at the containing plot area and its default values 2792 * and at last, it will look at the default values stored in {@link GlobalFlagContainer}. 2793 * 2794 * @param flagClass The flag type (Class) 2795 * @param <T> the flag value type 2796 * @return The flag value 2797 */ 2798 public @NonNull <T> T getFlag(final @NonNull Class<? extends PlotFlag<T, ?>> flagClass) { 2799 return this.flagContainer.getFlag(flagClass).getValue(); 2800 } 2801 2802 /** 2803 * Get the value associated with the specified flag. This will first look at plot 2804 * specific flag values, then at the containing plot area and its default values 2805 * and at last, it will look at the default values stored in {@link GlobalFlagContainer}. 2806 * 2807 * @param flag The flag type (Any instance of the flag) 2808 * @param <V> the flag type (Any instance of the flag) 2809 * @param <T> the flag's value type 2810 * @return The flag value 2811 */ 2812 public @NonNull <T, V extends PlotFlag<T, ?>> T getFlag(final @NonNull V flag) { 2813 final Class<?> flagClass = flag.getClass(); 2814 final PlotFlag<?, ?> flagInstance = this.flagContainer.getFlagErased(flagClass); 2815 return FlagContainer.<T, V>castUnsafe(flagInstance).getValue(); 2816 } 2817 2818 public CompletableFuture<Caption> format(final Caption iInfo, PlotPlayer<?> player, final boolean full) { 2819 final CompletableFuture<Caption> future = new CompletableFuture<>(); 2820 int num = this.getConnectedPlots().size(); 2821 String alias = !this.getAlias().isEmpty() ? this.getAlias() : TranslatableCaption.of("info.none").getComponent(player); 2822 Location bot = this.getCorners()[0]; 2823 PlotSquared.platform().worldUtil().getBiome( 2824 Objects.requireNonNull(this.getWorldName()), 2825 bot.getX(), 2826 bot.getZ(), 2827 biome -> { 2828 Component trusted = PlayerManager.getPlayerList(this.getTrusted(), player); 2829 Component members = PlayerManager.getPlayerList(this.getMembers(), player); 2830 Component denied = PlayerManager.getPlayerList(this.getDenied(), player); 2831 String seen; 2832 if (Settings.Enabled_Components.PLOT_EXPIRY && PlotSquared.platform().expireManager() != null) { 2833 if (this.isOnline()) { 2834 seen = TranslatableCaption.of("info.now").getComponent(player); 2835 } else { 2836 int time = (int) (PlotSquared.platform().expireManager().getAge(this, false) / 1000); 2837 if (time != 0) { 2838 seen = TimeUtil.secToTime(time); 2839 } else { 2840 seen = TranslatableCaption.of("info.unknown").getComponent(player); 2841 } 2842 } 2843 } else { 2844 seen = TranslatableCaption.of("info.never").getComponent(player); 2845 } 2846 2847 String description = this.getFlag(DescriptionFlag.class); 2848 if (description.isEmpty()) { 2849 description = TranslatableCaption.of("info.plot_no_description").getComponent(player); 2850 } 2851 2852 Component flags; 2853 Collection<PlotFlag<?, ?>> flagCollection = this.getApplicableFlags(true); 2854 if (flagCollection.isEmpty()) { 2855 flags = MINI_MESSAGE.parse(TranslatableCaption.of("info.none").getComponent(player)); 2856 } else { 2857 TextComponent.Builder flagBuilder = Component.text(); 2858 String prefix = ""; 2859 for (final PlotFlag<?, ?> flag : flagCollection) { 2860 Object value; 2861 if (flag instanceof DoubleFlag && !Settings.General.SCIENTIFIC) { 2862 value = FLAG_DECIMAL_FORMAT.format(flag.getValue()); 2863 } else { 2864 value = flag.toString(); 2865 } 2866 Component snip = MINI_MESSAGE.parse( 2867 prefix + CaptionUtility.format( 2868 player, 2869 TranslatableCaption.of("info.plot_flag_list").getComponent(player) 2870 ), 2871 Template.of("flag", flag.getName()), 2872 Template.of("value", CaptionUtility.formatRaw(player, value.toString())) 2873 ); 2874 flagBuilder.append(snip); 2875 prefix = ", "; 2876 } 2877 flags = flagBuilder.build(); 2878 } 2879 boolean build = this.isAdded(player.getUUID()); 2880 Component owner; 2881 if (this.getOwner() == null) { 2882 owner = Component.text("unowned"); 2883 } else if (this.getOwner().equals(DBFunc.SERVER)) { 2884 owner = Component.text(MINI_MESSAGE.stripTokens(TranslatableCaption 2885 .of("info.server") 2886 .getComponent(player))); 2887 } else { 2888 owner = PlayerManager.getPlayerList(this.getOwners(), player); 2889 } 2890 Template headerTemplate = Template.of( 2891 "header", 2892 TranslatableCaption.of("info.plot_info_header").getComponent(player) 2893 ); 2894 Template footerTemplate = Template.of( 2895 "footer", 2896 TranslatableCaption.of("info.plot_info_footer").getComponent(player) 2897 ); 2898 Template areaTemplate; 2899 if (this.getArea() != null) { 2900 areaTemplate = 2901 Template.of( 2902 "area", 2903 this.getArea().getWorldName() + (this.getArea().getId() == null 2904 ? "" 2905 : "(" + this.getArea().getId() + ")") 2906 ); 2907 } else { 2908 areaTemplate = Template.of("area", TranslatableCaption.of("info.none").getComponent(player)); 2909 } 2910 long creationDate = Long.parseLong(String.valueOf(timestamp)); 2911 SimpleDateFormat sdf = new SimpleDateFormat(Settings.Timeformat.DATE_FORMAT); 2912 sdf.setTimeZone(TimeZone.getTimeZone(Settings.Timeformat.TIME_ZONE)); 2913 String newDate = sdf.format(creationDate); 2914 2915 Template idTemplate = Template.of("id", this.getId().toString()); 2916 Template aliasTemplate = Template.of("alias", alias); 2917 Template numTemplate = Template.of("num", String.valueOf(num)); 2918 Template descTemplate = Template.of("desc", description); 2919 Template biomeTemplate = Template.of("biome", biome.toString().toLowerCase()); 2920 Template ownerTemplate = Template.of("owner", owner); 2921 Template membersTemplate = Template.of("members", members); 2922 Template playerTemplate = Template.of("player", player.getName()); 2923 Template trustedTemplate = Template.of("trusted", trusted); 2924 Template helpersTemplate = Template.of("helpers", members); 2925 Template deniedTemplate = Template.of("denied", denied); 2926 Template seenTemplate = Template.of("seen", seen); 2927 Template flagsTemplate = Template.of("flags", flags); 2928 Template creationTemplate = Template.of("creationdate", newDate); 2929 Template buildTemplate = Template.of("build", String.valueOf(build)); 2930 Template sizeTemplate = Template.of("size", String.valueOf(getConnectedPlots().size())); 2931 String component = iInfo.getComponent(player); 2932 if (component.contains("<rating>") || component.contains("<likes>")) { 2933 TaskManager.runTaskAsync(() -> { 2934 Template ratingTemplate; 2935 Template likesTemplate; 2936 if (Settings.Ratings.USE_LIKES) { 2937 ratingTemplate = Template.of( 2938 "rating", 2939 String.format("%.0f%%", Like.getLikesPercentage(this) * 100D) 2940 ); 2941 likesTemplate = Template.of( 2942 "likes", 2943 String.format("%.0f%%", Like.getLikesPercentage(this) * 100D) 2944 ); 2945 } else { 2946 int max = 10; 2947 if (Settings.Ratings.CATEGORIES != null && !Settings.Ratings.CATEGORIES.isEmpty()) { 2948 max = 8; 2949 } 2950 if (full && Settings.Ratings.CATEGORIES != null && Settings.Ratings.CATEGORIES.size() > 1) { 2951 double[] ratings = this.getAverageRatings(); 2952 StringBuilder rating = new StringBuilder(); 2953 String prefix = ""; 2954 for (int i = 0; i < ratings.length; i++) { 2955 rating.append(prefix).append(Settings.Ratings.CATEGORIES.get(i)).append('=') 2956 .append(String.format("%.1f", ratings[i])); 2957 prefix = ","; 2958 } 2959 ratingTemplate = Template.of("rating", rating.toString()); 2960 } else { 2961 double rating = this.getAverageRating(); 2962 if (Double.isFinite(rating)) { 2963 ratingTemplate = Template.of("rating", String.format("%.1f", rating) + '/' + max); 2964 } else { 2965 ratingTemplate = Template.of( 2966 "rating", 2967 TranslatableCaption.of("info.none").getComponent(player) 2968 ); 2969 } 2970 } 2971 likesTemplate = Template.of("likes", "N/A"); 2972 } 2973 future.complete(StaticCaption.of(MINI_MESSAGE.serialize(MINI_MESSAGE 2974 .parse( 2975 iInfo.getComponent(player), 2976 headerTemplate, 2977 areaTemplate, 2978 idTemplate, 2979 aliasTemplate, 2980 numTemplate, 2981 descTemplate, 2982 biomeTemplate, 2983 ownerTemplate, 2984 membersTemplate, 2985 playerTemplate, 2986 trustedTemplate, 2987 helpersTemplate, 2988 deniedTemplate, 2989 seenTemplate, 2990 flagsTemplate, 2991 buildTemplate, 2992 ratingTemplate, 2993 creationTemplate, 2994 sizeTemplate, 2995 likesTemplate, 2996 footerTemplate 2997 )))); 2998 }); 2999 return; 3000 } 3001 future.complete(StaticCaption.of(MINI_MESSAGE.serialize(MINI_MESSAGE 3002 .parse( 3003 iInfo.getComponent(player), 3004 headerTemplate, 3005 areaTemplate, 3006 idTemplate, 3007 aliasTemplate, 3008 numTemplate, 3009 descTemplate, 3010 biomeTemplate, 3011 ownerTemplate, 3012 membersTemplate, 3013 playerTemplate, 3014 trustedTemplate, 3015 helpersTemplate, 3016 deniedTemplate, 3017 seenTemplate, 3018 flagsTemplate, 3019 buildTemplate, 3020 creationTemplate, 3021 sizeTemplate, 3022 footerTemplate 3023 )))); 3024 } 3025 ); 3026 return future; 3027 } 3028 3029 /** 3030 * If rating categories are enabled, get the average rating by category.<br> 3031 * - The index corresponds to the index of the category in the config 3032 * 3033 * <p> 3034 * See {@link Settings.Ratings#CATEGORIES} for rating categories 3035 * </p> 3036 * 3037 * @return Average ratings in each category 3038 */ 3039 public @NonNull double[] getAverageRatings() { 3040 Map<UUID, Integer> rating; 3041 if (this.getSettings().getRatings() != null) { 3042 rating = this.getSettings().getRatings(); 3043 } else if (Settings.Enabled_Components.RATING_CACHE) { 3044 rating = new HashMap<>(); 3045 } else { 3046 rating = DBFunc.getRatings(this); 3047 } 3048 int size = 1; 3049 if (!Settings.Ratings.CATEGORIES.isEmpty()) { 3050 size = Math.max(1, Settings.Ratings.CATEGORIES.size()); 3051 } 3052 double[] ratings = new double[size]; 3053 if (rating == null || rating.isEmpty()) { 3054 return ratings; 3055 } 3056 for (Entry<UUID, Integer> entry : rating.entrySet()) { 3057 int current = entry.getValue(); 3058 if (Settings.Ratings.CATEGORIES.isEmpty()) { 3059 ratings[0] += current; 3060 } else { 3061 for (int i = 0; i < Settings.Ratings.CATEGORIES.size(); i++) { 3062 ratings[i] += current % 10 - 1; 3063 current /= 10; 3064 } 3065 } 3066 } 3067 for (int i = 0; i < size; i++) { 3068 ratings[i] /= rating.size(); 3069 } 3070 return ratings; 3071 } 3072 3073 /** 3074 * Get the plot flag container 3075 * 3076 * @return Flag container 3077 */ 3078 public @NonNull FlagContainer getFlagContainer() { 3079 return this.flagContainer; 3080 } 3081 3082 /** 3083 * Get the plot comment container. This can be used to manage 3084 * and access plot comments 3085 * 3086 * @return Plot comment container 3087 */ 3088 public @NonNull PlotCommentContainer getPlotCommentContainer() { 3089 return this.plotCommentContainer; 3090 } 3091 3092 /** 3093 * Get the plot modification manager 3094 * 3095 * @return Plot modification manager 3096 */ 3097 public @NonNull PlotModificationManager getPlotModificationManager() { 3098 return this.plotModificationManager; 3099 } 3100 3101}