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