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