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