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