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