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