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