diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 06fa996c7f..6b1d6a3eeb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,7 +7,7 @@ body: attributes: value: | Thanks for taking the time to fill out this bug report for PlotSquared! Fill out the following form to your best ability to help us fix the problem. - Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/intellectualsites) or [the wiki](https://intellectualsites.github.io/plotsquared-documentation/). + Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/intellectualsites) or [the wiki](https://intellectualsites.gitbook.io/plotsquared/). Do NOT use the public issue tracker to report security vulnerabilities! They are disclosed using [this](https://github.com/IntellectualSites/PlotSquared/security/policy) GitHub form! - type: dropdown @@ -27,7 +27,7 @@ body: description: Which server version version you using? If your server version is not listed, it is not supported. Update to a supported version first. multiple: false options: - - '1.20.1' + - '1.20.2' - '1.20' - '1.19.4' - '1.19.3' diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 98e4777e67..033ae12770 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,5 +4,5 @@ contact_links: url: https://discord.gg/intellectualsites about: Our support Discord, please ask questions and seek support here. - name: PlotSquared Wiki - url: https://intellectualsites.github.io/plotsquared-documentation/ + url: https://intellectualsites.gitbook.io/plotsquared/ about: Take a look at the wiki page for instructions how to setup PlotSquared and use its commands. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 648dc10a4c..88cbea5cdd 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -7,7 +7,7 @@ body: attributes: value: | Thanks for taking the time to fill out this feature request for PlotSquared! Fill out the following form to your best ability to help us understand your feature request and greately improve the change of it getting added. - For anything else than a feature request, use: [our Discord server](https://discord.gg/intellectualsites) or [the wiki](https://intellectualsites.github.io/plotsquared-documentation/). + For anything else than a feature request, use: [our Discord server](https://discord.gg/intellectualsites) or [the wiki](https://intellectualsites.gitbook.io/plotsquared/). - type: textarea attributes: diff --git a/.github/renovate.json b/.github/renovate.json index fbd42be831..c8d280e14c 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,9 +4,16 @@ "config:base", ":semanticCommitsDisabled" ], + "automerge": true, "labels": [ "dependencies" ], "rebaseWhen": "conflicted", - "schedule": ["on the first day of the month"] + "schedule": ["on the first day of the month"], + "ignoreDeps": [ + "com.google.code.gson:gson", + "com.google.guava:guava", + "org.yaml:snakeyaml", + "org.apache.logging.log4j:log4j-api" + ] } diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 02b69cabcd..8b02519b76 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -9,7 +9,7 @@ jobs: os: [ ubuntu-latest, windows-latest, macos-latest ] steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Setup Java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a9dd532d8..d7eaf2e155 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Setup Java @@ -42,7 +42,7 @@ jobs: ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} - name: Publish core javadoc - # if: ${{ runner.os == 'Linux' && env.STATUS == 'release' && github.event_name == 'push' && github.ref == 'refs/heads/main'}} + if: ${{ runner.os == 'Linux' && env.STATUS == 'release' && github.event_name == 'push' && github.ref == 'refs/heads/main'}} uses: cpina/github-action-push-to-another-repository@main env: SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} @@ -54,7 +54,7 @@ jobs: target-branch: main target-directory: v7/core - name: Publish bukkit javadoc - # if: ${{ runner.os == 'Linux' && env.STATUS == 'release' && github.event_name == 'push' && github.ref == 'refs/heads/main'}} + if: ${{ runner.os == 'Linux' && env.STATUS == 'release' && github.event_name == 'push' && github.ref == 'refs/heads/main'}} uses: cpina/github-action-push-to-another-repository@main env: SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1499881ec3..6aafd09de0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -20,7 +20,7 @@ jobs: language: [ 'java' ] steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 with: diff --git a/.github/workflows/label-merge-conflicts.yaml b/.github/workflows/label-merge-conflicts.yaml new file mode 100644 index 0000000000..d189f55205 --- /dev/null +++ b/.github/workflows/label-merge-conflicts.yaml @@ -0,0 +1,23 @@ +name: "Label conflicting PRs" +on: + push: + pull_request_target: + types: [ synchronize ] + pull_request: + types: [ synchronize ] + +permissions: + pull-requests: write + +jobs: + main: + if: github.event.pull_request.user.login != 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Label conflicting PRs + uses: eps1lon/actions-label-merge-conflict@v2.1.0 + with: + dirtyLabel: "unresolved-merge-conflict" + repoToken: "${{ secrets.GITHUB_TOKEN }}" + commentOnDirty: "Please take a moment and address the merge conflicts of your pull request. Thanks!" + continueOnMissingPermissions: true diff --git a/.gitignore b/.gitignore index 0b17ea5b10..b2bc4a8dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -138,6 +138,5 @@ build/ .DS_Store # Ignore run folders -run-[0-0].[0-9]/ -run-[0-0].[0-9].[0-9]/ - +run-[0-9].[0-9][0-9]/ +run-[0-9].[0-9][0-9].[0-9]/ diff --git a/Bukkit/build.gradle.kts b/Bukkit/build.gradle.kts index abc3cea42c..bab7c27bf0 100644 --- a/Bukkit/build.gradle.kts +++ b/Bukkit/build.gradle.kts @@ -21,20 +21,20 @@ dependencies { api(projects.plotsquaredCore) // Metrics - implementation("org.bstats:bstats-bukkit") + implementation(libs.bstatsBukkit) // Paper - compileOnly("io.papermc.paper:paper-api") - implementation("io.papermc:paperlib") + compileOnly(libs.paper) + implementation(libs.paperlib) // Plugins compileOnly(libs.worldeditBukkit) { exclude(group = "org.bukkit") exclude(group = "org.spigotmc") } - compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Bukkit") { isTransitive = false } - testImplementation("com.fastasyncworldedit:FastAsyncWorldEdit-Bukkit") { isTransitive = false } - compileOnly("com.github.MilkBowl:VaultAPI") { + compileOnly(libs.faweBukkit) { isTransitive = false } + testImplementation(libs.faweBukkit) { isTransitive = false } + compileOnly(libs.vault) { exclude(group = "org.bukkit") } compileOnly(libs.placeholderapi) @@ -44,15 +44,15 @@ dependencies { // Other libraries implementation(libs.squirrelid) { isTransitive = false } - implementation("dev.notmyfault.serverlib:ServerLib") + implementation(libs.serverlib) // Our libraries implementation(libs.arkitektonika) - implementation("com.intellectualsites.paster:Paster") - implementation("com.intellectualsites.informative-annotations:informative-annotations") + implementation(libs.paster) + implementation(libs.informativeAnnotations) // Adventure - implementation("net.kyori:adventure-platform-bukkit") + implementation(libs.adventureBukkit) } tasks.processResources { @@ -100,7 +100,7 @@ tasks { withType { val isRelease = if (rootProject.version.toString().endsWith("-SNAPSHOT")) "TODO" else rootProject.version.toString() val opt = options as StandardJavadocDocletOptions - opt.links("https://jd.papermc.io/paper/1.19/") + opt.links("https://jd.papermc.io/paper/1.20/") opt.links("https://docs.enginehub.org/javadoc/com.sk89q.worldedit/worldedit-bukkit/" + libs.worldeditBukkit.get().versionConstraint.toString()) opt.links("https://intellectualsites.github.io/plotsquared-javadocs/core/") opt.links("https://jd.advntr.dev/api/4.14.0/") diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java index 2b05b97429..ad332176e5 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitPlatform.java @@ -37,6 +37,7 @@ import com.plotsquared.bukkit.listener.EntitySpawnListener; import com.plotsquared.bukkit.listener.PaperListener; import com.plotsquared.bukkit.listener.PlayerEventListener; +import com.plotsquared.bukkit.listener.PlayerEventListener1201; import com.plotsquared.bukkit.listener.ProjectileEventListener; import com.plotsquared.bukkit.listener.ServerListener; import com.plotsquared.bukkit.listener.SingleWorldListener; @@ -135,6 +136,7 @@ import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -252,6 +254,7 @@ public int versionMaxHeight() { } @Override + @SuppressWarnings("deprecation") // Paper deprecation public void onEnable() { this.pluginName = getDescription().getName(); @@ -357,6 +360,9 @@ public void onEnable() { if (Settings.Enabled_Components.EVENTS) { getServer().getPluginManager().registerEvents(injector().getInstance(PlayerEventListener.class), this); + if ((serverVersion()[1] == 20 && serverVersion()[2] >= 1) || serverVersion()[1] > 20) { + getServer().getPluginManager().registerEvents(injector().getInstance(PlayerEventListener1201.class), this); + } getServer().getPluginManager().registerEvents(injector().getInstance(BlockEventListener.class), this); if (serverVersion()[1] >= 17) { getServer().getPluginManager().registerEvents(injector().getInstance(BlockEventListener117.class), this); @@ -550,7 +556,7 @@ public void onEnable() { this.startMetrics(); if (Settings.Enabled_Components.WORLDS) { - TaskManager.getPlatformImplementation().taskRepeat(this::unload, TaskTime.seconds(1L)); + TaskManager.getPlatformImplementation().taskRepeat(this::unload, TaskTime.seconds(10L)); try { singleWorldListener = injector().getInstance(SingleWorldListener.class); Bukkit.getPluginManager().registerEvents(singleWorldListener, this); @@ -812,6 +818,7 @@ private void runEntityTask() { case "MINECART_MOB_SPAWNER": case "ENDER_CRYSTAL": case "MINECART_TNT": + case "CHEST_BOAT": case "BOAT": if (Settings.Enabled_Components.KILL_ROAD_VEHICLES) { com.plotsquared.core.location.Location location = BukkitUtil.adapt(entity.getLocation()); @@ -1160,6 +1167,7 @@ public void setGenerator(final @NonNull String worldName) { return new BukkitPlotGenerator(world, generator, this.plotAreaManager); } + @SuppressWarnings("deprecation") // Paper deprecation @Override public @NonNull String pluginsFormatted() { StringBuilder msg = new StringBuilder(); @@ -1176,12 +1184,23 @@ public void setGenerator(final @NonNull String worldName) { .append(" • Load Before: ").append(p.getDescription().getLoadBefore()).append("\n") .append(" • Dependencies: ").append(p.getDescription().getDepend()).append("\n") .append(" • Soft Dependencies: ").append(p.getDescription().getSoftDepend()).append("\n"); + List> providers = Bukkit.getServicesManager().getRegistrations(p); + if (!providers.isEmpty()) { + msg.append(" • Provided Services: \n"); + for (RegisteredServiceProvider provider : providers) { + msg.append(" • ") + .append(provider.getService().getName()).append(" = ") + .append(provider.getProvider().getClass().getName()) + .append(" (priority: ").append(provider.getPriority()).append(")") + .append("\n"); + } + } } return msg.toString(); } @Override - @SuppressWarnings("ConstantConditions") + @SuppressWarnings({"ConstantConditions", "deprecation"}) // Paper deprecation public @NonNull String worldEditImplementations() { StringBuilder msg = new StringBuilder(); if (Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null) { diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/entity/ReplicatingEntityWrapper.java b/Bukkit/src/main/java/com/plotsquared/bukkit/entity/ReplicatingEntityWrapper.java index 183710a8e5..43202f34d5 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/entity/ReplicatingEntityWrapper.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/entity/ReplicatingEntityWrapper.java @@ -33,6 +33,7 @@ import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Bat; import org.bukkit.entity.Boat; +import org.bukkit.entity.Breedable; import org.bukkit.entity.ChestedHorse; import org.bukkit.entity.EnderDragon; import org.bukkit.entity.Entity; @@ -74,6 +75,7 @@ public final class ReplicatingEntityWrapper extends EntityWrapper { private HorseStats horse; private boolean noGravity; + @SuppressWarnings("deprecation") // Deprecation exists since 1.20, while we support 1.16 onwards public ReplicatingEntityWrapper(Entity entity, short depth) { super(entity); @@ -164,7 +166,7 @@ public ReplicatingEntityWrapper(Entity entity, short depth) { //this.horse.style = horse.getStyle(); //this.horse.color = horse.getColor(); storeTameable(horse); - storeAgeable(horse); + storeBreedable(horse); storeLiving(horse); storeInventory(horse); return; @@ -172,7 +174,7 @@ public ReplicatingEntityWrapper(Entity entity, short depth) { // END INVENTORY HOLDER // case "WOLF", "OCELOT" -> { storeTameable((Tameable) entity); - storeAgeable((Ageable) entity); + storeBreedable((Breedable) entity); storeLiving((LivingEntity) entity); return; } @@ -186,18 +188,18 @@ public ReplicatingEntityWrapper(Entity entity, short depth) { this.dataByte = (byte) 0; } this.dataByte2 = sheep.getColor().getDyeData(); - storeAgeable(sheep); + storeBreedable(sheep); storeLiving(sheep); return; } case "VILLAGER", "CHICKEN", "COW", "MUSHROOM_COW", "PIG", "TURTLE", "POLAR_BEAR" -> { - storeAgeable((Ageable) entity); + storeBreedable((Breedable) entity); storeLiving((LivingEntity) entity); return; } case "RABBIT" -> { this.dataByte = getOrdinal(Rabbit.Type.values(), ((Rabbit) entity).getRabbitType()); - storeAgeable((Ageable) entity); + storeBreedable((Breedable) entity); storeLiving((LivingEntity) entity); return; } @@ -381,6 +383,11 @@ private void restoreTameable(Tameable entity) { } } + /** + * @deprecated Use {@link #restoreBreedable(Breedable)} instead + * @since 7.1.0 + */ + @Deprecated(forRemoval = true, since = "7.1.0") private void restoreAgeable(Ageable entity) { if (!this.aged.adult) { entity.setBaby(); @@ -391,6 +398,11 @@ private void restoreAgeable(Ageable entity) { } } + /** + * @deprecated Use {@link #storeBreedable(Breedable)} instead + * @since 7.1.0 + */ + @Deprecated(forRemoval = true, since = "7.1.0") public void storeAgeable(Ageable aged) { this.aged = new AgeableStats(); this.aged.age = aged.getAge(); @@ -398,6 +410,29 @@ public void storeAgeable(Ageable aged) { this.aged.adult = aged.isAdult(); } + /** + * @since 7.1.0 + */ + private void restoreBreedable(Breedable entity) { + if (!this.aged.adult) { + entity.setBaby(); + } + entity.setAgeLock(this.aged.locked); + if (this.aged.age > 0) { + entity.setAge(this.aged.age); + } + } + + /** + * @since 7.1.0 + */ + private void storeBreedable(Breedable breedable) { + this.aged = new AgeableStats(); + this.aged.age = breedable.getAge(); + this.aged.locked = breedable.getAgeLock(); + this.aged.adult = breedable.isAdult(); + } + public void storeTameable(Tameable tamed) { this.tamed = new TameableStats(); this.tamed.owner = tamed.getOwner(); @@ -501,7 +536,7 @@ public Entity spawn(World world, int xOffset, int zOffset) { //horse.setStyle(this.horse.style); //horse.setColor(this.horse.color); restoreTameable(horse); - restoreAgeable(horse); + restoreBreedable(horse); restoreLiving(horse); restoreInventory(horse); return entity; @@ -509,7 +544,7 @@ public Entity spawn(World world, int xOffset, int zOffset) { // END INVENTORY HOLDER // case "WOLF", "OCELOT" -> { restoreTameable((Tameable) entity); - restoreAgeable((Ageable) entity); + restoreBreedable((Breedable) entity); restoreLiving((LivingEntity) entity); return entity; } @@ -522,12 +557,12 @@ public Entity spawn(World world, int xOffset, int zOffset) { if (this.dataByte2 != 0) { sheep.setColor(DyeColor.getByDyeData(this.dataByte2)); } - restoreAgeable(sheep); + restoreBreedable(sheep); restoreLiving(sheep); return sheep; } case "VILLAGER", "CHICKEN", "COW", "TURTLE", "POLAR_BEAR", "MUSHROOM_COW", "PIG" -> { - restoreAgeable((Ageable) entity); + restoreBreedable((Breedable) entity); restoreLiving((LivingEntity) entity); return entity; } @@ -536,7 +571,7 @@ public Entity spawn(World world, int xOffset, int zOffset) { if (this.dataByte != 0) { ((Rabbit) entity).setRabbitType(Rabbit.Type.values()[this.dataByte]); } - restoreAgeable((Ageable) entity); + restoreBreedable((Breedable) entity); restoreLiving((LivingEntity) entity); return entity; } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java index 6d7e2accbf..bfccaeb3d0 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/generator/BukkitPlotGenerator.java @@ -34,6 +34,7 @@ import com.plotsquared.core.util.ChunkManager; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.HeightMap; @@ -420,7 +421,11 @@ private synchronized PlotArea getPlotArea(String name, int chunkX, int chunkZ) { if (lastPlotArea != null && name.equals(this.levelName) && chunkX == lastChunkX && chunkZ == lastChunkZ) { return lastPlotArea; } - PlotArea area = UncheckedWorldLocation.at(name, chunkX << 4, 0, chunkZ << 4).getPlotArea(); + BlockVector3 loc = BlockVector3.at(chunkX << 4, 0, chunkZ << 4); + if (lastPlotArea != null && lastPlotArea.getRegion().contains(loc) && lastPlotArea.getRegion().contains(loc)) { + return lastPlotArea; + } + PlotArea area = UncheckedWorldLocation.at(name, loc).getPlotArea(); if (area == null) { throw new IllegalStateException(String.format( "Cannot generate chunk that does not belong to a plot area. World: %s", diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/inject/BukkitModule.java b/Bukkit/src/main/java/com/plotsquared/bukkit/inject/BukkitModule.java index 0b6810785d..fba7790f0e 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/inject/BukkitModule.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/inject/BukkitModule.java @@ -23,13 +23,13 @@ import com.google.inject.Singleton; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.plotsquared.bukkit.BukkitPlatform; +import com.plotsquared.bukkit.listener.ServerListener; import com.plotsquared.bukkit.listener.SingleWorldListener; import com.plotsquared.bukkit.player.BukkitPlayerManager; import com.plotsquared.bukkit.queue.BukkitChunkCoordinator; import com.plotsquared.bukkit.queue.BukkitQueueCoordinator; import com.plotsquared.bukkit.schematic.BukkitSchematicHandler; import com.plotsquared.bukkit.util.BukkitChunkManager; -import com.plotsquared.bukkit.util.BukkitEconHandler; import com.plotsquared.bukkit.util.BukkitInventoryUtil; import com.plotsquared.bukkit.util.BukkitRegionManager; import com.plotsquared.bukkit.util.BukkitSetupUtils; @@ -47,6 +47,9 @@ import com.plotsquared.core.inject.factory.ChunkCoordinatorFactory; import com.plotsquared.core.inject.factory.HybridPlotWorldFactory; import com.plotsquared.core.inject.factory.ProgressSubscriberFactory; +import com.plotsquared.core.player.OfflinePlotPlayer; +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.world.DefaultPlotAreaManager; import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.plot.world.SinglePlotAreaManager; @@ -72,6 +75,8 @@ import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Objects; + public class BukkitModule extends AbstractModule { private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + BukkitModule.class.getSimpleName()); @@ -128,21 +133,64 @@ protected void configure() { @Provides @Singleton @NonNull EconHandler provideEconHandler() { - if (!Settings.Enabled_Components.ECONOMY) { + if (!Settings.Enabled_Components.ECONOMY || !Bukkit.getPluginManager().isPluginEnabled("Vault")) { return EconHandler.nullEconHandler(); } - if (Bukkit.getPluginManager().isPluginEnabled("Vault")) { - try { - BukkitEconHandler econHandler = new BukkitEconHandler(); - if (!econHandler.init()) { - LOGGER.warn("Economy is enabled but no plugin is providing an economy service. Falling back..."); - return EconHandler.nullEconHandler(); - } - return econHandler; - } catch (final Exception ignored) { - } + // Guice eagerly initializes singletons, so we need to bring the laziness ourselves + return new LazyEconHandler(); + } + + private static final class LazyEconHandler extends EconHandler implements ServerListener.MutableEconHandler { + private volatile EconHandler implementation; + + public void setImplementation(EconHandler econHandler) { + this.implementation = econHandler; } - return EconHandler.nullEconHandler(); + + @Override + public boolean init() { + return get().init(); + } + + @Override + public double getBalance(final PlotPlayer player) { + return get().getBalance(player); + } + + @Override + public void withdrawMoney(final PlotPlayer player, final double amount) { + get().withdrawMoney(player, amount); + } + + @Override + public void depositMoney(final PlotPlayer player, final double amount) { + get().depositMoney(player, amount); + } + + @Override + public void depositMoney(final OfflinePlotPlayer player, final double amount) { + get().depositMoney(player, amount); + } + + @Override + public boolean isEnabled(final PlotArea plotArea) { + return get().isEnabled(plotArea); + } + + @Override + public @NonNull String format(final double balance) { + return get().format(balance); + } + + @Override + public boolean isSupported() { + return get().isSupported(); + } + + private EconHandler get() { + return Objects.requireNonNull(this.implementation, "EconHandler not ready yet."); + } + } } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener.java index 1d87b283ae..52e8ba623f 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener.java @@ -33,6 +33,7 @@ import com.plotsquared.core.plot.flag.implementations.BlockBurnFlag; import com.plotsquared.core.plot.flag.implementations.BlockIgnitionFlag; import com.plotsquared.core.plot.flag.implementations.BreakFlag; +import com.plotsquared.core.plot.flag.implementations.ConcreteHardenFlag; import com.plotsquared.core.plot.flag.implementations.CoralDryFlag; import com.plotsquared.core.plot.flag.implementations.CropGrowFlag; import com.plotsquared.core.plot.flag.implementations.DisablePhysicsFlag; @@ -586,6 +587,12 @@ public void onBlockForm(BlockFormEvent event) { event.setCancelled(true); } } + if (event.getNewState().getType().toString().endsWith("CONCRETE")) { + if (!plot.getFlag(ConcreteHardenFlag.class)) { + plot.debug("Concrete powder could not harden because concrete-harden = false"); + event.setCancelled(true); + } + } } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @@ -1116,6 +1123,7 @@ public void onBigBoom(BlockExplodeEvent event) { if (plot != null) { plot.debug("Explosion was cancelled because explosion = false"); } + return; } event.blockList().removeIf(blox -> !plot.equals(area.getOwnedPlot(BukkitUtil.adapt(blox.getLocation())))); } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java index 34a69adc7d..0909001b42 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/BlockEventListener117.java @@ -26,6 +26,7 @@ import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.flag.implementations.CopperOxideFlag; import com.plotsquared.core.plot.flag.implementations.MiscInteractFlag; +import com.plotsquared.core.plot.flag.implementations.SculkSensorInteractFlag; import com.plotsquared.core.util.PlotFlagUtil; import org.bukkit.Material; import org.bukkit.block.Block; @@ -96,12 +97,12 @@ public void onBlockReceiveGame(BlockReceiveGameEvent event) { area, MiscInteractFlag.class, true - ) || plot != null && !plot.getFlag( - MiscInteractFlag.class)) { + ) || plot != null && (!plot.getFlag(MiscInteractFlag.class) || !plot.getFlag(SculkSensorInteractFlag.class))) { if (plotPlayer != null) { if (plot != null) { if (!plot.isAdded(plotPlayer.getUUID())) { - plot.debug(plotPlayer.getName() + " couldn't trigger sculk sensors because misc-interact = false"); + plot.debug(plotPlayer.getName() + " couldn't trigger sculk sensors because both " + + "sculk-sensor-interact and misc-interact = false"); event.setCancelled(true); } } @@ -112,13 +113,15 @@ public void onBlockReceiveGame(BlockReceiveGameEvent event) { if (plot != null) { if (itemThrower == null && (itemThrower = item.getOwner()) == null) { plot.debug( - "A thrown item couldn't trigger sculk sensors because misc-interact = false and the item's owner could not be resolved."); + "A thrown item couldn't trigger sculk sensors because both sculk-sensor-interact and " + + "misc-interact = false and the item's owner could not be resolved."); event.setCancelled(true); return; } if (!plot.isAdded(itemThrower)) { if (!plot.isAdded(itemThrower)) { - plot.debug("A thrown item couldn't trigger sculk sensors because misc-interact = false"); + plot.debug("A thrown item couldn't trigger sculk sensors because both sculk-sensor-interact and " + + "misc-interact = false"); event.setCancelled(true); } } @@ -137,7 +140,6 @@ public void onBlockFertilize(BlockFertilizeEvent event) { if (area == null) { for (int i = blocks.size() - 1; i >= 0; i--) { Location blockLocation = BukkitUtil.adapt(blocks.get(i).getLocation()); - blockLocation = BukkitUtil.adapt(blocks.get(i).getLocation()); if (blockLocation.isPlotArea()) { blocks.remove(i); } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java index 9af09527c9..763263e991 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ChunkListener.java @@ -26,6 +26,7 @@ import com.plotsquared.core.plot.PlotArea; import com.plotsquared.core.plot.world.PlotAreaManager; import com.plotsquared.core.plot.world.SinglePlotArea; +import com.plotsquared.core.util.ReflectionUtils; import com.plotsquared.core.util.ReflectionUtils.RefClass; import com.plotsquared.core.util.ReflectionUtils.RefField; import com.plotsquared.core.util.ReflectionUtils.RefMethod; @@ -64,9 +65,11 @@ public class ChunkListener implements Listener { private final PlotAreaManager plotAreaManager; private final int version; + private RefMethod methodSetUnsaved; private RefMethod methodGetHandleChunk; private RefMethod methodGetHandleWorld; - private RefField mustSave; + private RefField mustNotSave; + private Object objChunkStatusFull = null; /* private RefMethod methodGetFullChunk; private RefMethod methodGetBukkitChunk; @@ -79,7 +82,6 @@ public class ChunkListener implements Listener { */ private Chunk lastChunk; private boolean ignoreUnload = false; - private boolean isTrueForNotSave = true; @Inject public ChunkListener(final @NonNull PlotAreaManager plotAreaManager) { @@ -90,22 +92,27 @@ public ChunkListener(final @NonNull PlotAreaManager plotAreaManager) { } try { RefClass classCraftWorld = getRefClass("{cb}.CraftWorld"); - this.methodGetHandleWorld = classCraftWorld.getMethod("getHandle"); RefClass classCraftChunk = getRefClass("{cb}.CraftChunk"); - this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle"); + ReflectionUtils.RefClass classChunkAccess = getRefClass("net.minecraft.world.level.chunk.IChunkAccess"); + this.methodSetUnsaved = classChunkAccess.getMethod("a", boolean.class); + try { + this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle"); + } catch (NoSuchMethodException ignored) { + try { + RefClass classChunkStatus = getRefClass("net.minecraft.world.level.chunk.ChunkStatus"); + this.objChunkStatusFull = classChunkStatus.getRealClass().getField("n").get(null); + this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle", classChunkStatus.getRealClass()); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex); + } + } try { if (version < 17) { RefClass classChunk = getRefClass("{nms}.Chunk"); - if (version == 13) { - this.mustSave = classChunk.getField("mustSave"); - this.isTrueForNotSave = false; - } else { - this.mustSave = classChunk.getField("mustNotSave"); - } + this.mustNotSave = classChunk.getField("mustNotSave"); } else { RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.Chunk"); - this.mustSave = classChunk.getField("mustNotSave"); - + this.mustNotSave = classChunk.getField("mustNotSave"); } } catch (NoSuchFieldException e) { e.printStackTrace(); @@ -167,10 +174,13 @@ public boolean unloadChunk(String world, Chunk chunk, boolean safe) { if (safe && shouldSave(world, chunk.getX(), chunk.getZ())) { return false; } - Object c = this.methodGetHandleChunk.of(chunk).call(); - RefField.RefExecutor field = this.mustSave.of(c); - if ((Boolean) field.get() != isTrueForNotSave) { - field.set(isTrueForNotSave); + Object c = objChunkStatusFull != null + ? this.methodGetHandleChunk.of(chunk).call(objChunkStatusFull) + : this.methodGetHandleChunk.of(chunk).call(); + RefField.RefExecutor field = this.mustNotSave.of(c); + methodSetUnsaved.of(c).call(false); + if (!((Boolean) field.get())) { + field.set(true); if (chunk.isLoaded()) { ignoreUnload = true; chunk.unload(false); diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java index 5ca02d00f2..783eacd2b6 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/EntityEventListener.java @@ -143,6 +143,10 @@ public void creatureSpawnEvent(CreatureSpawnEvent event) { if (area == null) { return; } + // Armour-stands are handled elsewhere and should not be handled by area-wide entity-spawn options + if (entity.getType() == EntityType.ARMOR_STAND) { + return; + } CreatureSpawnEvent.SpawnReason reason = event.getSpawnReason(); switch (reason.toString()) { case "DISPENSE_EGG", "EGG", "OCELOT_BABY", "SPAWNER_EGG" -> { @@ -152,7 +156,8 @@ public void creatureSpawnEvent(CreatureSpawnEvent event) { } } case "REINFORCEMENTS", "NATURAL", "MOUNT", "PATROL", "RAID", "SHEARED", "SILVERFISH_BLOCK", "ENDER_PEARL", - "TRAP", "VILLAGE_DEFENSE", "VILLAGE_INVASION", "BEEHIVE", "CHUNK_GEN" -> { + "TRAP", "VILLAGE_DEFENSE", "VILLAGE_INVASION", "BEEHIVE", "CHUNK_GEN", "NETHER_PORTAL", + "DUPLICATION", "FROZEN", "SPELL", "DEFAULT" -> { if (!area.isMobSpawning()) { event.setCancelled(true); return; @@ -165,7 +170,7 @@ public void creatureSpawnEvent(CreatureSpawnEvent event) { } } case "BUILD_IRONGOLEM", "BUILD_SNOWMAN", "BUILD_WITHER", "CUSTOM" -> { - if (!area.isSpawnCustom() && entity.getType() != EntityType.ARMOR_STAND) { + if (!area.isSpawnCustom()) { event.setCancelled(true); return; } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java index 0e058e9a17..6bd528d3bb 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PaperListener.java @@ -124,7 +124,7 @@ public void onEntityPathfind(SlimePathfindEvent event) { } Slime slime = event.getEntity(); - Block b = slime.getTargetBlock(4); + Block b = slime.getTargetBlockExact(4); if (b == null) { return; } @@ -166,12 +166,16 @@ public void onPreCreatureSpawnEvent(PreCreatureSpawnEvent event) { } Location location = BukkitUtil.adapt(event.getSpawnLocation()); PlotArea area = location.getPlotArea(); - if (!location.isPlotArea()) { + if (area == null) { + return; + } + // Armour-stands are handled elsewhere and should not be handled by area-wide entity-spawn options + if (event.getType() == EntityType.ARMOR_STAND) { return; } - //If entities are spawning... the chunk should be loaded? + // If entities are spawning... the chunk should be loaded? Entity[] entities = event.getSpawnLocation().getChunk().getEntities(); - if (entities.length > Settings.Chunk_Processor.MAX_ENTITIES) { + if (entities.length >= Settings.Chunk_Processor.MAX_ENTITIES) { event.setShouldAbortSpawn(true); event.setCancelled(true); return; @@ -200,7 +204,7 @@ public void onPreCreatureSpawnEvent(PreCreatureSpawnEvent event) { } } case "BUILD_IRONGOLEM", "BUILD_SNOWMAN", "BUILD_WITHER", "CUSTOM" -> { - if (!area.isSpawnCustom() && event.getType() != EntityType.ARMOR_STAND) { + if (!area.isSpawnCustom()) { event.setShouldAbortSpawn(true); event.setCancelled(true); return; diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java index 9beaa2ad95..964a03d3f0 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener.java @@ -369,6 +369,7 @@ public void onPreLogin(final AsyncPlayerPreLoginEvent event) { } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + @SuppressWarnings("deprecation") // Paper deprecation public void onConnect(PlayerJoinEvent event) { final Player player = event.getPlayer(); PlotSquared.platform().playerManager().removePlayer(player.getUniqueId()); @@ -607,7 +608,7 @@ public void playerMove(PlayerMoveEvent event) { this.tmpTeleport = true; return; } - int border = area.getBorder(); + int border = area.getBorder(true); int x1; if (x2 > border && this.tmpTeleport) { if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) { @@ -702,7 +703,7 @@ public void playerMove(PlayerMoveEvent event) { this.tmpTeleport = true; return; } - int border = area.getBorder(); + int border = area.getBorder(true); int z1; if (z2 > border && this.tmpTeleport) { if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) { @@ -733,6 +734,7 @@ public void playerMove(PlayerMoveEvent event) { } @EventHandler(priority = EventPriority.LOW) + @SuppressWarnings("deprecation") // Paper deprecation public void onChat(AsyncPlayerChatEvent event) { if (event.isCancelled()) { return; @@ -1063,6 +1065,7 @@ public void onInteract(PlayerInteractAtEntityEvent e) { } @EventHandler(priority = EventPriority.LOW) + @SuppressWarnings("deprecation") // Paper deprecation public void onCancelledInteract(PlayerInteractEvent event) { if (event.isCancelled() && event.getAction() == Action.RIGHT_CLICK_AIR) { Player player = event.getPlayer(); @@ -1167,7 +1170,7 @@ public void onInteract(PlayerInteractEvent event) { } } if (type.isEdible()) { - //Allow all players to eat while also allowing the block place event ot be fired + //Allow all players to eat while also allowing the block place event to be fired return; } if (type == Material.ARMOR_STAND) { diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener1201.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener1201.java new file mode 100644 index 0000000000..4dab1198b8 --- /dev/null +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/PlayerEventListener1201.java @@ -0,0 +1,63 @@ +/* + * PlotSquared, a land and world management plugin for Minecraft. + * Copyright (C) IntellectualSites + * Copyright (C) IntellectualSites team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.bukkit.listener; + +import com.plotsquared.bukkit.util.BukkitUtil; +import com.plotsquared.core.location.Location; +import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.plot.PlotArea; +import com.plotsquared.core.plot.flag.implementations.EditSignFlag; +import com.plotsquared.core.util.PlotFlagUtil; +import org.bukkit.block.Sign; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerSignOpenEvent; + +/** + * For events since 1.20.1 + * @since TODO + */ +public class PlayerEventListener1201 implements Listener { + + @EventHandler(ignoreCancelled = true) + @SuppressWarnings({"removal", "UnstableApiUsage"}) // thanks Paper, thanks Spigot + public void onPlayerSignOpenEvent(PlayerSignOpenEvent event) { + Sign sign = event.getSign(); + Location location = BukkitUtil.adapt(sign.getLocation()); + PlotArea area = location.getPlotArea(); + if (area == null) { + return; + } + Plot plot = location.getOwnedPlot(); + if (plot == null) { + if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, EditSignFlag.class, false)) { + event.setCancelled(true); + } + return; + } + if (plot.isAdded(event.getPlayer().getUniqueId())) { + return; // allow for added players + } + if (!plot.getFlag(EditSignFlag.class)) { + plot.debug(event.getPlayer().getName() + " could not edit the sign because of edit-sign = false"); + event.setCancelled(true); + } + } + +} diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ServerListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ServerListener.java index 6e25ebe145..7ad0337ae8 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ServerListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/ServerListener.java @@ -21,9 +21,14 @@ import com.google.inject.Inject; import com.plotsquared.bukkit.BukkitPlatform; import com.plotsquared.bukkit.placeholder.MVdWPlaceholders; +import com.plotsquared.bukkit.util.BukkitEconHandler; +import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.player.ConsolePlayer; +import com.plotsquared.core.util.EconHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -32,6 +37,8 @@ public class ServerListener implements Listener { + private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + ServerListener.class.getSimpleName()); + private final BukkitPlatform plugin; @Inject @@ -45,6 +52,29 @@ public void onServerLoad(ServerLoadEvent event) { new MVdWPlaceholders(this.plugin, this.plugin.placeholderRegistry()); ConsolePlayer.getConsole().sendMessage(TranslatableCaption.of("placeholder.hooked")); } + if (Settings.Enabled_Components.ECONOMY && Bukkit.getPluginManager().isPluginEnabled("Vault")) { + EconHandler econHandler = new BukkitEconHandler(); + try { + if (!econHandler.init()) { + LOGGER.warn("Economy is enabled but no plugin is providing an economy service. Falling back..."); + econHandler = EconHandler.nullEconHandler(); + } + } catch (final Exception ignored) { + econHandler = EconHandler.nullEconHandler(); + } + if (PlotSquared.platform().econHandler() instanceof MutableEconHandler meh) { + meh.setImplementation(econHandler); + } + } + } + + /** + * Internal use only. Required to implement lazy econ loading using Guice. + * + * @since 7.2.0 + */ + public interface MutableEconHandler { + void setImplementation(EconHandler econHandler); } } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java index deb14ed7ba..34184659ed 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/listener/SingleWorldListener.java @@ -31,45 +31,39 @@ import org.bukkit.event.world.ChunkEvent; import org.bukkit.event.world.ChunkLoadEvent; -import java.lang.reflect.Field; import java.lang.reflect.Method; import static com.plotsquared.core.util.ReflectionUtils.getRefClass; public class SingleWorldListener implements Listener { - private final Method methodGetHandleChunk; - private Field shouldSave = null; + private final Method methodSetUnsaved; + private Method methodGetHandleChunk; + private Object objChunkStatusFull = null; public SingleWorldListener() throws Exception { ReflectionUtils.RefClass classCraftChunk = getRefClass("{cb}.CraftChunk"); - this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle").getRealMethod(); + ReflectionUtils.RefClass classChunkAccess = getRefClass("net.minecraft.world.level.chunk.IChunkAccess"); + this.methodSetUnsaved = classChunkAccess.getMethod("a", boolean.class).getRealMethod(); try { - if (PlotSquared.platform().serverVersion()[1] < 17) { - ReflectionUtils.RefClass classChunk = getRefClass("{nms}.Chunk"); - if (PlotSquared.platform().serverVersion()[1] == 13) { - this.shouldSave = classChunk.getField("mustSave").getRealField(); - } else { - this.shouldSave = classChunk.getField("s").getRealField(); - } - } else if (PlotSquared.platform().serverVersion()[1] == 17) { - ReflectionUtils.RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.Chunk"); - this.shouldSave = classChunk.getField("r").getRealField(); - } else if (PlotSquared.platform().serverVersion()[1] == 18) { - ReflectionUtils.RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.IChunkAccess"); - this.shouldSave = classChunk.getField("b").getRealField(); + this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle").getRealMethod(); + } catch (NoSuchMethodException ignored) { + try { + ReflectionUtils.RefClass classChunkStatus = getRefClass("net.minecraft.world.level.chunk.ChunkStatus"); + this.objChunkStatusFull = classChunkStatus.getRealClass().getField("n").get(null); + this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle", classChunkStatus.getRealClass()).getRealMethod(); + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex); } - } catch (NoSuchFieldException e) { - e.printStackTrace(); } } public void markChunkAsClean(Chunk chunk) { try { - Object nmsChunk = methodGetHandleChunk.invoke(chunk); - if (shouldSave != null) { - this.shouldSave.set(nmsChunk, false); - } + Object nmsChunk = objChunkStatusFull != null + ? this.methodGetHandleChunk.invoke(chunk, objChunkStatusFull) + : this.methodGetHandleChunk.invoke(chunk); + methodSetUnsaved.invoke(nmsChunk, false); } catch (Throwable e) { e.printStackTrace(); } @@ -85,7 +79,12 @@ private void handle(ChunkEvent event) { if (!SinglePlotArea.isSinglePlotWorld(name)) { return; } - + int x = event.getChunk().getX(); + int z = event.getChunk().getZ(); + if (x < 16 && x > -16 && z < 16 && z > -16) { + // Allow spawn to generate + return; + } markChunkAsClean(event.getChunk()); } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java b/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java index d86d48c7d7..66e935a179 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/player/BukkitPlayer.java @@ -158,6 +158,7 @@ public int hasPermissionRange( } final String[] nodes = stub.split("\\."); final StringBuilder n = new StringBuilder(); + // Wildcard check from less specific permission to more specific permission for (int i = 0; i < (nodes.length - 1); i++) { n.append(nodes[i]).append("."); if (!stub.equals(n + Permission.PERMISSION_STAR.toString())) { @@ -166,9 +167,11 @@ public int hasPermissionRange( } } } + // Wildcard check for the full permission if (hasPermission(stub + ".*")) { return Integer.MAX_VALUE; } + // Permission value cache for iterative check int max = 0; if (CHECK_EFFECTIVE) { boolean hasAny = false; diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitInventoryUtil.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitInventoryUtil.java index 3d2565fc52..c13675e430 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitInventoryUtil.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitInventoryUtil.java @@ -44,6 +44,7 @@ @Singleton public class BukkitInventoryUtil extends InventoryUtil { + @SuppressWarnings("deprecation") // Paper deprecation private static @Nullable ItemStack getItem(PlotItemStack item) { if (item == null) { return null; diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitSetupUtils.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitSetupUtils.java index 88ede2cc5e..5ce91a6675 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitSetupUtils.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/BukkitSetupUtils.java @@ -67,6 +67,7 @@ public BukkitSetupUtils( this.worldFile = worldFile; } + @SuppressWarnings("deprecation") // Paper deprecation @Override public void updateGenerators(final boolean force) { if (loaded && !SetupUtils.generators.isEmpty() && !force) { diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/TranslationUpdateManager.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/TranslationUpdateManager.java index 71466eb8ce..2e499c6c9c 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/TranslationUpdateManager.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/TranslationUpdateManager.java @@ -28,7 +28,7 @@ import java.util.stream.Stream; /** - * This is a helper class which replaces occurrences of 'suggest_command' with 'run_command' in messages_%.json. + * This is a helper class which replaces older syntax no longer supported by MiniMessage with replacements in messages_%.json. * MiniMessage changed the syntax between major releases. To warrant a smooth upgrade, we attempt to replace any occurrences * while loading PlotSquared. * @@ -38,14 +38,28 @@ public class TranslationUpdateManager { public static void upgradeTranslationFile() throws IOException { - String searchText = "suggest_command"; - String replacementText = "run_command"; + String suggestCommand = "suggest_command"; + String suggestCommandReplacement = "run_command"; + String minHeight = "minHeight"; + String minheightReplacement = "minheight"; + String maxHeight = "maxHeight"; + String maxheightReplacement = "maxheight"; + String usedGrants = "usedGrants"; + String usedGrantsReplacement = "used_grants"; + String remainingGrants = "remainingGrants"; + String rremainingGrantsReplacement = "remaining_grants"; try (Stream paths = Files.walk(Paths.get(PlotSquared.platform().getDirectory().toPath().resolve("lang").toUri()))) { paths .filter(Files::isRegularFile) .filter(p -> p.getFileName().toString().matches("messages_[a-z]{2}\\.json")) - .forEach(p -> replaceInFile(p, searchText, replacementText)); + .forEach(p -> { + replaceInFile(p, suggestCommand, suggestCommandReplacement); + replaceInFile(p, minHeight, minheightReplacement); + replaceInFile(p, maxHeight, maxheightReplacement); + replaceInFile(p, usedGrants, usedGrantsReplacement); + replaceInFile(p, remainingGrants, rremainingGrantsReplacement); + }); } } diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/util/UpdateUtility.java b/Bukkit/src/main/java/com/plotsquared/bukkit/util/UpdateUtility.java index 5e1e4c985a..2cffa291b3 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/util/UpdateUtility.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/util/UpdateUtility.java @@ -35,7 +35,7 @@ import javax.net.ssl.HttpsURLConnection; import java.io.IOException; import java.io.InputStreamReader; -import java.net.URL; +import java.net.URI; public class UpdateUtility implements Listener { @@ -59,8 +59,9 @@ public UpdateUtility(final JavaPlugin javaPlugin) { public void updateChecker() { task = Bukkit.getScheduler().runTaskTimerAsynchronously(this.javaPlugin, () -> { try { - HttpsURLConnection connection = (HttpsURLConnection) new URL( - "https://api.spigotmc.org/simple/0.1/index.php?action=getResource&id=77506") + HttpsURLConnection connection = (HttpsURLConnection) URI.create( + "https://api.spigotmc.org/simple/0.2/index.php?action=getResource&id=77506") + .toURL() .openConnection(); connection.setRequestMethod("GET"); JsonObject result = new JsonParser() diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 742b2c651a..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at contactintellectualsites.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/Core/build.gradle.kts b/Core/build.gradle.kts index c2ed27b559..b8914c6eb0 100644 --- a/Core/build.gradle.kts +++ b/Core/build.gradle.kts @@ -2,18 +2,18 @@ import java.time.format.DateTimeFormatter dependencies { // Expected everywhere. - compileOnlyApi("org.checkerframework:checker-qual") + compileOnlyApi(libs.checkerqual) // Minecraft expectations - compileOnlyApi("com.google.code.gson:gson") - compileOnly("com.google.guava:guava") + compileOnlyApi(libs.gson) + compileOnly(libs.guava) // Platform expectations - compileOnlyApi("org.yaml:snakeyaml") + compileOnlyApi(libs.snakeyaml) // Adventure - api("net.kyori:adventure-api") - api("net.kyori:adventure-text-minimessage") + api(libs.adventureApi) + api(libs.adventureMiniMessage) // Guice api(libs.guice) { @@ -31,19 +31,19 @@ dependencies { exclude(group = "dummypermscompat") } testImplementation(libs.worldeditCore) - compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core") { isTransitive = false } - testImplementation("com.fastasyncworldedit:FastAsyncWorldEdit-Core") { isTransitive = false } + compileOnly(libs.faweBukkit) { isTransitive = false } + testImplementation(libs.faweCore) { isTransitive = false } // Logging - compileOnlyApi("org.apache.logging.log4j:log4j-api") + compileOnlyApi(libs.log4j) // Other libraries api(libs.prtree) api(libs.aopalliance) api(libs.cloudServices) api(libs.arkitektonika) - api("com.intellectualsites.paster:Paster") - api("com.intellectualsites.informative-annotations:informative-annotations") + api(libs.paster) + api(libs.informativeAnnotations) // Database implementation("com.zaxxer:HikariCP:5.0.1") @@ -60,8 +60,8 @@ tasks.processResources { doLast { copy { - from(File("$rootDir/LICENSE")) - into("$buildDir/resources/main/") + from(layout.buildDirectory.file("$rootDir/LICENSE")) + into(layout.buildDirectory.dir("resources/main")) } } } @@ -75,7 +75,6 @@ tasks { opt.links("https://jd.advntr.dev/text-minimessage/4.14.0/") opt.links("https://google.github.io/guice/api-docs/" + libs.guice.get().versionConstraint.toString() + "/javadoc/") opt.links("https://checkerframework.org/api/") - opt.links("https://javadoc.io/doc/com.intellectualsites.informative-annotations/informative-annotations/latest/") opt.isLinkSource = true opt.bottom(File("$rootDir/javadocfooter.html").readText()) opt.isUse = true diff --git a/Core/src/main/java/com/plotsquared/core/PlotSquared.java b/Core/src/main/java/com/plotsquared/core/PlotSquared.java index 579abe1e8f..702de9e68d 100644 --- a/Core/src/main/java/com/plotsquared/core/PlotSquared.java +++ b/Core/src/main/java/com/plotsquared/core/PlotSquared.java @@ -84,7 +84,7 @@ import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; @@ -210,9 +210,10 @@ public PlotSquared( try { URL logurl = PlotSquared.class.getProtectionDomain().getCodeSource().getLocation(); this.jarFile = new File( - new URL(logurl.toURI().toString().split("\\!")[0].replaceAll("jar:file", "file")) - .toURI().getPath()); - } catch (MalformedURLException | URISyntaxException | SecurityException e) { + URI.create( + logurl.toURI().toString().split("\\!")[0].replaceAll("jar:file", "file")) + .getPath()); + } catch (URISyntaxException | SecurityException e) { e.printStackTrace(); this.jarFile = new File(this.platform.getDirectory().getParentFile(), "PlotSquared.jar"); if (!this.jarFile.exists()) { @@ -1281,7 +1282,7 @@ private void checkRoadRegenPersistence() { } /** - * Setup the database connection. + * Set up the database connection. */ public void setupDatabase() { try { diff --git a/Core/src/main/java/com/plotsquared/core/command/Add.java b/Core/src/main/java/com/plotsquared/core/command/Add.java index 45db5366d9..02a7124d24 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Add.java +++ b/Core/src/main/java/com/plotsquared/core/command/Add.java @@ -19,6 +19,7 @@ package com.plotsquared.core.command; import com.google.inject.Inject; +import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.database.DBFunc; @@ -101,9 +102,14 @@ public CompletableFuture execute( Permission.PERMISSION_ADMIN_COMMAND_TRUST))) { player.sendMessage( TranslatableCaption.of("errors.invalid_player"), - TagResolver.resolver("value", Tag.inserting( - PlayerManager.resolveName(uuid).toComponent(player) - )) + PlotSquared + .platform() + .playerManager() + .getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( + "value", + Tag.inserting(caption.toComponent(player)) + )) ); iterator.remove(); continue; @@ -111,9 +117,11 @@ public CompletableFuture execute( if (plot.isOwner(uuid)) { player.sendMessage( TranslatableCaption.of("member.already_added"), - TagResolver.resolver("player", Tag.inserting( - PlayerManager.resolveName(uuid).toComponent(player) - )) + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( + "player", + Tag.inserting(caption.toComponent(player)) + )) ); iterator.remove(); continue; @@ -121,9 +129,11 @@ public CompletableFuture execute( if (plot.getMembers().contains(uuid)) { player.sendMessage( TranslatableCaption.of("member.already_added"), - TagResolver.resolver("player", Tag.inserting( - PlayerManager.resolveName(uuid).toComponent(player) - )) + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( + "player", + Tag.inserting(caption.toComponent(player)) + )) ); iterator.remove(); continue; diff --git a/Core/src/main/java/com/plotsquared/core/command/Auto.java b/Core/src/main/java/com/plotsquared/core/command/Auto.java index 2490af9818..230ed9e3aa 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Auto.java +++ b/Core/src/main/java/com/plotsquared/core/command/Auto.java @@ -131,8 +131,8 @@ public static boolean checkAllowedPlots( player.sendMessage( TranslatableCaption.of("economy.removed_granted_plot"), TagResolver.builder() - .tag("usedGrants", Tag.inserting(Component.text(grantedPlots - left))) - .tag("remainingGrants", Tag.inserting(Component.text(left))) + .tag("used_grants", Tag.inserting(Component.text(grantedPlots - left))) + .tag("remaining_grants", Tag.inserting(Component.text(left))) .build() ); } @@ -294,11 +294,11 @@ public boolean onCommand(final PlotPlayer player, String[] args) { return true; } } - if (this.econHandler != null && plotarea.useEconomy()) { + if (this.econHandler != null && plotarea.useEconomy() && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { PlotExpression costExp = plotarea.getPrices().get("claim"); PlotExpression mergeCostExp = plotarea.getPrices().get("merge"); int size = sizeX * sizeZ; - double mergeCost = size > 1 && mergeCostExp == null ? 0d : mergeCostExp.evaluate(size); + double mergeCost = size <= 1 || mergeCostExp == null ? 0d : mergeCostExp.evaluate(size); double cost = costExp.evaluate(Settings.Limit.GLOBAL ? player.getPlotCount() : player.getPlotCount(plotarea.getWorldName())); diff --git a/Core/src/main/java/com/plotsquared/core/command/Claim.java b/Core/src/main/java/com/plotsquared/core/command/Claim.java index e9848685c2..c118d4cb00 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Claim.java +++ b/Core/src/main/java/com/plotsquared/core/command/Claim.java @@ -141,7 +141,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { } } } - if (this.econHandler.isEnabled(area) && !force) { + if (this.econHandler.isEnabled(area) && !force && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { PlotExpression costExr = area.getPrices().get("claim"); double cost = costExr.evaluate(currentPlots); if (cost > 0d) { @@ -186,14 +186,14 @@ public boolean onCommand(final PlotPlayer player, String[] args) { player.sendMessage( TranslatableCaption.of("economy.removed_granted_plot"), TagResolver.builder() - .tag("usedGrants", Tag.inserting(Component.text(grants - 1))) - .tag("remainingGrants", Tag.inserting(Component.text(grants))) + .tag("used_grants", Tag.inserting(Component.text(grants - 1))) + .tag("remaining_grants", Tag.inserting(Component.text(grants))) .build() ); } } if (!player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_BORDER)) { - int border = area.getBorder(); + int border = area.getBorder(false); if (border != Integer.MAX_VALUE && plot.getDistanceFromOrigin() > border && !force) { player.sendMessage(TranslatableCaption.of("border.denied")); return false; diff --git a/Core/src/main/java/com/plotsquared/core/command/CommandCategory.java b/Core/src/main/java/com/plotsquared/core/command/CommandCategory.java index a769385776..c30ec82413 100644 --- a/Core/src/main/java/com/plotsquared/core/command/CommandCategory.java +++ b/Core/src/main/java/com/plotsquared/core/command/CommandCategory.java @@ -25,6 +25,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; /** * CommandCategory. @@ -82,7 +83,7 @@ public enum CommandCategory implements Caption { // TODO this method shouldn't be invoked @Deprecated @Override - public String toString() { + public @NotNull String toString() { return this.caption.getComponent(LocaleHolder.console()); } @@ -108,4 +109,5 @@ boolean canAccess(PlotPlayer player) { return !MainCommand.getInstance().getCommands(this, player).isEmpty(); } + } diff --git a/Core/src/main/java/com/plotsquared/core/command/Debug.java b/Core/src/main/java/com/plotsquared/core/command/Debug.java index 1e0090e44f..d4c8864885 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Debug.java +++ b/Core/src/main/java/com/plotsquared/core/command/Debug.java @@ -29,7 +29,6 @@ import com.plotsquared.core.util.entity.EntityCategories; import com.plotsquared.core.util.entity.EntityCategory; import com.plotsquared.core.util.query.PlotQuery; -import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.uuid.UUIDMapping; import com.sk89q.worldedit.world.entity.EntityType; import net.kyori.adventure.text.Component; @@ -71,7 +70,7 @@ public boolean onCommand(PlotPlayer player, String[] args) { TranslatableCaption.of("commandconfig.command_syntax"), TagResolver.resolver( "value", - Tag.inserting(Component.text("/plot debug ")) + Tag.inserting(Component.text("/plot debug ")) ) ); } @@ -85,16 +84,6 @@ public boolean onCommand(PlotPlayer player, String[] args) { return true; } } - if (args.length > 0 && "loadedchunks".equalsIgnoreCase(args[0])) { - final long start = System.currentTimeMillis(); - player.sendMessage(TranslatableCaption.of("debug.fetching_loaded_chunks")); - TaskManager.runTaskAsync(() -> player.sendMessage(StaticCaption - .of("Loaded chunks: " + this.worldUtil - .getChunkChunks(player.getLocation().getWorldName()) - .size() + " (" + (System.currentTimeMillis() - - start) + "ms) using thread: " + Thread.currentThread().getName()))); - return true; - } if (args.length > 0 && "uuids".equalsIgnoreCase(args[0])) { final Collection mappings = PlotSquared.get().getImpromptuUUIDPipeline().getAllImmediately(); player.sendMessage( @@ -196,7 +185,7 @@ public boolean onCommand(PlotPlayer player, String[] args) { @Override public Collection tab(final PlotPlayer player, String[] args, boolean space) { - return Stream.of("loadedchunks", "debug-players", "entitytypes") + return Stream.of("debug-players", "entitytypes") .filter(value -> value.startsWith(args[0].toLowerCase(Locale.ENGLISH))) .map(value -> new Command(null, false, value, "plots.admin", RequiredType.NONE, null) { }).collect(Collectors.toList()); diff --git a/Core/src/main/java/com/plotsquared/core/command/DebugRoadRegen.java b/Core/src/main/java/com/plotsquared/core/command/DebugRoadRegen.java index 4534949941..df6bd11d6c 100644 --- a/Core/src/main/java/com/plotsquared/core/command/DebugRoadRegen.java +++ b/Core/src/main/java/com/plotsquared/core/command/DebugRoadRegen.java @@ -96,6 +96,7 @@ public boolean regenPlot(PlotPlayer player) { PlotArea area = location.getPlotArea(); if (area == null) { player.sendMessage(TranslatableCaption.of("errors.not_in_plot_world")); + return false; } Plot plot = player.getCurrentPlot(); if (plot == null) { diff --git a/Core/src/main/java/com/plotsquared/core/command/Deny.java b/Core/src/main/java/com/plotsquared/core/command/Deny.java index ec3811a944..56e7c2c8af 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Deny.java +++ b/Core/src/main/java/com/plotsquared/core/command/Deny.java @@ -117,10 +117,11 @@ public boolean onCommand(PlotPlayer player, String[] args) { } else if (plot.getDenied().contains(uuid)) { player.sendMessage( TranslatableCaption.of("member.already_added"), - TagResolver.resolver( + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( "player", - Tag.inserting(PlayerManager.resolveName(uuid).toComponent(player)) - ) + Tag.inserting(caption.toComponent(player)) + )) ); return; } else { diff --git a/Core/src/main/java/com/plotsquared/core/command/Download.java b/Core/src/main/java/com/plotsquared/core/command/Download.java index 1de860586a..eb02ce2262 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Download.java +++ b/Core/src/main/java/com/plotsquared/core/command/Download.java @@ -136,7 +136,9 @@ public void run(URL url) { } player.sendMessage( TranslatableCaption.of("web.generation_link_success_legacy_world"), - TagResolver.resolver("url", Tag.inserting(Component.text(url.toString()))) + TagResolver.builder() + .tag("url", Tag.preProcessParsed(url.toString())) + .build() ); } }); diff --git a/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java b/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java index 9ab6ad3408..7987bfb037 100644 --- a/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java +++ b/Core/src/main/java/com/plotsquared/core/command/FlagCommand.java @@ -103,9 +103,10 @@ private static boolean checkPermValue( if (flag instanceof IntegerFlag && MathMan.isInteger(value)) { try { int numeric = Integer.parseInt(value); + // Getting full permission without "." at the end perm = perm.substring(0, perm.length() - value.length() - 1); boolean result = false; - if (numeric > 0) { + if (numeric >= 0) { int checkRange = PlotSquared.get().getPlatform().equalsIgnoreCase("bukkit") ? numeric : Settings.Limit.MAX_PLOTS; diff --git a/Core/src/main/java/com/plotsquared/core/command/Kick.java b/Core/src/main/java/com/plotsquared/core/command/Kick.java index a9de323e8b..cf91f9ad29 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Kick.java +++ b/Core/src/main/java/com/plotsquared/core/command/Kick.java @@ -112,15 +112,15 @@ public boolean onCommand(PlotPlayer player, String[] args) { for (PlotPlayer player2 : players) { if (!plot.equals(player2.getCurrentPlot())) { player.sendMessage( - TranslatableCaption.of("errors.invalid_player"), - TagResolver.resolver("value", Tag.inserting(Component.text(args[0]))) + TranslatableCaption.of("kick.player_not_in_plot"), + TagResolver.resolver("player", Tag.inserting(Component.text(player2.getName()))) ); return; } if (player2.hasPermission(Permission.PERMISSION_ADMIN_ENTRY_DENIED)) { player.sendMessage( - TranslatableCaption.of("cluster.cannot_kick_player"), - TagResolver.resolver("name", Tag.inserting(Component.text(player2.getName()))) + TranslatableCaption.of("kick.cannot_kick_player"), + TagResolver.resolver("player", Tag.inserting(Component.text(player2.getName()))) ); return; } diff --git a/Core/src/main/java/com/plotsquared/core/command/ListCmd.java b/Core/src/main/java/com/plotsquared/core/command/ListCmd.java index 5ba5d1a009..de7378c2d8 100644 --- a/Core/src/main/java/com/plotsquared/core/command/ListCmd.java +++ b/Core/src/main/java/com/plotsquared/core/command/ListCmd.java @@ -517,6 +517,7 @@ public void run(Integer i, Plot plot, CaptionHolder caption) { } } finalResolver.tag("players", Tag.inserting(builder.asComponent())); + finalResolver.tag("size", Tag.inserting(Component.text(plot.getConnectedPlots().size()))); caption.set(TranslatableCaption.of("info.plot_list_item")); caption.setTagResolvers(finalResolver.build()); } diff --git a/Core/src/main/java/com/plotsquared/core/command/Load.java b/Core/src/main/java/com/plotsquared/core/command/Load.java index 4ead54146b..14c700d4c3 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Load.java +++ b/Core/src/main/java/com/plotsquared/core/command/Load.java @@ -41,6 +41,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.util.Collections; import java.util.List; @@ -116,7 +117,7 @@ public boolean onCommand(final PlotPlayer player, final String[] args) { } final URL url; try { - url = new URL(Settings.Web.URL + "saves/" + player.getUUID() + '/' + schematic); + url = URI.create(Settings.Web.URL + "saves/" + player.getUUID() + '/' + schematic).toURL(); } catch (MalformedURLException e) { e.printStackTrace(); player.sendMessage(TranslatableCaption.of("web.load_failed")); diff --git a/Core/src/main/java/com/plotsquared/core/command/MainCommand.java b/Core/src/main/java/com/plotsquared/core/command/MainCommand.java index 1911f24e94..721bec05f3 100644 --- a/Core/src/main/java/com/plotsquared/core/command/MainCommand.java +++ b/Core/src/main/java/com/plotsquared/core/command/MainCommand.java @@ -183,7 +183,7 @@ public void run(final Command cmd, final Runnable success, final Runnable failur if (cmd.hasConfirmation(player)) { CmdConfirm.addPending(player, cmd.getUsage(), () -> { PlotArea area = player.getApplicablePlotArea(); - if (area != null && econHandler.isEnabled(area)) { + if (area != null && econHandler.isEnabled(area) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { PlotExpression priceEval = area.getPrices().get(cmd.getFullId()); double price = priceEval != null ? priceEval.evaluate(0d) : 0d; @@ -201,7 +201,7 @@ public void run(final Command cmd, final Runnable success, final Runnable failur return; } PlotArea area = player.getApplicablePlotArea(); - if (area != null && econHandler.isEnabled(area)) { + if (area != null && econHandler.isEnabled(area) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { PlotExpression priceEval = area.getPrices().get(cmd.getFullId()); double price = priceEval != null ? priceEval.evaluate(0d) : 0d; if (price != 0d && econHandler.getMoney(player) < price) { diff --git a/Core/src/main/java/com/plotsquared/core/command/Merge.java b/Core/src/main/java/com/plotsquared/core/command/Merge.java index 24e8426454..0e06508364 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Merge.java +++ b/Core/src/main/java/com/plotsquared/core/command/Merge.java @@ -109,7 +109,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { } } if (direction == null && (args[0].equalsIgnoreCase("all") || args[0] - .equalsIgnoreCase("auto"))) { + .equalsIgnoreCase("auto")) && player.hasPermission(Permission.PERMISSION_MERGE_ALL)) { direction = Direction.ALL; } } @@ -178,7 +178,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { return true; } if (plot.getPlotModificationManager().autoMerge(Direction.ALL, maxSize, uuid, player, terrain)) { - if (this.econHandler.isEnabled(plotArea) && price > 0d) { + if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { this.econHandler.withdrawMoney(player, price); player.sendMessage( TranslatableCaption.of("economy.removed_balance"), @@ -196,8 +196,8 @@ public boolean onCommand(final PlotPlayer player, String[] args) { player.sendMessage(TranslatableCaption.of("merge.no_available_automerge")); return false; } - if (!force && this.econHandler.isEnabled(plotArea) && price > 0d - && this.econHandler.getMoney(player) < price) { + if (!force && this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d && this.econHandler.getMoney( + player) < price) { player.sendMessage( TranslatableCaption.of("economy.cannot_afford_merge"), TagResolver.resolver("money", Tag.inserting(Component.text(this.econHandler.format(price)))) @@ -218,7 +218,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { return true; } if (plot.getPlotModificationManager().autoMerge(direction, maxSize - size, uuid, player, terrain)) { - if (this.econHandler.isEnabled(plotArea) && price > 0d) { + if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { this.econHandler.withdrawMoney(player, price); player.sendMessage( TranslatableCaption.of("economy.removed_balance"), @@ -259,7 +259,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { accepter.sendMessage(TranslatableCaption.of("merge.merge_not_valid")); return; } - if (this.econHandler.isEnabled(plotArea) && price > 0d) { + if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { if (!force && this.econHandler.getMoney(player) < price) { player.sendMessage( TranslatableCaption.of("economy.cannot_afford_merge"), @@ -303,7 +303,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { player, terrain )) { - if (this.econHandler.isEnabled(plotArea) && price > 0d) { + if (this.econHandler.isEnabled(plotArea) && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON) && price > 0d) { if (!force && this.econHandler.getMoney(player) < price) { player.sendMessage( TranslatableCaption.of("economy.cannot_afford_merge"), diff --git a/Core/src/main/java/com/plotsquared/core/command/Owner.java b/Core/src/main/java/com/plotsquared/core/command/Owner.java index ee3c32eec3..362b176544 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Owner.java +++ b/Core/src/main/java/com/plotsquared/core/command/Owner.java @@ -31,7 +31,6 @@ import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.Plot; import com.plotsquared.core.util.EventDispatcher; -import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.TabCompletions; import com.plotsquared.core.util.task.TaskManager; import net.kyori.adventure.text.Component; @@ -136,10 +135,11 @@ public boolean set(final PlotPlayer player, final Plot plot, String value) { if (plot.isOwner(uuid)) { player.sendMessage( TranslatableCaption.of("member.already_owner"), - TagResolver.resolver( + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( "player", - Tag.inserting(PlayerManager.resolveName(uuid, false).toComponent(player)) - ) + Tag.inserting(caption.toComponent(player)) + )) ); return; } @@ -147,10 +147,11 @@ public boolean set(final PlotPlayer player, final Plot plot, String value) { if (other == null) { player.sendMessage( TranslatableCaption.of("errors.invalid_player_offline"), - TagResolver.resolver( + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( "player", - Tag.inserting(PlayerManager.resolveName(uuid).toComponent(player)) - ) + Tag.inserting(caption.toComponent(player)) + )) ); return; } diff --git a/Core/src/main/java/com/plotsquared/core/command/PluginCmd.java b/Core/src/main/java/com/plotsquared/core/command/PluginCmd.java index be78475b54..b25d56493c 100644 --- a/Core/src/main/java/com/plotsquared/core/command/PluginCmd.java +++ b/Core/src/main/java/com/plotsquared/core/command/PluginCmd.java @@ -46,7 +46,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { player.sendMessage(StaticCaption.of( ">> Authors: Citymonstret & Empire92 & MattBDev & dordsor21 & NotMyFault & SirYwell")); player.sendMessage(StaticCaption.of( - ">> Wiki: https://intellectualsites.github.io/plotsquared-documentation/")); + ">> Wiki: https://intellectualsites.gitbook.io/plotsquared/")); player.sendMessage(StaticCaption.of( ">> Discord: https://discord.gg/intellectualsites")); player.sendMessage( diff --git a/Core/src/main/java/com/plotsquared/core/command/Remove.java b/Core/src/main/java/com/plotsquared/core/command/Remove.java index ec936f9c7c..d62898b568 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Remove.java +++ b/Core/src/main/java/com/plotsquared/core/command/Remove.java @@ -100,15 +100,17 @@ public boolean onCommand(PlotPlayer player, String[] args) { count++; } } else if (uuid == DBFunc.EVERYONE) { + count += plot.getTrusted().size(); if (plot.removeTrusted(uuid)) { this.eventDispatcher.callTrusted(player, plot, uuid, false); - count++; - } else if (plot.removeMember(uuid)) { + } + count += plot.getMembers().size(); + if (plot.removeMember(uuid)) { this.eventDispatcher.callMember(player, plot, uuid, false); - count++; - } else if (plot.removeDenied(uuid)) { + } + count += plot.getDenied().size(); + if (plot.removeDenied(uuid)) { this.eventDispatcher.callDenied(player, plot, uuid, false); - count++; } } } diff --git a/Core/src/main/java/com/plotsquared/core/command/SchematicCmd.java b/Core/src/main/java/com/plotsquared/core/command/SchematicCmd.java index 613f2eec76..a92f06c5ae 100644 --- a/Core/src/main/java/com/plotsquared/core/command/SchematicCmd.java +++ b/Core/src/main/java/com/plotsquared/core/command/SchematicCmd.java @@ -40,6 +40,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.checkerframework.checker.nullness.qual.NonNull; +import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -130,8 +131,7 @@ public boolean onCommand(final PlotPlayer player, String[] args) { if (location.startsWith("url:")) { try { UUID uuid = UUID.fromString(location.substring(4)); - URL base = new URL(Settings.Web.URL); - URL url = new URL(base, "uploads/" + uuid + ".schematic"); + URL url = URI.create(Settings.Web.URL + "uploads/" + uuid + ".schematic").toURL(); schematic = this.schematicHandler.getSchematic(url); } catch (Exception e) { e.printStackTrace(); diff --git a/Core/src/main/java/com/plotsquared/core/command/Trust.java b/Core/src/main/java/com/plotsquared/core/command/Trust.java index 9ad0d6e499..23d306ad39 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Trust.java +++ b/Core/src/main/java/com/plotsquared/core/command/Trust.java @@ -19,6 +19,7 @@ package com.plotsquared.core.command; import com.google.inject.Inject; +import com.plotsquared.core.PlotSquared; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.database.DBFunc; @@ -103,10 +104,11 @@ public CompletableFuture execute( player.hasPermission(Permission.PERMISSION_TRUST_EVERYONE) || player.hasPermission(Permission.PERMISSION_ADMIN_COMMAND_TRUST))) { player.sendMessage( TranslatableCaption.of("errors.invalid_player"), - TagResolver.resolver( + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( "value", - Tag.inserting(PlayerManager.resolveName(uuid).toComponent(player)) - ) + Tag.inserting(caption.toComponent(player)) + )) ); iterator.remove(); continue; @@ -114,10 +116,11 @@ public CompletableFuture execute( if (currentPlot.isOwner(uuid)) { player.sendMessage( TranslatableCaption.of("member.already_added"), - TagResolver.resolver( - "value", - Tag.inserting(PlayerManager.resolveName(uuid).toComponent(player)) - ) + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( + "player", + Tag.inserting(caption.toComponent(player)) + )) ); iterator.remove(); continue; @@ -125,10 +128,11 @@ public CompletableFuture execute( if (currentPlot.getTrusted().contains(uuid)) { player.sendMessage( TranslatableCaption.of("member.already_added"), - TagResolver.resolver( - "value", - Tag.inserting(PlayerManager.resolveName(uuid).toComponent(player)) - ) + PlotSquared.platform().playerManager().getUsernameCaption(uuid) + .thenApply(caption -> TagResolver.resolver( + "player", + Tag.inserting(caption.toComponent(player)) + )) ); iterator.remove(); continue; diff --git a/Core/src/main/java/com/plotsquared/core/components/ComponentPresetManager.java b/Core/src/main/java/com/plotsquared/core/components/ComponentPresetManager.java index fd8e5b5ab8..fb76aef42f 100644 --- a/Core/src/main/java/com/plotsquared/core/components/ComponentPresetManager.java +++ b/Core/src/main/java/com/plotsquared/core/components/ComponentPresetManager.java @@ -206,7 +206,7 @@ public boolean onClick(final int index) { return false; } - if (componentPreset.cost() > 0.0D) { + if (componentPreset.cost() > 0.0D && !player.hasPermission(Permission.PERMISSION_ADMIN_BYPASS_ECON)) { if (!econHandler.isEnabled(plot.getArea())) { getPlayer().sendMessage( TranslatableCaption.of("preset.economy_disabled"), diff --git a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java index 025c779ee3..03ac33e78a 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java @@ -194,7 +194,7 @@ public static final class Auto_Clear extends ConfigBlock { public List WORLDS = new ArrayList<>(Collections.singletonList("*")); - @Comment("See: https://intellectualsites.github.io/plotsquared-documentation/optimization/plot-analysis for a description of each value.") + @Comment("See: https://intellectualsites.gitbook.io/plotsquared/optimization/plot-analysis for a description of each value.") public static final class CALIBRATION { public int VARIETY = 0; @@ -214,7 +214,7 @@ public static final class CALIBRATION { @Comment({"Chunk processor related settings", - "See https://intellectualsites.github.io/plotsquared-documentation/optimization/chunk-processor for more information."}) + "See https://intellectualsites.gitbook.io/plotsquared/optimization/chunk-processor for more information."}) public static class Chunk_Processor { @Comment("Auto trim will not save chunks which aren't claimed") @@ -280,7 +280,7 @@ public static final class General { @Comment("Always show explosion Particles, even if explosion flag is set to false") public static boolean ALWAYS_SHOW_EXPLOSIONS = false; @Comment({"Blocks that may not be used in plot components", - "Checkout the wiki article regarding plot components before modifying: https://intellectualsites.github.io/plotsquared-documentation/customization/plot-components"}) + "Checkout the wiki article regarding plot components before modifying: https://intellectualsites.gitbook.io/plotsquared/customization/plot-components"}) public static List INVALID_BLOCKS = Arrays.asList( // Acacia Stuff @@ -402,7 +402,7 @@ public static final class UpdateChecker { @Comment({"Schematic Settings", - "See https://intellectualsites.github.io/plotsquared-documentation/schematics/schematic-on-claim for more information."}) + "See https://intellectualsites.gitbook.io/plotsquared/schematics/schematic-on-claim for more information."}) public static final class Schematics { @Comment( @@ -522,7 +522,7 @@ public static final class Limit { @Comment("Should the limit be global (over multiple worlds)") public static boolean GLOBAL = false; - @Comment({"The max range of permissions to check for, e.g. plots.plot.127", + @Comment({"The max range of integer permissions to check for, e.g. 'plots.plot.127' or 'plots.set.flag.mob-cap.127'", "The value covers the permission range to check, you need to assign the permission to players/groups still", "Modifying the value does NOT change the amount of plots players can claim"}) public static int MAX_PLOTS = 127; @@ -531,7 +531,7 @@ public static final class Limit { @Comment({"Backup related settings", - "See https://intellectualsites.github.io/plotsquared-documentation/plot-backups for more information."}) + "See https://intellectualsites.gitbook.io/plotsquared/plot-backups for more information."}) public static final class Backup { @Comment("Automatically backup plots when destructive commands are performed, e.g. /plot clear") @@ -783,7 +783,7 @@ public static final class Enabled_Components { // Group the following values int public static boolean PERSISTENT_ROAD_REGEN = true; @Comment({"Enable the `/plot component` preset GUI", - "Read more about components here: https://intellectualsites.github.io/plotsquared-documentation/customization/plot-components"}) + "Read more about components here: https://intellectualsites.gitbook.io/plotsquared/customization/plot-components"}) public static boolean COMPONENT_PRESETS = true; @Comment("Enable per user locale") public static boolean PER_USER_LOCALE = false; diff --git a/Core/src/main/java/com/plotsquared/core/configuration/caption/Caption.java b/Core/src/main/java/com/plotsquared/core/configuration/caption/Caption.java index 21da701ab1..02155a4e92 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/caption/Caption.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/caption/Caption.java @@ -44,4 +44,6 @@ public interface Caption { */ @NonNull Component toComponent(@NonNull LocaleHolder localeHolder); + @NonNull String toString(); + } diff --git a/Core/src/main/java/com/plotsquared/core/configuration/caption/StaticCaption.java b/Core/src/main/java/com/plotsquared/core/configuration/caption/StaticCaption.java index e7f9a122d3..88e69d14f5 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/caption/StaticCaption.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/caption/StaticCaption.java @@ -51,4 +51,9 @@ private StaticCaption(final String value) { return MiniMessage.miniMessage().deserialize(this.value); } + @Override + public @NonNull String toString() { + return "StaticCaption(" + value + ")"; + } + } diff --git a/Core/src/main/java/com/plotsquared/core/configuration/caption/TranslatableCaption.java b/Core/src/main/java/com/plotsquared/core/configuration/caption/TranslatableCaption.java index bb3db5182c..18a2c9fcaf 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/caption/TranslatableCaption.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/caption/TranslatableCaption.java @@ -25,6 +25,7 @@ import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.NotNull; import java.util.Locale; import java.util.regex.Pattern; @@ -132,4 +133,9 @@ public int hashCode() { return Objects.hashCode(this.getNamespace(), this.getKey()); } + @Override + public @NotNull String toString() { + return "TranslatableCaption(" + getNamespace() + ":" + getKey() + ")"; + } + } diff --git a/Core/src/main/java/com/plotsquared/core/database/SQLManager.java b/Core/src/main/java/com/plotsquared/core/database/SQLManager.java index b4bcc60838..ebd8dfe998 100644 --- a/Core/src/main/java/com/plotsquared/core/database/SQLManager.java +++ b/Core/src/main/java/com/plotsquared/core/database/SQLManager.java @@ -128,6 +128,9 @@ public class SQLManager implements AbstractDB { * cluster_settings */ public volatile ConcurrentHashMap> clusterTasks; + // Private + private Connection connection; + private boolean supportsGetGeneratedKeys; private boolean closed = false; /** @@ -151,6 +154,9 @@ public SQLManager( this.plotListener = plotListener; this.worldConfiguration = worldConfiguration; this.database = database; + this.connection = database.openConnection(); + final DatabaseMetaData databaseMetaData = this.connection.getMetaData(); + this.supportsGetGeneratedKeys = databaseMetaData.supportsGetGeneratedKeys(); this.mySQL = database instanceof MySQL; this.globalTasks = new ConcurrentLinkedQueue<>(); this.notifyTasks = new ConcurrentLinkedQueue<>(); @@ -158,6 +164,14 @@ public SQLManager( this.playerTasks = new ConcurrentHashMap<>(); this.clusterTasks = new ConcurrentHashMap<>(); this.prefix = prefix; + + if (mySQL && !supportsGetGeneratedKeys) { + String driver = databaseMetaData.getDriverName(); + String driverVersion = databaseMetaData.getDriverVersion(); + throw new SQLException("Database Driver for MySQL does not support Statement#getGeneratedKeys - which breaks " + + "PlotSquared functionality (Using " + driver + ":" + driverVersion + ")"); + } + this.SET_OWNER = "UPDATE `" + this.prefix + "plot` SET `owner` = ? WHERE `plot_id_x` = ? AND `plot_id_z` = ? AND `world` = ?"; this.GET_ALL_PLOTS = @@ -168,20 +182,32 @@ public SQLManager( "INSERT INTO `" + this.prefix + "plot_settings` (`plot_plot_id`) values "; this.CREATE_TIERS = "INSERT INTO `" + this.prefix + "plot_%tier%` (`plot_plot_id`, `user_uuid`) values "; - this.CREATE_PLOT = "INSERT INTO `" + this.prefix + String tempCreatePlot = "INSERT INTO `" + this.prefix + "plot`(`plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp`) VALUES(?, ?, ?, ?, ?)"; - + if (!supportsGetGeneratedKeys) { + tempCreatePlot += " RETURNING `id`"; + } + this.CREATE_PLOT = tempCreatePlot; if (mySQL) { this.CREATE_PLOT_SAFE = "INSERT IGNORE INTO `" + this.prefix + "plot`(`plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp`) SELECT ?, ?, ?, ?, ? FROM DUAL WHERE NOT EXISTS (SELECT null FROM `" + this.prefix + "plot` WHERE `world` = ? AND `plot_id_x` = ? AND `plot_id_z` = ?)"; } else { - this.CREATE_PLOT_SAFE = "INSERT INTO `" + this.prefix + String tempCreatePlotSafe = "INSERT INTO `" + this.prefix + "plot`(`plot_id_x`, `plot_id_z`, `owner`, `world`, `timestamp`) SELECT ?, ?, ?, ?, ? WHERE NOT EXISTS (SELECT null FROM `" + this.prefix + "plot` WHERE `world` = ? AND `plot_id_x` = ? AND `plot_id_z` = ?)"; + if (!supportsGetGeneratedKeys) { + tempCreatePlotSafe += " RETURNING `id`"; + } + this.CREATE_PLOT_SAFE = tempCreatePlotSafe; } - this.CREATE_CLUSTER = "INSERT INTO `" + this.prefix + String tempCreateCluster = "INSERT INTO `" + this.prefix + "cluster`(`pos1_x`, `pos1_z`, `pos2_x`, `pos2_z`, `owner`, `world`) VALUES(?, ?, ?, ?, ?, ?)"; + if (!supportsGetGeneratedKeys) { + tempCreateCluster += " RETURNING `id`"; + } + this.CREATE_CLUSTER = tempCreateCluster; + try { createTables(); } catch (SQLException e) { @@ -1065,9 +1091,8 @@ public void execute(PreparedStatement statement) { @Override public void addBatch(PreparedStatement statement) throws SQLException { - int inserted = statement.executeUpdate(); - if (inserted > 0) { - try (ResultSet keys = statement.getGeneratedKeys()) { + if (statement.execute() || statement.getUpdateCount() > 0) { + try (ResultSet keys = supportsGetGeneratedKeys ? statement.getGeneratedKeys() : statement.getResultSet()) { if (keys.next()) { plot.temp = keys.getInt(1); addPlotTask(plot, new UniqueStatement( @@ -1137,8 +1162,8 @@ public void execute(PreparedStatement statement) { @Override public void addBatch(PreparedStatement statement) throws SQLException { - statement.executeUpdate(); - try (ResultSet keys = statement.getGeneratedKeys()) { + statement.execute(); + try (ResultSet keys = supportsGetGeneratedKeys ? statement.getGeneratedKeys() : statement.getResultSet()) { if (keys.next()) { plot.temp = keys.getInt(1); } @@ -3050,8 +3075,8 @@ public void execute(PreparedStatement statement) { @Override public void addBatch(PreparedStatement statement) throws SQLException { - statement.executeUpdate(); - try (ResultSet keys = statement.getGeneratedKeys()) { + statement.execute(); + try (ResultSet keys = supportsGetGeneratedKeys ? statement.getGeneratedKeys() : statement.getResultSet()) { if (keys.next()) { cluster.temp = keys.getInt(1); } diff --git a/Core/src/main/java/com/plotsquared/core/events/PlayerTeleportToPlotEvent.java b/Core/src/main/java/com/plotsquared/core/events/PlayerTeleportToPlotEvent.java index 36c64440c2..0b56c1e958 100644 --- a/Core/src/main/java/com/plotsquared/core/events/PlayerTeleportToPlotEvent.java +++ b/Core/src/main/java/com/plotsquared/core/events/PlayerTeleportToPlotEvent.java @@ -21,21 +21,26 @@ import com.plotsquared.core.location.Location; import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.Plot; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.function.UnaryOperator; /** * Called when a player teleports to a plot */ public class PlayerTeleportToPlotEvent extends PlotPlayerEvent implements CancellablePlotEvent { - private final Location from; private final TeleportCause cause; private Result eventResult; + private final Location from; + private UnaryOperator locationTransformer; + /** * PlayerTeleportToPlotEvent: Called when a player teleports to a plot * * @param player That was teleported - * @param from Start location + * @param from The origin location, from where the teleport was triggered (players location most likely) * @param plot Plot to which the player was teleported * @param cause Why the teleport is being completed * @since 6.1.0 @@ -57,7 +62,8 @@ public TeleportCause getCause() { } /** - * Get the from location + * Get the location, from where the teleport was triggered + * (the players current location when executing the home command for example) * * @return Location */ @@ -65,6 +71,27 @@ public Location getFrom() { return this.from; } + /** + * Gets the currently applied {@link UnaryOperator transformer} or null, if none was set + * + * @return LocationTransformer + * @since TODO + */ + public @Nullable UnaryOperator getLocationTransformer() { + return this.locationTransformer; + } + + /** + * Sets the {@link UnaryOperator transformer} to mutate the location where the player will be teleported to. + * May be {@code null}, if any previous set transformations should be discarded. + * + * @param locationTransformer The new transformer + * @since TODO + */ + public void setLocationTransformer(@Nullable UnaryOperator locationTransformer) { + this.locationTransformer = locationTransformer; + } + @Override public Result getEventResult() { return eventResult; diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java b/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java index a51d860041..175184c9cf 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridGen.java @@ -69,8 +69,8 @@ private void placeSchem( EnumSet features ) { int minY; // Math.min(world.PLOT_HEIGHT, world.ROAD_HEIGHT); - if ((features.contains(SchematicFeature.ROAD) && Settings.Schematics.PASTE_ROAD_ON_TOP) - || (!features.contains(SchematicFeature.ROAD) && Settings.Schematics.PASTE_ON_TOP)) { + boolean isRoad = features.contains(SchematicFeature.ROAD); + if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad && Settings.Schematics.PASTE_ON_TOP)) { minY = world.SCHEM_Y; } else { minY = world.getMinBuildHeight(); diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridPlotManager.java b/Core/src/main/java/com/plotsquared/core/generator/HybridPlotManager.java index 92b3c9a277..2ecdff4a2a 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridPlotManager.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridPlotManager.java @@ -162,6 +162,7 @@ private void createSchemAbs( } else { minY = hybridPlotWorld.getMinBuildHeight(); } + int schemYDiff = (isRoad ? hybridPlotWorld.getRoadYStart() : hybridPlotWorld.getPlotYStart()) - minY; BaseBlock airBlock = BlockTypes.AIR.getDefaultState().toBaseBlock(); for (int x = pos1.getX(); x <= pos2.getX(); x++) { short absX = (short) ((x - hybridPlotWorld.ROAD_OFFSET_X) % size); @@ -178,10 +179,14 @@ private void createSchemAbs( for (int y = 0; y < blocks.length; y++) { if (blocks[y] != null) { queue.setBlock(x, minY + y, z, blocks[y]); - } else if (!isRoad) { - // This is necessary, otherwise any blocks not specified in the schematic will remain after a clear - // Do not set air for road as this may cause cavernous roads when debugroadregen is used + } else if (y > schemYDiff) { + // This is necessary, otherwise any blocks not specified in the schematic will remain after a clear. + // This should only be done where the schematic has actually "started" queue.setBlock(x, minY + y, z, airBlock); + } else if (isRoad) { + queue.setBlock(x, minY + y, z, hybridPlotWorld.ROAD_BLOCK.toPattern()); + } else { + queue.setBlock(x, minY + y, z, hybridPlotWorld.MAIN_BLOCK.toPattern()); } } } diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java b/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java index 679a1b2df5..d0ebd643b7 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridPlotWorld.java @@ -76,6 +76,9 @@ public class HybridPlotWorld extends ClassicPlotWorld { * The Y level at which schematic generation will start, lowest of either road or plot schematic generation. */ public int SCHEM_Y; + + private int plotY; + private int roadY; private Location SIGN_LOCATION; private File root = null; private int lastOverlayHeightError = Integer.MIN_VALUE; @@ -186,7 +189,7 @@ public void loadConfiguration(ConfigurationSection config) { } Object value; try { - final boolean accessible = field.isAccessible(); + final boolean accessible = field.canAccess(this); field.setAccessible(true); value = field.get(this); field.setAccessible(accessible); @@ -252,68 +255,60 @@ public void setupSchematics() throws SchematicHandler.UnsupportedFormatException SCHEM_Y = schematicStartHeight(); - // plotY and roadY are important to allow plot and/or road schematic "overflow" into each other without causing AIOOB - // exceptions when attempting either to set blocks to, or get block from G_SCH + // plotY and roadY are important to allow plot and/or road schematic "overflow" into each other + // without causing AIOOB exceptions when attempting either to set blocks to, or get block from G_SCH // Default plot schematic start height, normalized to the minimum height schematics are pasted from. - int plotY = PLOT_HEIGHT - SCHEM_Y; + plotY = PLOT_HEIGHT - SCHEM_Y; int minRoadWall = Settings.Schematics.USE_WALL_IN_ROAD_SCHEM_HEIGHT ? Math.min(ROAD_HEIGHT, WALL_HEIGHT) : ROAD_HEIGHT; // Default road schematic start height, normalized to the minimum height schematics are pasted from. - int roadY = minRoadWall - SCHEM_Y; + roadY = minRoadWall - SCHEM_Y; int worldGenHeight = getMaxGenHeight() - getMinGenHeight() + 1; - int maxSchematicHeight = 0; int plotSchemHeight = 0; // SCHEM_Y should be normalised to the plot "start" height if (schematic3 != null) { - plotSchemHeight = maxSchematicHeight = schematic3.getClipboard().getDimensions().getY(); - if (maxSchematicHeight == worldGenHeight) { + plotSchemHeight = schematic3.getClipboard().getDimensions().getY(); + if (plotSchemHeight == worldGenHeight) { SCHEM_Y = getMinGenHeight(); plotY = 0; } else if (!Settings.Schematics.PASTE_ON_TOP) { - SCHEM_Y = getMinBuildHeight(); + SCHEM_Y = getMinGenHeight(); plotY = 0; } } - int roadSchemHeight; + int roadSchemHeight = 0; if (schematic1 != null) { roadSchemHeight = Math.max( schematic1.getClipboard().getDimensions().getY(), schematic2.getClipboard().getDimensions().getY() ); - maxSchematicHeight = Math.max(roadSchemHeight, maxSchematicHeight); - if (maxSchematicHeight == worldGenHeight) { + if (roadSchemHeight == worldGenHeight) { SCHEM_Y = getMinGenHeight(); roadY = 0; // Road is the lowest schematic if (schematic3 != null && schematic3.getClipboard().getDimensions().getY() != worldGenHeight) { // Road is the lowest schematic. Normalize plotY to it. if (Settings.Schematics.PASTE_ON_TOP) { plotY = PLOT_HEIGHT - getMinGenHeight(); - } else { - plotY = getMinBuildHeight() - getMinGenHeight(); } } } else if (!Settings.Schematics.PASTE_ROAD_ON_TOP) { - if (SCHEM_Y == getMinGenHeight()) { // Only possible if plot schematic is enabled - // Plot is still the lowest schematic, normalize roadY to it - roadY = getMinBuildHeight() - getMinGenHeight(); - } else if (schematic3 != null) { - SCHEM_Y = getMinBuildHeight(); - roadY = 0;// Road is the lowest schematic + roadY = 0; + SCHEM_Y = getMinGenHeight(); + if (schematic3 != null) { if (Settings.Schematics.PASTE_ON_TOP) { // Road is the lowest schematic. Normalize plotY to it. - plotY = PLOT_HEIGHT - getMinBuildHeight(); + plotY = PLOT_HEIGHT - SCHEM_Y; } - maxSchematicHeight = Math.max(maxSchematicHeight, plotY + plotSchemHeight); } } else { roadY = minRoadWall - SCHEM_Y; - maxSchematicHeight = Math.max(maxSchematicHeight, roadY + roadSchemHeight); } } + int maxSchematicHeight = Math.max(plotY + plotSchemHeight, roadY + roadSchemHeight); if (schematic3 != null) { this.PLOT_SCHEMATIC = true; @@ -554,4 +549,24 @@ public boolean populationNeeded() { return this.root; } + /** + * Get the y value where the plot schematic should be pasted from. + * + * @return plot schematic y start value + * @since 7.0.0 + */ + public int getPlotYStart() { + return SCHEM_Y + plotY; + } + + /** + * Get the y value where the road schematic should be pasted from. + * + * @return road schematic y start value + * @since 7.0.0 + */ + public int getRoadYStart() { + return SCHEM_Y + roadY; + } + } diff --git a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java index 31523ba3b6..a2ea486d8d 100644 --- a/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java +++ b/Core/src/main/java/com/plotsquared/core/generator/HybridUtils.java @@ -77,6 +77,10 @@ public class HybridUtils { private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + HybridUtils.class.getSimpleName()); private static final BlockState AIR = BlockTypes.AIR.getDefaultState(); + /** + * Deprecated and likely to be removed in a future release. + */ + @Deprecated(forRemoval = true, since = "7.0.0") public static HybridUtils manager; public static Set regions; public static int height; @@ -529,7 +533,7 @@ public boolean setupRoadSchematic(Plot plot) { Math.min(plotworld.PLOT_HEIGHT, Math.min(plotworld.WALL_HEIGHT, plotworld.ROAD_HEIGHT)) : plotworld.ROAD_HEIGHT; int sx = bot.getX() - plotworld.ROAD_WIDTH + 1; int sz = bot.getZ() + 1; - int sy = Settings.Schematics.PASTE_ROAD_ON_TOP ? schemY : plot.getArea().getMinBuildHeight(); + int sy = Settings.Schematics.PASTE_ROAD_ON_TOP ? schemY : plot.getArea().getMinGenHeight(); int ex = bot.getX(); int ez = top.getZ(); int ey = get_ey(plotworld, queue, sx, ex, sz, ez, sy); @@ -668,7 +672,7 @@ public boolean regenerateRoad( } if (condition) { BaseBlock[] blocks = plotWorld.G_SCH.get(MathMan.pair(absX, absZ)); - int minY = Settings.Schematics.PASTE_ROAD_ON_TOP ? plotWorld.SCHEM_Y : area.getMinGenHeight() + 1; + int minY = plotWorld.getRoadYStart(); int maxDy = Math.max(extend, blocks.length); for (int dy = 0; dy < maxDy; dy++) { if (dy > blocks.length - 1) { diff --git a/Core/src/main/java/com/plotsquared/core/listener/PlotListener.java b/Core/src/main/java/com/plotsquared/core/listener/PlotListener.java index c583ed0722..1ac4887a3c 100644 --- a/Core/src/main/java/com/plotsquared/core/listener/PlotListener.java +++ b/Core/src/main/java/com/plotsquared/core/listener/PlotListener.java @@ -55,7 +55,6 @@ import com.plotsquared.core.plot.flag.implementations.WeatherFlag; import com.plotsquared.core.plot.flag.types.TimedFlag; import com.plotsquared.core.util.EventDispatcher; -import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.task.TaskTime; import com.sk89q.worldedit.world.gamemode.GameMode; @@ -63,7 +62,6 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.item.ItemTypes; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; @@ -77,6 +75,7 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; public class PlotListener { @@ -321,22 +320,27 @@ public boolean plotEntry(final PlotPlayer player, final Plot plot) { } if ((lastPlot != null) && plot.getId().equals(lastPlot.getId()) && plot.hasOwner()) { final UUID plotOwner = plot.getOwnerAbs(); - ComponentLike owner = PlayerManager.resolveName(plotOwner, true).toComponent(player); Caption header = fromFlag ? StaticCaption.of(title) : TranslatableCaption.of("titles" + ".title_entered_plot"); Caption subHeader = fromFlag ? StaticCaption.of(subtitle) : TranslatableCaption.of("titles" + ".title_entered_plot_sub"); - TagResolver resolver = TagResolver.builder() - .tag("plot", Tag.inserting(Component.text(lastPlot.getId().toString()))) - .tag("world", Tag.inserting(Component.text(player.getLocation().getWorldName()))) - .tag("owner", Tag.inserting(owner)) - .tag("alias", Tag.inserting(Component.text(plot.getAlias()))) - .build(); - if (Settings.Titles.TITLES_AS_ACTIONBAR) { - player.sendActionBar(header, resolver); - } else { - player.sendTitle(header, subHeader, resolver); - } + + CompletableFuture future = PlotSquared.platform().playerManager() + .getUsernameCaption(plotOwner).thenApply(caption -> TagResolver.builder() + .tag("owner", Tag.inserting(caption.toComponent(player))) + .tag("plot", Tag.inserting(Component.text(lastPlot.getId().toString()))) + .tag("world", Tag.inserting(Component.text(player.getLocation().getWorldName()))) + .tag("alias", Tag.inserting(Component.text(plot.getAlias()))) + .build() + ); + + future.whenComplete((tagResolver, throwable) -> { + if (Settings.Titles.TITLES_AS_ACTIONBAR) { + player.sendActionBar(header, tagResolver); + } else { + player.sendTitle(header, subHeader, tagResolver); + } + }); } }, TaskTime.seconds(1L)); } diff --git a/Core/src/main/java/com/plotsquared/core/location/Direction.java b/Core/src/main/java/com/plotsquared/core/location/Direction.java index eecfb6c376..cf6fe3877b 100644 --- a/Core/src/main/java/com/plotsquared/core/location/Direction.java +++ b/Core/src/main/java/com/plotsquared/core/location/Direction.java @@ -55,6 +55,25 @@ public static Direction getFromIndex(int index) { return NORTH; } + /** + * {@return the opposite direction} + * If this is {@link Direction#ALL}, then {@link Direction#ALL} is returned. + * @since 7.2.0 + */ + public Direction opposite() { + return switch (this) { + case ALL -> ALL; + case NORTH -> SOUTH; + case EAST -> WEST; + case SOUTH -> NORTH; + case WEST -> EAST; + case NORTHEAST -> SOUTHWEST; + case SOUTHEAST -> NORTHWEST; + case SOUTHWEST -> NORTHEAST; + case NORTHWEST -> SOUTHEAST; + }; + } + public int getIndex() { return index; } diff --git a/Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java b/Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java index 38183269b3..f284dd7386 100644 --- a/Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java +++ b/Core/src/main/java/com/plotsquared/core/location/UncheckedWorldLocation.java @@ -60,6 +60,19 @@ private UncheckedWorldLocation( return new UncheckedWorldLocation(world, x, y, z); } + /** + * Construct a new location with yaw and pitch equal to 0 + * + * @param world World + * @param loc Coordinates + * @return New location + * @since 7.0.0 + */ + @DoNotUse + public static @NonNull UncheckedWorldLocation at(final @NonNull String world, BlockVector3 loc) { + return new UncheckedWorldLocation(world, loc.getX(), loc.getY(), loc.getZ()); + } + @Override @DoNotUse public @NonNull String getWorldName() { diff --git a/Core/src/main/java/com/plotsquared/core/permissions/Permission.java b/Core/src/main/java/com/plotsquared/core/permissions/Permission.java index 806dcfc9be..94b71761bf 100644 --- a/Core/src/main/java/com/plotsquared/core/permissions/Permission.java +++ b/Core/src/main/java/com/plotsquared/core/permissions/Permission.java @@ -45,6 +45,7 @@ public enum Permission implements ComponentLike { PERMISSION_ADMIN_ENTRY_FORCEFIELD("plots.admin.entry.forcefield"), PERMISSION_ADMIN_COMMANDS_CHATSPY("plots.admin.command.chatspy"), PERMISSION_MERGE("plots.merge"), + PERMISSION_MERGE_ALL("plots.merge.all"), PERMISSION_MERGE_OTHER("plots.merge.other"), PERMISSION_MERGE_KEEP_ROAD("plots.merge.keeproad"), PERMISSION_ADMIN_CAPS_OTHER("plots.admin.caps.other"), @@ -200,7 +201,8 @@ public enum Permission implements ComponentLike { PERMISSION_RATE("plots.rate"), PERMISSION_ADMIN_FLIGHT("plots.admin.flight"), PERMISSION_ADMIN_COMPONENTS_OTHER("plots.admin.component.other"), - PERMISSION_ADMIN_BYPASS_BORDER("plots.admin.border.bypass"); + PERMISSION_ADMIN_BYPASS_BORDER("plots.admin.border.bypass"), + PERMISSION_ADMIN_BYPASS_ECON("plots.admin.econ.bypass"); // private final String text; diff --git a/Core/src/main/java/com/plotsquared/core/permissions/PermissionHolder.java b/Core/src/main/java/com/plotsquared/core/permissions/PermissionHolder.java index 3b483f45b2..8a21d8c5df 100644 --- a/Core/src/main/java/com/plotsquared/core/permissions/PermissionHolder.java +++ b/Core/src/main/java/com/plotsquared/core/permissions/PermissionHolder.java @@ -100,6 +100,7 @@ default int hasPermissionRange( } String[] nodes = stub.split("\\."); StringBuilder builder = new StringBuilder(); + // Wildcard check from less specific permission to more specific permission for (int i = 0; i < (nodes.length - 1); i++) { builder.append(nodes[i]).append("."); if (!stub.equals(builder + Permission.PERMISSION_STAR.toString())) { @@ -108,6 +109,7 @@ default int hasPermissionRange( } } } + // Wildcard check for the full permission if (hasPermission(stub + ".*")) { return Integer.MAX_VALUE; } diff --git a/Core/src/main/java/com/plotsquared/core/player/OfflinePlotPlayer.java b/Core/src/main/java/com/plotsquared/core/player/OfflinePlotPlayer.java index 87505b44fc..f88f58c7b7 100644 --- a/Core/src/main/java/com/plotsquared/core/player/OfflinePlotPlayer.java +++ b/Core/src/main/java/com/plotsquared/core/player/OfflinePlotPlayer.java @@ -25,9 +25,9 @@ public interface OfflinePlotPlayer extends PermissionHolder { /** - * Gets the {@code UUID} of this player + * Returns the UUID of the player. * - * @return the player {@link UUID} + * @return the UUID of the player */ UUID getUUID(); @@ -39,9 +39,9 @@ public interface OfflinePlotPlayer extends PermissionHolder { long getLastPlayed(); /** - * Gets the name of this player. + * Returns the name of the player. * - * @return the player name + * @return the name of the player */ String getName(); diff --git a/Core/src/main/java/com/plotsquared/core/player/PlotPlayer.java b/Core/src/main/java/com/plotsquared/core/player/PlotPlayer.java index 6bb9e1bad3..611e8d868f 100644 --- a/Core/src/main/java/com/plotsquared/core/player/PlotPlayer.java +++ b/Core/src/main/java/com/plotsquared/core/player/PlotPlayer.java @@ -80,6 +80,7 @@ import java.util.Queue; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -273,8 +274,9 @@ Object deleteMeta(String key) { return this.meta == null ? null : this.meta.remove(key); } + /** - * This player's name. + * Returns the name of the player. * * @return the name of the player */ @@ -880,7 +882,7 @@ public void sendTitle( final Component titleComponent = MiniMessage.miniMessage().deserialize(title.getComponent(this), replacements); final Component subtitleComponent = MiniMessage.miniMessage().deserialize(subtitle.getComponent(this), replacements); - final Title.Times times = Title.Times.of( + final Title.Times times = Title.Times.times( Duration.of(Settings.Titles.TITLES_FADE_IN * 50L, ChronoUnit.MILLIS), Duration.of(Settings.Titles.TITLES_STAY * 50L, ChronoUnit.MILLIS), Duration.of(Settings.Titles.TITLES_FADE_OUT * 50L, ChronoUnit.MILLIS) @@ -952,6 +954,54 @@ public void sendMessage( } } + /** + * Sends a message to the command caller, when the future is resolved + * + * @param caption Caption to send + * @param asyncReplacement Async variable replacement + * @return A Future to be resolved, after the message was sent + * @since 7.1.0 + */ + public final CompletableFuture sendMessage( + @NonNull Caption caption, + CompletableFuture<@NonNull TagResolver> asyncReplacement + ) { + return sendMessage(caption, new CompletableFuture[]{asyncReplacement}); + } + + /** + * Sends a message to the command caller, when all futures are resolved + * + * @param caption Caption to send + * @param asyncReplacements Async variable replacements + * @param replacements Sync variable replacements + * @return A Future to be resolved, after the message was sent + * @since 7.1.0 + */ + public final CompletableFuture sendMessage( + @NonNull Caption caption, + CompletableFuture<@NonNull TagResolver>[] asyncReplacements, + @NonNull TagResolver... replacements + ) { + return CompletableFuture.allOf(asyncReplacements).whenComplete((unused, throwable) -> { + Set resolvers = new HashSet<>(Arrays.asList(replacements)); + if (throwable != null) { + sendMessage( + TranslatableCaption.of("errors.error"), + TagResolver.resolver("value", Tag.inserting( + Component.text("Failed to resolve asynchronous caption replacements") + )) + ); + LOGGER.error("Failed to resolve asynchronous tagresolver(s) for " + caption, throwable); + } else { + for (final CompletableFuture asyncReplacement : asyncReplacements) { + resolvers.add(asyncReplacement.join()); + } + } + sendMessage(caption, resolvers.toArray(TagResolver[]::new)); + }); + } + // Redefine from PermissionHolder as it's required from CommandCaller @Override public boolean hasPermission(@NonNull String permission) { diff --git a/Core/src/main/java/com/plotsquared/core/plot/Plot.java b/Core/src/main/java/com/plotsquared/core/plot/Plot.java index 90e3393082..2c47ef322c 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/Plot.java +++ b/Core/src/main/java/com/plotsquared/core/plot/Plot.java @@ -29,6 +29,7 @@ import com.plotsquared.core.configuration.caption.StaticCaption; import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.database.DBFunc; +import com.plotsquared.core.events.PlayerTeleportToPlotEvent; import com.plotsquared.core.events.Result; import com.plotsquared.core.events.TeleportCause; import com.plotsquared.core.generator.ClassicPlotWorld; @@ -85,6 +86,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -2283,8 +2285,8 @@ public Plot getRelative(PlotArea area, int x, int y) { } /** - * Gets a set of plots connected (and including) this plot
- * - This result is cached globally + * Gets a set of plots connected (and including) this plot. + * The returned set is immutable. * * @return a Set of Plots connected to this Plot */ @@ -2295,115 +2297,73 @@ public Set getConnectedPlots() { if (!this.isMerged()) { return Collections.singleton(this); } + Plot basePlot = getBasePlot(false); + if (this.connectedCache == null && this != basePlot) { + // share cache between connected plots + Set connectedPlots = basePlot.getConnectedPlots(); + this.connectedCache = connectedPlots; + return connectedPlots; + } if (this.connectedCache != null && this.connectedCache.contains(this)) { return this.connectedCache; } - HashSet tmpSet = new HashSet<>(); + Set tmpSet = new HashSet<>(); tmpSet.add(this); - Plot tmp; - HashSet queuecache = new HashSet<>(); + HashSet queueCache = new HashSet<>(); ArrayDeque frontier = new ArrayDeque<>(); - if (this.isMerged(Direction.NORTH)) { - tmp = this.area.getPlotAbs(this.id.getRelative(Direction.NORTH)); - if (!tmp.isMerged(Direction.SOUTH)) { - // invalid merge - if (tmp.isOwnerAbs(this.getOwnerAbs())) { - tmp.getSettings().setMerged(Direction.SOUTH, true); - DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); - } else { - this.getSettings().setMerged(Direction.NORTH, false); - DBFunc.setMerged(this, this.getSettings().getMerged()); - } + computeDirectMerged(queueCache, frontier, Direction.NORTH); + computeDirectMerged(queueCache, frontier, Direction.EAST); + computeDirectMerged(queueCache, frontier, Direction.SOUTH); + computeDirectMerged(queueCache, frontier, Direction.WEST); + Plot current; + while ((current = frontier.poll()) != null) { + if (!current.hasOwner() || current.settings == null) { + continue; } - queuecache.add(tmp); - frontier.add(tmp); + tmpSet.add(current); + queueCache.remove(current); + addIfIncluded(current, Direction.NORTH, queueCache, tmpSet, frontier); + addIfIncluded(current, Direction.EAST, queueCache, tmpSet, frontier); + addIfIncluded(current, Direction.SOUTH, queueCache, tmpSet, frontier); + addIfIncluded(current, Direction.WEST, queueCache, tmpSet, frontier); } - if (this.isMerged(Direction.EAST)) { - tmp = this.area.getPlotAbs(this.id.getRelative(Direction.EAST)); + tmpSet = Set.copyOf(tmpSet); + this.connectedCache = tmpSet; + return tmpSet; + } + + private void computeDirectMerged(Set queueCache, Deque frontier, Direction direction) { + if (this.isMerged(direction)) { + Plot tmp = this.area.getPlotAbs(this.id.getRelative(direction)); assert tmp != null; - if (!tmp.isMerged(Direction.WEST)) { + if (!tmp.isMerged(direction.opposite())) { // invalid merge if (tmp.isOwnerAbs(this.getOwnerAbs())) { - tmp.getSettings().setMerged(Direction.WEST, true); + tmp.getSettings().setMerged(direction.opposite(), true); DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); } else { - this.getSettings().setMerged(Direction.EAST, false); + this.getSettings().setMerged(direction, false); DBFunc.setMerged(this, this.getSettings().getMerged()); } } - queuecache.add(tmp); + queueCache.add(tmp); frontier.add(tmp); } - if (this.isMerged(Direction.SOUTH)) { - tmp = this.area.getPlotAbs(this.id.getRelative(Direction.SOUTH)); - assert tmp != null; - if (!tmp.isMerged(Direction.NORTH)) { - // invalid merge - if (tmp.isOwnerAbs(this.getOwnerAbs())) { - tmp.getSettings().setMerged(Direction.NORTH, true); - DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); - } else { - this.getSettings().setMerged(Direction.SOUTH, false); - DBFunc.setMerged(this, this.getSettings().getMerged()); - } - } - queuecache.add(tmp); - frontier.add(tmp); + } + + private void addIfIncluded( + Plot current, Direction + direction, Set queueCache, Set tmpSet, Deque frontier + ) { + if (!current.isMerged(direction)) { + return; } - if (this.isMerged(Direction.WEST)) { - tmp = this.area.getPlotAbs(this.id.getRelative(Direction.WEST)); - if (!tmp.isMerged(Direction.EAST)) { - // invalid merge - if (tmp.isOwnerAbs(this.getOwnerAbs())) { - tmp.getSettings().setMerged(Direction.EAST, true); - DBFunc.setMerged(tmp, tmp.getSettings().getMerged()); - } else { - this.getSettings().setMerged(Direction.WEST, false); - DBFunc.setMerged(this, this.getSettings().getMerged()); - } - } - queuecache.add(tmp); + Plot tmp = current.area.getPlotAbs(current.id.getRelative(direction)); + if (tmp != null && !queueCache.contains(tmp) && !tmpSet.contains(tmp)) { + queueCache.add(tmp); frontier.add(tmp); } - Plot current; - while ((current = frontier.poll()) != null) { - if (!current.hasOwner() || current.settings == null) { - continue; - } - tmpSet.add(current); - queuecache.remove(current); - if (current.isMerged(Direction.NORTH)) { - tmp = current.area.getPlotAbs(current.id.getRelative(Direction.NORTH)); - if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { - queuecache.add(tmp); - frontier.add(tmp); - } - } - if (current.isMerged(Direction.EAST)) { - tmp = current.area.getPlotAbs(current.id.getRelative(Direction.EAST)); - if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { - queuecache.add(tmp); - frontier.add(tmp); - } - } - if (current.isMerged(Direction.SOUTH)) { - tmp = current.area.getPlotAbs(current.id.getRelative(Direction.SOUTH)); - if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { - queuecache.add(tmp); - frontier.add(tmp); - } - } - if (current.isMerged(Direction.WEST)) { - tmp = current.area.getPlotAbs(current.id.getRelative(Direction.WEST)); - if (tmp != null && !queuecache.contains(tmp) && !tmpSet.contains(tmp)) { - queuecache.add(tmp); - frontier.add(tmp); - } - } - } - this.connectedCache = tmpSet; - return tmpSet; } /** @@ -2614,8 +2574,9 @@ public void teleportPlayer(final PlotPlayer player, Consumer result) */ public void teleportPlayer(final PlotPlayer player, TeleportCause cause, Consumer resultConsumer) { Plot plot = this.getBasePlot(false); - Result result = this.eventDispatcher.callTeleport(player, player.getLocation(), plot, cause).getEventResult(); - if (result == Result.DENY) { + + PlayerTeleportToPlotEvent event = this.eventDispatcher.callTeleport(player, player.getLocation(), plot, cause); + if (event.getEventResult() == Result.DENY) { player.sendMessage( TranslatableCaption.of("events.event_denied"), TagResolver.resolver("value", Tag.inserting(Component.text("Teleport"))) @@ -2623,7 +2584,10 @@ public void teleportPlayer(final PlotPlayer player, TeleportCause cause, Cons resultConsumer.accept(false); return; } - final Consumer locationConsumer = location -> { + + final Consumer locationConsumer = calculatedLocation -> { + Location location = event.getLocationTransformer() == null ? calculatedLocation : + Objects.requireNonNullElse(event.getLocationTransformer().apply(calculatedLocation), calculatedLocation); if (Settings.Teleport.DELAY == 0 || player.hasPermission("plots.teleport.delay.bypass")) { player.sendMessage(TranslatableCaption.of("teleport.teleported_to_plot")); player.teleport(location, cause); @@ -2679,6 +2643,11 @@ public boolean isOnline() { return false; } + /** + * Get the maximum distance of the plot from x=0, z=0. + * + * @return max block distance from 0,0 + */ public int getDistanceFromOrigin() { Location bot = getManager().getPlotBottomLocAbs(id); Location top = getManager().getPlotTopLocAbs(id); @@ -2692,7 +2661,7 @@ public int getDistanceFromOrigin() { * Expands the world border to include this plot if it is beyond the current border. */ public void updateWorldBorder() { - int border = this.area.getBorder(); + int border = this.area.getBorder(false); if (border == Integer.MAX_VALUE) { return; } diff --git a/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java b/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java index 97535efb32..ad6861da18 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java +++ b/Core/src/main/java/com/plotsquared/core/plot/PlotArea.java @@ -51,6 +51,8 @@ import com.plotsquared.core.util.PlotExpression; import com.plotsquared.core.util.RegionUtil; import com.plotsquared.core.util.StringMan; +import com.plotsquared.core.util.task.TaskManager; +import com.plotsquared.core.util.task.TaskTime; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; @@ -145,6 +147,7 @@ public abstract class PlotArea implements ComponentLike { private Map prices = new HashMap<>(); private List schematics = new ArrayList<>(); private boolean worldBorder = false; + private int borderSize = 1; private boolean useEconomy = false; private int hash; private CuboidRegion region; @@ -180,8 +183,7 @@ public PlotArea( this.worldConfiguration = worldConfiguration; } - private static Collection> parseFlags(List flagStrings) { - final Collection> flags = new ArrayList<>(); + private static void parseFlags(FlagContainer flagContainer, List flagStrings) { for (final String key : flagStrings) { final String[] split; if (key.contains(";")) { @@ -193,7 +195,7 @@ public PlotArea( GlobalFlagContainer.getInstance().getFlagFromString(split[0]); if (flagInstance != null) { try { - flags.add(flagInstance.parse(split[1])); + flagContainer.addFlag(flagInstance.parse(split[1])); } catch (final FlagParseException e) { LOGGER.warn( "Failed to parse default flag with key '{}' and value '{}'. " @@ -204,9 +206,10 @@ public PlotArea( ); e.printStackTrace(); } + } else { + flagContainer.addUnknownFlag(split[0], split[1]); } } - return flags; } @NonNull @@ -354,6 +357,7 @@ public void loadDefaultConfiguration(ConfigurationSection config) { this.plotChat = config.getBoolean("chat.enabled"); this.forcingPlotChat = config.getBoolean("chat.forced"); this.worldBorder = config.getBoolean("world.border"); + this.borderSize = config.getInt("world.border_size"); this.maxBuildHeight = config.getInt("world.max_height"); this.minBuildHeight = config.getInt("world.min_height"); this.minGenHeight = config.getInt("world.min_gen_height"); @@ -391,6 +395,28 @@ public void loadDefaultConfiguration(ConfigurationSection config) { } } + this.spawnEggs = config.getBoolean("event.spawn.egg"); + this.spawnCustom = config.getBoolean("event.spawn.custom"); + this.spawnBreeding = config.getBoolean("event.spawn.breeding"); + + if (PlotSquared.get().isWeInitialised()) { + loadFlags(config); + } else { + ConsolePlayer.getConsole().sendMessage( + TranslatableCaption.of("flags.delaying_loading_area_flags"), + TagResolver.resolver("area", Tag.inserting(Component.text(this.id == null ? this.worldName : this.id))) + ); + TaskManager.runTaskLater(() -> loadFlags(config), TaskTime.ticks(1)); + } + + loadConfiguration(config); + } + + private void loadFlags(ConfigurationSection config) { + ConsolePlayer.getConsole().sendMessage( + TranslatableCaption.of("flags.loading_area_flags"), + TagResolver.resolver("area", Tag.inserting(Component.text(this.id == null ? this.worldName : this.id))) + ); List flags = config.getStringList("flags.default"); if (flags.isEmpty()) { flags = config.getStringList("flags"); @@ -405,16 +431,12 @@ public void loadDefaultConfiguration(ConfigurationSection config) { } } } - this.getFlagContainer().addAll(parseFlags(flags)); + parseFlags(this.getFlagContainer(), flags); ConsolePlayer.getConsole().sendMessage( TranslatableCaption.of("flags.area_flags"), TagResolver.resolver("flags", Tag.inserting(Component.text(flags.toString()))) ); - this.spawnEggs = config.getBoolean("event.spawn.egg"); - this.spawnCustom = config.getBoolean("event.spawn.custom"); - this.spawnBreeding = config.getBoolean("event.spawn.breeding"); - List roadflags = config.getStringList("road.flags"); if (roadflags.isEmpty()) { roadflags = new ArrayList<>(); @@ -426,14 +448,12 @@ public void loadDefaultConfiguration(ConfigurationSection config) { } } } - this.roadFlags = roadflags.size() > 0; - this.getRoadFlagContainer().addAll(parseFlags(roadflags)); + this.roadFlags = !roadflags.isEmpty(); + parseFlags(this.getRoadFlagContainer(), roadflags); ConsolePlayer.getConsole().sendMessage( TranslatableCaption.of("flags.road_flags"), TagResolver.resolver("flags", Tag.inserting(Component.text(roadflags.toString()))) ); - - loadConfiguration(config); } public abstract void loadConfiguration(ConfigurationSection config); @@ -471,6 +491,7 @@ public void saveConfiguration(ConfigurationSection config) { options.put("event.spawn.custom", this.isSpawnCustom()); options.put("event.spawn.breeding", this.isSpawnBreeding()); options.put("world.border", this.hasWorldBorder()); + options.put("world.border_size", this.getBorderSize()); options.put("home.default", "side"); String position = config.getString( "home.nonmembers", @@ -657,9 +678,9 @@ public boolean notifyIfOutsideBuildArea(PlotPlayer player, int y) { player.sendMessage( TranslatableCaption.of("height.height_limit"), TagResolver.builder() - .tag("minHeight", Tag.inserting(Component.text(minBuildHeight))) + .tag("minheight", Tag.inserting(Component.text(minBuildHeight))) .tag( - "maxHeight", + "maxheight", Tag.inserting(Component.text(maxBuildHeight)) ).build() ); @@ -919,7 +940,9 @@ public boolean addPlotAbs(final @NonNull Plot plot) { * Get the plot border distance for a world
* * @return The border distance or Integer.MAX_VALUE if no border is set + * @deprecated Use {@link PlotArea#getBorder(boolean)} */ + @Deprecated(forRemoval = true, since = "7.2.0") public int getBorder() { final Integer meta = (Integer) getMeta("worldBorder"); if (meta != null) { @@ -933,6 +956,27 @@ public int getBorder() { return Integer.MAX_VALUE; } + /** + * Get the plot border distance for a world, specifying whether the returned value should include the world.border-size + * value. This is a player-traversable area, where plots cannot be claimed + * + * @param getExtended If the extra border given by world.border-size should be included + * @return Border distance of Integer.MAX_VALUE if no border is set + * @since 7.2.0 + */ + public int getBorder(boolean getExtended) { + final Integer meta = (Integer) getMeta("worldBorder"); + if (meta != null) { + int border = meta + 1; + if (border == 0) { + return Integer.MAX_VALUE; + } else { + return getExtended ? border + borderSize : border; + } + } + return Integer.MAX_VALUE; + } + /** * Setup the plot border for a world (usually done when the world is created). */ @@ -1192,6 +1236,16 @@ public boolean hasWorldBorder() { return worldBorder; } + /** + * Get the "extra border" size of the plot area. + * + * @return Plot area extra border size + * @since 7.2.0 + */ + public int getBorderSize() { + return borderSize; + } + /** * Get whether plot signs are allowed or not. * diff --git a/Core/src/main/java/com/plotsquared/core/plot/PlotId.java b/Core/src/main/java/com/plotsquared/core/plot/PlotId.java index b0ce3ffed0..3b7ec0b86d 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/PlotId.java +++ b/Core/src/main/java/com/plotsquared/core/plot/PlotId.java @@ -26,8 +26,8 @@ import java.util.NoSuchElementException; /** - * Plot (X,Y) tuples for plot locations - * within a plot area + * The PlotId class represents a Plot's x and y coordinates within a {@link PlotArea}. PlotId x,y values do not correspond to Block locations. + * A PlotId instance can be created using the {@link #of(int, int)} method or parsed from a string using the {@link #fromString(String)} method. */ public final class PlotId { @@ -36,10 +36,10 @@ public final class PlotId { private final int hash; /** - * PlotId class (PlotId x,y values do not correspond to Block locations) + * Constructs a new PlotId with the given x and y coordinates. * - * @param x The plot x coordinate - * @param y The plot y coordinate + * @param x the x-coordinate of the plot + * @param y the y-coordinate of the plot */ private PlotId(final int x, final int y) { this.x = x; @@ -48,11 +48,11 @@ private PlotId(final int x, final int y) { } /** - * Create a new plot ID instance + * Returns a new PlotId instance with the specified x and y coordinates. * - * @param x The plot x coordinate - * @param y The plot y coordinate - * @return a new PlotId at x,y + * @param x the x-coordinate of the plot + * @param y the y-coordinate of the plot + * @return a new PlotId instance with the specified x and y coordinates */ public static @NonNull PlotId of(final int x, final int y) { return new PlotId(x, y); @@ -74,10 +74,13 @@ private PlotId(final int x, final int y) { } /** - * Attempt to parse a plot ID from a string + * Returns a PlotId object from the given string, or null if the string is invalid. + * The string should be in the format "x;y" where x and y are integers. + * The string can also contain any combination of the characters ";_,." + * as delimiters. * - * @param string ID string - * @return Plot ID, or {@code null} if none could be parsed + * @param string the string to parse + * @return a PlotId object parsed from the given string, or null if the string is invalid */ public static @Nullable PlotId fromStringOrNull(final @NonNull String string) { final String[] parts = string.split("[;_,.]"); @@ -95,39 +98,39 @@ private PlotId(final int x, final int y) { return of(x, y); } + /** - * Gets the PlotId from the HashCode
- * Note: Only accurate for small x,z values (short) + * Returns a new PlotId instance from the given hash. * - * @param hash ID hash - * @return Plot ID + * @param hash the hash to unpair + * @return a new PlotId instance */ public static @NonNull PlotId unpair(final int hash) { return PlotId.of(hash >> 16, hash & 0xFFFF); } /** - * Get the ID X component + * Returns the x-coordinate of this Plot ID. * - * @return X component + * @return the x-coordinate of this Plot ID */ public int getX() { return this.x; } /** - * Get the ID Y component + * Returns the y-coordinate of this Plot ID. * - * @return Y component + * @return the y-coordinate of this Plot ID */ public int getY() { return this.y; } /** - * Get the next plot ID for claiming purposes + * Returns the next Plot ID for claiming purposes based on the current Plot ID. * - * @return Next plot ID + * @return the next Plot ID */ public @NonNull PlotId getNextId() { final int absX = Math.abs(x); @@ -159,10 +162,11 @@ public int getY() { } /** - * Get the PlotId in a relative direction + * Returns a new Plot ID in the specified relative direction based on the + * current Plot ID. * - * @param direction Direction - * @return Relative plot ID + * @param direction the direction in which to get the relative Plot ID + * @return the relative Plot ID */ public @NonNull PlotId getRelative(final @NonNull Direction direction) { return switch (direction) { @@ -193,10 +197,11 @@ public boolean equals(final Object obj) { } /** - * Get a String representation of the plot ID where the - * components are separated by ";" + * Returns a string representation of this Plot ID in the format "x;y". + * + *

The format is {@code x + ";" + y} * - * @return {@code x + ";" + y} + * @return a string representation of this Plot ID */ @Override public @NonNull String toString() { @@ -204,41 +209,40 @@ public boolean equals(final Object obj) { } /** - * Get a String representation of the plot ID where the - * components are separated by a specified string + * Returns a string representation of this Plot ID with the specified separator. + *

+ * The format is {@code x + separator + y} * - * @param separator Separator - * @return {@code x + separator + y} + * @param separator the separator to use between the X and Y coordinates + * @return a string representation of this Plot ID with the specified separator */ public @NonNull String toSeparatedString(String separator) { return this.getX() + separator + this.getY(); } /** - * Get a String representation of the plot ID where the - * components are separated by "," + * Returns a string representation of this Plot ID in the format "x,y". * - * @return {@code x + "," + y} + * @return a string representation of this Plot ID */ public @NonNull String toCommaSeparatedString() { return this.getX() + "," + this.getY(); } /** - * Get a String representation of the plot ID where the - * components are separated by "_" + * Returns a string representation of this Plot ID in the format "x_y". * - * @return {@code x + "_" + y} + * @return a string representation of this Plot ID */ + public @NonNull String toUnderscoreSeparatedString() { return this.getX() + "_" + this.getY(); } /** - * Get a String representation of the plot ID where the - * components are separated by "-" + * Returns a string representation of this Plot ID in the format "x-y". * - * @return {@code x + "-" + y} + * @return a string representation of this Plot ID */ public @NonNull String toDashSeparatedString() { return this.getX() + "-" + this.getY(); @@ -250,6 +254,10 @@ public int hashCode() { } + /** + * An iterator that iterates over a range of {@link PlotId}s. + * The range is defined by a start and end {@link PlotId}. + */ public static final class PlotRangeIterator implements Iterator, Iterable { private final PlotId start; @@ -265,6 +273,13 @@ private PlotRangeIterator(final @NonNull PlotId start, final @NonNull PlotId end this.y = this.start.getY(); } + /** + * Returns a new {@link PlotRangeIterator} that iterates over the range of Plots between the specified start and end Plots (inclusive). + * + * @param start the starting Plot of the range + * @param end the ending Plot of the range + * @return a new {@link PlotRangeIterator} that iterates over the range of Plots between the specified start and end Plots (inclusive) + */ public static PlotRangeIterator range(final @NonNull PlotId start, final @NonNull PlotId end) { return new PlotRangeIterator(start, end); } diff --git a/Core/src/main/java/com/plotsquared/core/plot/PlotItemStack.java b/Core/src/main/java/com/plotsquared/core/plot/PlotItemStack.java index ea9e5d7de2..68ea240dcc 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/PlotItemStack.java +++ b/Core/src/main/java/com/plotsquared/core/plot/PlotItemStack.java @@ -67,14 +67,25 @@ public ItemType getType() { return this.type; } + /** + * Returns the number of items in this stack. + * Valid values range from 1-255. + * + * @return the amount of items in this stack + */ public int getAmount() { return amount; } + /** + * Returns the given name of this stack of items. The name is displayed when + * hovering over the item. + * + * @return the given name of this stack of items + */ public String getName() { return name; } - public String[] getLore() { return lore; } diff --git a/Core/src/main/java/com/plotsquared/core/plot/PlotModificationManager.java b/Core/src/main/java/com/plotsquared/core/plot/PlotModificationManager.java index ed764444da..3fa8d3dd4b 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/PlotModificationManager.java +++ b/Core/src/main/java/com/plotsquared/core/plot/PlotModificationManager.java @@ -38,7 +38,6 @@ import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.plot.flag.PlotFlag; import com.plotsquared.core.queue.QueueCoordinator; -import com.plotsquared.core.util.PlayerManager; import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.task.TaskTime; import com.sk89q.worldedit.function.pattern.Pattern; @@ -59,6 +58,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -383,13 +383,17 @@ public boolean unlinkPlot(final boolean createRoad, final boolean createSign, fi } if (createSign) { queue.setCompleteTask(() -> TaskManager.runTaskAsync(() -> { - for (Plot current : plots) { - current.getPlotModificationManager().setSign(PlayerManager.resolveName(current.getOwnerAbs()).getComponent( - LocaleHolder.console())); - } - if (whenDone != null) { - TaskManager.runTask(whenDone); - } + List> tasks = plots.stream().map(current -> PlotSquared.platform().playerManager() + .getUsernameCaption(current.getOwnerAbs()) + .thenAccept(caption -> current + .getPlotModificationManager() + .setSign(caption.getComponent(LocaleHolder.console())))) + .toList(); + CompletableFuture.allOf(tasks.toArray(CompletableFuture[]::new)).whenComplete((unused, throwable) -> { + if (whenDone != null) { + TaskManager.runTask(whenDone); + } + }); })); } else if (whenDone != null) { queue.setCompleteTask(whenDone); @@ -891,7 +895,6 @@ public boolean deletePlot(@Nullable PlotPlayer actor, final Runnable whenDone } /** - * /** * Sets components such as border, wall, floor. * (components are generator specific) * diff --git a/Core/src/main/java/com/plotsquared/core/plot/PlotWeather.java b/Core/src/main/java/com/plotsquared/core/plot/PlotWeather.java index 331dc846b8..1c4d8ada1d 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/PlotWeather.java +++ b/Core/src/main/java/com/plotsquared/core/plot/PlotWeather.java @@ -18,9 +18,25 @@ */ package com.plotsquared.core.plot; +/** + * The different types of weather that can be set for a Plot. + */ public enum PlotWeather { + + /** + * Rainy weather conditions + */ RAIN, + /** + * Clear weather conditions + */ CLEAR, + /** + * Use the weather of the world the plot is in + */ WORLD, + /** + * Turn off weather for the plot + */ OFF } diff --git a/Core/src/main/java/com/plotsquared/core/plot/flag/GlobalFlagContainer.java b/Core/src/main/java/com/plotsquared/core/plot/flag/GlobalFlagContainer.java index 734c313965..fe2eb23986 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/flag/GlobalFlagContainer.java +++ b/Core/src/main/java/com/plotsquared/core/plot/flag/GlobalFlagContainer.java @@ -29,6 +29,7 @@ import com.plotsquared.core.plot.flag.implementations.BlockedCmdsFlag; import com.plotsquared.core.plot.flag.implementations.BreakFlag; import com.plotsquared.core.plot.flag.implementations.ChatFlag; +import com.plotsquared.core.plot.flag.implementations.ConcreteHardenFlag; import com.plotsquared.core.plot.flag.implementations.CopperOxideFlag; import com.plotsquared.core.plot.flag.implementations.CoralDryFlag; import com.plotsquared.core.plot.flag.implementations.CropGrowFlag; @@ -41,6 +42,7 @@ import com.plotsquared.core.plot.flag.implementations.DisablePhysicsFlag; import com.plotsquared.core.plot.flag.implementations.DoneFlag; import com.plotsquared.core.plot.flag.implementations.DropProtectionFlag; +import com.plotsquared.core.plot.flag.implementations.EditSignFlag; import com.plotsquared.core.plot.flag.implementations.EntityCapFlag; import com.plotsquared.core.plot.flag.implementations.EntityChangeBlockFlag; import com.plotsquared.core.plot.flag.implementations.ExplosionFlag; @@ -91,6 +93,7 @@ import com.plotsquared.core.plot.flag.implementations.PveFlag; import com.plotsquared.core.plot.flag.implementations.PvpFlag; import com.plotsquared.core.plot.flag.implementations.RedstoneFlag; +import com.plotsquared.core.plot.flag.implementations.SculkSensorInteractFlag; import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; import com.plotsquared.core.plot.flag.implementations.SnowFormFlag; import com.plotsquared.core.plot.flag.implementations.SnowMeltFlag; @@ -141,6 +144,7 @@ private GlobalFlagContainer() { this.addFlag(BeaconEffectsFlag.BEACON_EFFECT_TRUE); this.addFlag(BlockIgnitionFlag.BLOCK_IGNITION_TRUE); this.addFlag(ChatFlag.CHAT_FLAG_TRUE); + this.addFlag(ConcreteHardenFlag.CONCRETE_HARDEN_TRUE); this.addFlag(CopperOxideFlag.COPPER_OXIDE_FALSE); this.addFlag(CoralDryFlag.CORAL_DRY_FALSE); this.addFlag(CropGrowFlag.CROP_GROW_TRUE); @@ -150,6 +154,7 @@ private GlobalFlagContainer() { this.addFlag(DeviceInteractFlag.DEVICE_INTERACT_FALSE); this.addFlag(DisablePhysicsFlag.DISABLE_PHYSICS_FALSE); this.addFlag(DropProtectionFlag.DROP_PROTECTION_FALSE); + this.addFlag(EditSignFlag.EDIT_SIGN_FALSE); this.addFlag(EntityChangeBlockFlag.ENTITY_CHANGE_BLOCK_FALSE); this.addFlag(ExplosionFlag.EXPLOSION_FALSE); this.addFlag(ForcefieldFlag.FORCEFIELD_FALSE); @@ -172,6 +177,7 @@ private GlobalFlagContainer() { this.addFlag(MobBreakFlag.MOB_BREAK_FALSE); this.addFlag(MobPlaceFlag.MOB_PLACE_FALSE); this.addFlag(MiscInteractFlag.MISC_INTERACT_FALSE); + this.addFlag(SculkSensorInteractFlag.SCULK_SENSOR_INTERACT_FALSE); this.addFlag(MiscPlaceFlag.MISC_PLACE_FALSE); this.addFlag(MycelGrowFlag.MYCEL_GROW_TRUE); this.addFlag(NotifyEnterFlag.NOTIFY_ENTER_FALSE); diff --git a/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/ConcreteHardenFlag.java b/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/ConcreteHardenFlag.java new file mode 100644 index 0000000000..b68b788b1f --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/ConcreteHardenFlag.java @@ -0,0 +1,39 @@ +/* + * PlotSquared, a land and world management plugin for Minecraft. + * Copyright (C) IntellectualSites + * Copyright (C) IntellectualSites team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.plot.flag.implementations; + +import com.plotsquared.core.configuration.caption.TranslatableCaption; +import com.plotsquared.core.plot.flag.types.BooleanFlag; +import org.checkerframework.checker.nullness.qual.NonNull; + +public class ConcreteHardenFlag extends BooleanFlag { + + public static final ConcreteHardenFlag CONCRETE_HARDEN_TRUE = new ConcreteHardenFlag(true); + public static final ConcreteHardenFlag CONCRETE_HARDEN_FALSE = new ConcreteHardenFlag(false); + + private ConcreteHardenFlag(boolean value) { + super(value, TranslatableCaption.of("flags.flag_description_concrete_harden")); + } + + @Override + protected ConcreteHardenFlag flagOf(@NonNull Boolean value) { + return value ? CONCRETE_HARDEN_TRUE : CONCRETE_HARDEN_FALSE; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/EditSignFlag.java b/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/EditSignFlag.java new file mode 100644 index 0000000000..a4a0f628d0 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/EditSignFlag.java @@ -0,0 +1,41 @@ +/* + * PlotSquared, a land and world management plugin for Minecraft. + * Copyright (C) IntellectualSites + * Copyright (C) IntellectualSites team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.plot.flag.implementations; + +import com.plotsquared.core.configuration.caption.TranslatableCaption; +import com.plotsquared.core.plot.flag.types.BooleanFlag; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * @since TODO + */ +public class EditSignFlag extends BooleanFlag { + public static final EditSignFlag EDIT_SIGN_TRUE = new EditSignFlag(true); + public static final EditSignFlag EDIT_SIGN_FALSE = new EditSignFlag(false); + + private EditSignFlag(final boolean value) { + super(value, TranslatableCaption.of("flags.flag_description_edit_sign")); + } + + @Override + protected EditSignFlag flagOf(@NonNull final Boolean value) { + return value ? EDIT_SIGN_TRUE : EDIT_SIGN_FALSE; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/SculkSensorInteractFlag.java b/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/SculkSensorInteractFlag.java new file mode 100644 index 0000000000..6ac1dafaab --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/plot/flag/implementations/SculkSensorInteractFlag.java @@ -0,0 +1,39 @@ +/* + * PlotSquared, a land and world management plugin for Minecraft. + * Copyright (C) IntellectualSites + * Copyright (C) IntellectualSites team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.plot.flag.implementations; + +import com.plotsquared.core.configuration.caption.TranslatableCaption; +import com.plotsquared.core.plot.flag.types.BooleanFlag; +import org.checkerframework.checker.nullness.qual.NonNull; + +public class SculkSensorInteractFlag extends BooleanFlag { + + public static final SculkSensorInteractFlag SCULK_SENSOR_INTERACT_TRUE = new SculkSensorInteractFlag(true); + public static final SculkSensorInteractFlag SCULK_SENSOR_INTERACT_FALSE = new SculkSensorInteractFlag(false); + + private SculkSensorInteractFlag(boolean value) { + super(value, TranslatableCaption.of("flags.flag_description_sculk_sensor_interact")); + } + + @Override + protected SculkSensorInteractFlag flagOf(@NonNull Boolean value) { + return value ? SCULK_SENSOR_INTERACT_TRUE : SCULK_SENSOR_INTERACT_FALSE; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java b/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java index e51e01eb32..0016ec71df 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java +++ b/Core/src/main/java/com/plotsquared/core/plot/world/DefaultPlotAreaManager.java @@ -29,17 +29,17 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; @Singleton public class DefaultPlotAreaManager implements PlotAreaManager { final PlotArea[] noPlotAreas = new PlotArea[0]; - private final Map plotWorlds = new HashMap<>(); + private final Map plotWorlds = new ConcurrentHashMap<>(); @Override public @NonNull PlotArea[] getAllPlotAreas() { diff --git a/Core/src/main/java/com/plotsquared/core/queue/ChunkCoordinatorBuilder.java b/Core/src/main/java/com/plotsquared/core/queue/ChunkCoordinatorBuilder.java index 4f657f86a7..408cba8d09 100644 --- a/Core/src/main/java/com/plotsquared/core/queue/ChunkCoordinatorBuilder.java +++ b/Core/src/main/java/com/plotsquared/core/queue/ChunkCoordinatorBuilder.java @@ -183,7 +183,7 @@ public ChunkCoordinatorBuilder(@NonNull ChunkCoordinatorFactory chunkCoordinator * Set whether the chunks should be allow to unload after being accessed. This should only be used where the chunks are read from * and then written to from a separate queue where they're consequently unloaded. * - * @param unloadAfter if to unload chuns afterwards + * @param unloadAfter if to unload chunks afterwards * @return this ChunkCoordinatorBuilder instance */ public @NonNull ChunkCoordinatorBuilder unloadAfter(final boolean unloadAfter) { diff --git a/Core/src/main/java/com/plotsquared/core/util/EntityUtil.java b/Core/src/main/java/com/plotsquared/core/util/EntityUtil.java index db2a3fc903..12cd40b11e 100644 --- a/Core/src/main/java/com/plotsquared/core/util/EntityUtil.java +++ b/Core/src/main/java/com/plotsquared/core/util/EntityUtil.java @@ -42,28 +42,14 @@ private EntityUtil() { } private static int capNumeral(final @NonNull String flagName) { - int i; - switch (flagName) { - case "mob-cap": - i = CAP_MOB; - break; - case "hostile-cap": - i = CAP_MONSTER; - break; - case "animal-cap": - i = CAP_ANIMAL; - break; - case "vehicle-cap": - i = CAP_VEHICLE; - break; - case "misc-cap": - i = CAP_MISC; - break; - case "entity-cap": - default: - i = CAP_ENTITY; - } - return i; + return switch (flagName) { + case "mob-cap" -> CAP_MOB; + case "hostile-cap" -> CAP_MONSTER; + case "animal-cap" -> CAP_ANIMAL; + case "vehicle-cap" -> CAP_VEHICLE; + case "misc-cap" -> CAP_MISC; + default -> CAP_ENTITY; + }; } @SuppressWarnings("unchecked") diff --git a/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java b/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java index 4de989601b..128337c208 100644 --- a/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java +++ b/Core/src/main/java/com/plotsquared/core/util/EventDispatcher.java @@ -63,6 +63,7 @@ import com.plotsquared.core.plot.Rating; import com.plotsquared.core.plot.flag.PlotFlag; import com.plotsquared.core.plot.flag.implementations.DeviceInteractFlag; +import com.plotsquared.core.plot.flag.implementations.EditSignFlag; import com.plotsquared.core.plot.flag.implementations.MiscPlaceFlag; import com.plotsquared.core.plot.flag.implementations.MobPlaceFlag; import com.plotsquared.core.plot.flag.implementations.PlaceFlag; @@ -74,6 +75,7 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.world.block.BlockCategories; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import net.kyori.adventure.text.Component; @@ -392,6 +394,12 @@ public boolean checkPlayerBlockEvent( if (player.hasPermission(Permission.PERMISSION_ADMIN_INTERACT_OTHER.toString(), false)) { return true; } + // we check for the EditSignFlag in the PlayerSignOpenEvent again, but we must not cancel the interact event + // or send a message if the flag is true + if (BlockCategories.ALL_SIGNS != null && BlockCategories.ALL_SIGNS.contains(blockType) + && plot.getFlag(EditSignFlag.class)) { + return true; + } if (notifyPerms) { player.sendMessage( TranslatableCaption.of("commandconfig.flag_tutorial_usage"), diff --git a/Core/src/main/java/com/plotsquared/core/util/PlayerManager.java b/Core/src/main/java/com/plotsquared/core/util/PlayerManager.java index 1f4e98a3d7..0d5f66e229 100644 --- a/Core/src/main/java/com/plotsquared/core/util/PlayerManager.java +++ b/Core/src/main/java/com/plotsquared/core/util/PlayerManager.java @@ -28,6 +28,7 @@ import com.plotsquared.core.player.ConsolePlayer; import com.plotsquared.core.player.OfflinePlotPlayer; import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; import com.plotsquared.core.uuid.UUIDMapping; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; @@ -37,6 +38,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.Contract; import java.util.ArrayList; import java.util.Collection; @@ -48,6 +50,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; @@ -169,7 +172,9 @@ public static void getUUIDsFromString( * @return A caption containing either the name, {@code None}, {@code Everyone} or {@code Unknown} * @see #resolveName(UUID, boolean) * @since 6.4.0 + * @deprecated Don't unnecessarily block threads and utilize playerMap - see {@link #getUsernameCaption(UUID)} */ + @Deprecated(since = "7.1.0") public static @NonNull Caption resolveName(final @Nullable UUID owner) { return resolveName(owner, true); } @@ -181,7 +186,9 @@ public static void getUUIDsFromString( * @param blocking If the operation should block the current thread for {@link Settings.UUID#BLOCKING_TIMEOUT} milliseconds * @return A caption containing either the name, {@code None}, {@code Everyone} or {@code Unknown} * @since 6.4.0 + * @deprecated Don't unnecessarily block threads and utilize playerMap - see {@link #getUsernameCaption(UUID)} */ + @Deprecated(since = "7.1.0") public static @NonNull Caption resolveName(final @Nullable UUID owner, final boolean blocking) { if (owner == null) { return TranslatableCaption.of("info.none"); @@ -211,6 +218,50 @@ public static void getUUIDsFromString( return StaticCaption.of(name); } + /** + * Resolves a UUID to a formatted {@link Caption} representing the player behind the UUID. + * Returns a {@link CompletableFuture} instead of a plain {@link UUID} as this method may query the + * {@link com.plotsquared.core.uuid.UUIDPipeline ImpromptuUUIDPipeline}. + *
+ * Special Cases: + *

    + *
  • {@code null}: Resolves to a {@link TranslatableCaption} with the key {@code info.none}
  • + *
  • {@link DBFunc#EVERYONE}: Resolves to a {@link TranslatableCaption} with the key {@code info.everyone}
  • + *
  • {@link DBFunc#SERVER}: Resolves to a {@link TranslatableCaption} with the key {@code info.server}
  • + *
+ *
+ * Otherwise, if the UUID is a valid UUID and not reserved by PlotSquared itself, this method first attempts to query the + * online players ({@link #getPlayerIfExists(UUID)}) for the specific UUID. + * If no online player was found for that UUID, the {@link com.plotsquared.core.uuid.UUIDPipeline ImpromptuUUIDPipeline} is + * queried to retrieve the known username + * + * @param uuid The UUID of the player (for example provided by {@link Plot#getOwner()} + * @return A CompletableFuture resolving to a Caption representing the players name of the uuid + * @since 7.1.0 + */ + @Contract("_->!null") + public @NonNull CompletableFuture getUsernameCaption(@Nullable UUID uuid) { + if (uuid == null) { + return CompletableFuture.completedFuture(TranslatableCaption.of("info.none")); + } + if (uuid.equals(DBFunc.EVERYONE)) { + return CompletableFuture.completedFuture(TranslatableCaption.of("info.everyone")); + } + if (uuid.equals(DBFunc.SERVER)) { + return CompletableFuture.completedFuture(TranslatableCaption.of("info.server")); + } + P player = getPlayerIfExists(uuid); + if (player != null) { + return CompletableFuture.completedFuture(StaticCaption.of(player.getName())); + } + return PlotSquared.get().getImpromptuUUIDPipeline().getNames(Collections.singleton(uuid)).thenApply(mapping -> { + if (mapping.isEmpty()) { + return TranslatableCaption.of("info.unknown"); + } + return StaticCaption.of(mapping.get(0).username()); + }); + } + /** * Remove a player from the player map * diff --git a/Core/src/main/java/com/plotsquared/core/util/SchematicHandler.java b/Core/src/main/java/com/plotsquared/core/util/SchematicHandler.java index c3f75e6491..93e8856080 100644 --- a/Core/src/main/java/com/plotsquared/core/util/SchematicHandler.java +++ b/Core/src/main/java/com/plotsquared/core/util/SchematicHandler.java @@ -83,6 +83,7 @@ import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.channels.Channels; @@ -144,7 +145,7 @@ public static void upload( } final URL url; try { - url = new URL(Settings.Web.URL + "?key=" + uuid + "&type=" + extension); + url = URI.create(Settings.Web.URL + "?key=" + uuid + "&type=" + extension).toURL(); } catch (MalformedURLException e) { e.printStackTrace(); whenDone.run(); @@ -153,7 +154,7 @@ public static void upload( TaskManager.runTaskAsync(() -> { try { String boundary = Long.toHexString(System.currentTimeMillis()); - URLConnection con = new URL(website).openConnection(); + URLConnection con = URI.create(website).toURL().openConnection(); con.setDoOutput(true); con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); try (OutputStream output = con.getOutputStream(); @@ -498,9 +499,10 @@ public Schematic getSchematic(@NonNull InputStream is) { public List getSaves(UUID uuid) { String rawJSON; try { - String website = Settings.Web.URL + "list.php?" + uuid.toString(); - URL url = new URL(website); - URLConnection connection = new URL(url.toString()).openConnection(); + URLConnection connection = URI.create( + Settings.Web.URL + "list.php?" + uuid.toString()) + .toURL() + .openConnection(); connection.setRequestProperty("User-Agent", "Mozilla/5.0"); try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { rawJSON = reader.lines().collect(Collectors.joining()); diff --git a/Core/src/main/java/com/plotsquared/core/util/TabCompletions.java b/Core/src/main/java/com/plotsquared/core/util/TabCompletions.java index b3324f5490..c9d1b3037f 100644 --- a/Core/src/main/java/com/plotsquared/core/util/TabCompletions.java +++ b/Core/src/main/java/com/plotsquared/core/util/TabCompletions.java @@ -173,7 +173,7 @@ private TabCompletions() { return Collections.emptyList(); } final List commands = new ArrayList<>(); - for (int i = offset; i < highestLimit && (offset - i + amountLimit) > 0; i++) { + for (int i = offset; i <= highestLimit && (offset - i + amountLimit) > 0; i++) { commands.add(String.valueOf(i)); } return asCompletions(commands.toArray(new String[0])); diff --git a/Core/src/main/java/com/plotsquared/core/util/placeholders/PlaceholderRegistry.java b/Core/src/main/java/com/plotsquared/core/util/placeholders/PlaceholderRegistry.java index ed62b87996..6e379df947 100644 --- a/Core/src/main/java/com/plotsquared/core/util/placeholders/PlaceholderRegistry.java +++ b/Core/src/main/java/com/plotsquared/core/util/placeholders/PlaceholderRegistry.java @@ -47,6 +47,7 @@ import java.util.Map; import java.util.TimeZone; import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; /** @@ -109,9 +110,9 @@ private void registerDefault() { if (plotOwner == null) { return legacyComponent(TranslatableCaption.of("generic.generic_unowned"), player); } - try { - return PlayerManager.resolveName(plotOwner, false).getComponent(player); + return PlotSquared.platform().playerManager().getUsernameCaption(plotOwner) + .get(Settings.UUID.BLOCKING_TIMEOUT, TimeUnit.MILLISECONDS).getComponent(player); } catch (final Exception ignored) { } return legacyComponent(TranslatableCaption.of("info.unknown"), player); @@ -187,6 +188,7 @@ private void registerDefault() { } }); this.createPlaceholder("currentplot_biome", (player, plot) -> plot.getBiomeSynchronous().toString()); + this.createPlaceholder("currentplot_size", (player, plot) -> String.valueOf(plot.getConnectedPlots().size())); } /** diff --git a/Core/src/main/resources/lang/messages_en.json b/Core/src/main/resources/lang/messages_en.json index 754f6b2856..72e1f72c70 100644 --- a/Core/src/main/resources/lang/messages_en.json +++ b/Core/src/main/resources/lang/messages_en.json @@ -41,7 +41,7 @@ "cluster.cluster_deleted": "Successfully deleted the cluster .", "cluster.cluster_resized": "Successfully resized the cluster.", "cluster.cluster_added_user": "Successfully added user to the cluster.", - "cluster.cannot_kick_player": "You cannot kick that player: ", + "cluster.cannot_kick_player": "You cannot kick the player .", "cluster.cluster_invited": "You have been invited to the following cluster: .", "cluster.cluster_removed": "You have been removed from cluster: .", "cluster.cluster_kicked_user": "Successfully kicked the user from the cluster.", @@ -124,7 +124,7 @@ "economy.cannot_afford_merge": "You cannot afford to merge the plots. It costs .", "economy.added_balance": " has been added to your balance.", "economy.removed_balance": " has been taken from your balance.", - "economy.removed_granted_plot": "You used plot grant(s), you've got left.", + "economy.removed_granted_plot": "You used plot grant(s), you've got left.", "setup.choose_generator": "What generator do you want?", "setup.setup_not_started": "No setup started.", "setup.setup_init": "Usage: /plot setup ", @@ -411,6 +411,8 @@ "deny.no_enter": "You are denied from the plot and therefore not allowed to enter.", "deny.you_got_denied": "You are denied from the plot you were previously on, and got teleported to spawn.", "deny.cant_remove_owner": "You can't remove the plot owner.", + "kick.player_not_in_plot": "The player is not on this plot.", + "kick.cannot_kick_player": "You cannot kick the player .", "kick.you_got_kicked": "You got kicked from the plot!", "trusted.trusted_added": "You successfully trusted a user to the plot.", "trusted.plot_removed_user": "Plot of which you were added to has been deleted due to owner inactivity.", @@ -486,7 +488,7 @@ "single.get_position": "Go to the first corner and use: to create position 1.", "single.delete_world_region": "Stop the server and delete: /region.", "single.regeneration_complete": "Regeneration complete.", - "single.worldcreation_location": "World creation settings may be stored in multiple locations:\n - bukkit.yml in your server's root folder.\n - PlotSquared's settings.yml\n - Hyperverse's worlds.yml (or any world management plugin)\n - Stop the server and delete it from these locations.", + "single.worldcreation_location": "World creation settings may be stored in multiple locations:\n - bukkit.yml in your server's root folder.\n - PlotSquared's worlds.yml\n - Hyperverse's worlds.yml (or any world management plugin)\n - Stop the server and delete it from these locations.", "legacyconfig.legacy_config_found": "A legacy configuration file was detected. Conversion will be attempted.", "legacyconfig.legacy_config_backup": "A copy of worlds.yml has been saved in the file worlds.yml.old.", "legacyconfig.legacy_config_replaced": " has been replaced with ", @@ -549,9 +551,11 @@ "flags.flag_description_block_burn": "Set to `true` to allow blocks to burn within the plot.", "flags.flag_description_block_ignition": "Set to `false` to prevent blocks from igniting within the plot.", "flags.flag_description_break": "Define a list of materials players should be able to break even when they aren't added to the plot.", + "flags.flag_description_concrete_harden": "Set to `false` to disable concrete powder forming to concrete with water.", "flags.flag_description_device_interact": "Set to `true` to allow devices to be interacted with in the plot.", "flags.flag_description_disable_physics": "Set to `true` to disable block physics in the plot.", "flags.flag_description_drop_protection": "Set to `true` to prevent dropped items from being picked up by non-members of the plot.", + "flags.flag_description_edit_sign": "Set to `true` to allow editing signs in the plot.", "flags.flag_description_feed": "Specify an interval in seconds and an optional amount by which the players will be fed (amount is 1 by default).", "flags.flag_description_forcefield": "Set to `true` to enable member forcefield in the plot.", "flags.flag_description_grass_grow": "Set to `false` to prevent grass from growing within the plot.", @@ -572,6 +576,7 @@ "flags.flag_description_misc_break": "Set to `true` to allow guests to break miscellaneous items.", "flags.flag_description_misc_cap": "Set to an integer value to limit the amount of miscellaneous entities on the plot.", "flags.flag_description_misc_interact": "Set to `true` to allow guests to interact with miscellaneous items.", + "flags.flag_description_sculk_sensor_interact": "Set to `true` to allow guests to interact with sculk sensors.", "flags.flag_description_misc_place": "Set to `true` to allow guests to place miscellaneous items.", "flags.flag_description_mob_break": "Set to `true` to allow mobs to break blocks within the plot.", "flags.flag_description_mob_cap": "Set to an integer value to limit the amount of mobs on the plot.", @@ -628,6 +633,8 @@ "flags.flag_error_double": "Flag value must be a decimal number.", "flags.flag_error_music": "Flag value must be a valid music disc ID.", "flags.flag_error_title": "Flag value must be in the format \"A title\" \"The subtitle\".", + "flags.delaying_loading_area_flags": "Delaying loading flags for area `` as WorldEdit is not initialised yet.", + "flags.loading_area_flags": "Loading flags for area: ", "flags.area_flags": "Area flags: ", "flags.road_flags": "Road flags: ", "commands.description.add": "Allow a user to build in a plot while the plot owner is online.", diff --git a/README.md b/README.md index 690cb86d9f..c5e8178925 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,16 @@ is to provide a lag-free and smooth experience. * [Download](https://www.spigotmc.org/resources/77506/) * [Discord](https://discord.gg/intellectualsites) -* [Wiki](https://intellectualsites.github.io/plotsquared-documentation/) +* [Wiki](https://intellectualsites.gitbook.io/plotsquared/) * [Issues](https://github.com/IntellectualSites/PlotSquared/issues) * [Translations](https://intellectualsites.crowdin.com/plotsquared/) * [Contributing](https://github.com/IntellectualSites/.github/blob/main/CONTRIBUTING.md) ### Developer Resources -* [API Documentation](https://intellectualsites.github.io/plotsquared-documentation/api/api-documentation) -* [Event API](https://intellectualsites.github.io/plotsquared-documentation/api/event-api) -* [Flag API](https://intellectualsites.github.io/plotsquared-documentation/api/flag-api) +* [API Documentation](https://intellectualsites.gitbook.io/plotsquared/api/api-documentation) +* [Event API](https://intellectualsites.gitbook.io/plotsquared/api/event-api) +* [Flag API](https://intellectualsites.gitbook.io/plotsquared/api/flag-api) # Official Addons diff --git a/build.gradle.kts b/build.gradle.kts index 7b81441bf0..92c26e34eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,8 @@ import com.diffplug.gradle.spotless.SpotlessPlugin import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin -import java.net.URI +import groovy.json.JsonSlurper import xyz.jpenilla.runpaper.task.RunServer +import java.net.URI plugins { java @@ -17,11 +18,11 @@ plugins { eclipse idea - id("xyz.jpenilla.run-paper") version "2.1.0" + alias(libs.plugins.runPaper) } group = "com.intellectualsites.plotsquared" -version = "7.0.0-SNAPSHOT" +version = "7.2.1-SNAPSHOT" if (!File("$rootDir/.git").exists()) { logger.lifecycle(""" @@ -76,13 +77,9 @@ subprojects { plugin() } - dependencies { - implementation(platform("com.intellectualsites.bom:bom-newest:1.29")) - } - dependencies { // Tests - testImplementation("org.junit.jupiter:junit-jupiter:5.9.3") + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") } plugins.withId("java") { @@ -153,29 +150,34 @@ subprojects { id.set("Sauilitired") name.set("Alexander Söderberg") organization.set("IntellectualSites") + organizationUrl.set("https://github.com/IntellectualSites") } developer { id.set("NotMyFault") name.set("Alexander Brandes") organization.set("IntellectualSites") + organizationUrl.set("https://github.com/IntellectualSites") email.set("contact(at)notmyfault.dev") } developer { id.set("SirYwell") name.set("Hannes Greule") organization.set("IntellectualSites") + organizationUrl.set("https://github.com/IntellectualSites") } developer { id.set("dordsor21") name.set("dordsor21") organization.set("IntellectualSites") + organizationUrl.set("https://github.com/IntellectualSites") } } scm { url.set("https://github.com/IntellectualSites/PlotSquared") - connection.set("scm:https://IntellectualSites@github.com/IntellectualSites/PlotSquared.git") - developerConnection.set("scm:git://github.com/IntellectualSites/PlotSquared.git") + connection.set("scm:git:https://github.com/IntellectualSites/PlotSquared.git") + developerConnection.set("scm:git:git@github.com:IntellectualSites/PlotSquared.git") + tag.set("${project.version}") } issueManagement { @@ -222,14 +224,28 @@ tasks.getByName("jar") { enabled = false } -val supportedVersions = listOf("1.16.5", "1.17", "1.17.1", "1.18.2", "1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4", "1.20") +val supportedVersions = listOf("1.16.5", "1.17.1", "1.18.2", "1.19.4", "1.20.1", "1.20.2") tasks { + register("cacheLatestFaweArtifact") { + val lastSuccessfulBuildUrl = uri("https://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/api/json").toURL() + val artifact = ((JsonSlurper().parse(lastSuccessfulBuildUrl) as Map<*, *>)["artifacts"] as List<*>) + .map { it as Map<*, *> } + .map { it["fileName"] as String } + .first { it -> it.contains("Bukkit") } + project.ext["faweArtifact"] = artifact + } + supportedVersions.forEach { register("runServer-$it") { + dependsOn(getByName("cacheLatestFaweArtifact")) minecraftVersion(it) - pluginJars(*project(":plotsquared-bukkit").getTasksByName("shadowJar", false).map { (it as Jar).archiveFile } + pluginJars(*project(":plotsquared-bukkit").getTasksByName("shadowJar", false) + .map { task -> (task as Jar).archiveFile } .toTypedArray()) jvmArgs("-DPaper.IgnoreJavaVersion=true", "-Dcom.mojang.eula.agree=true") + downloadPlugins { + url("https://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/artifacts/${project.ext["faweArtifact"]}") + } group = "run paper" runDirectory.set(file("run-$it")) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9de835154f..9f77d4d611 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,33 +1,58 @@ [versions] # Platform expectations +paper = "1.20.2-R0.1-SNAPSHOT" guice = "7.0.0" -spotbugs = "4.7.3" +spotbugs = "4.8.0" +checkerqual = "3.39.0" +gson = "2.10" +guava = "31.1-jre" +snakeyaml = "2.0" +adventure = "4.14.0" +adventure-bukkit = "4.3.1" +log4j = "2.19.0" # Plugins -worldedit = "7.2.14" -placeholderapi = "2.11.3" +worldedit = "7.2.17" +fawe = "2.8.1" +placeholderapi = "2.11.5" luckperms = "5.4" -essentialsx = "2.20.0" +essentialsx = "2.20.1" mvdwapi = "3.1.1" # Third party prtree = "2.0.1" aopalliance = "1.0" -cloud-services = "1.8.3" +cloud-services = "1.8.4" arkitektonika = "2.1.2" squirrelid = "0.3.2" +paster = "1.1.5" +bstats = "3.0.2" +paperlib = "1.0.8" +informative-annotations = "1.4" +vault = "1.7.1" +serverlib = "2.3.4" # Gradle plugins shadow = "8.1.1" grgit = "4.1.1" -spotless = "6.19.0" +spotless = "6.22.0" nexus = "1.3.0" +runPaper = "2.2.0" [libraries] # Platform expectations +paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" } guice = { group = "com.google.inject", name = "guice", version.ref = "guice" } guiceassistedinject = { group = "com.google.inject.extensions", name = "guice-assistedinject", version.ref = "guice" } spotbugs = { group = "com.github.spotbugs", name = "spotbugs-annotations", version.ref = "spotbugs" } +checkerqual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerqual" } +gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } +guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } +snakeyaml = { group = "org.yaml", name = "snakeyaml", version.ref = "snakeyaml" } +adventureApi = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" } +adventureMiniMessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" } +adventureBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventure-bukkit" } +log4j = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" } # Plugins worldeditCore = { group = "com.sk89q.worldedit", name = "worldedit-core", version.ref = "worldedit" } @@ -35,6 +60,8 @@ worldeditBukkit = { group = "com.sk89q.worldedit", name = "worldedit-bukkit", ve placeholderapi = { group = "me.clip", name = "placeholderapi", version.ref = "placeholderapi" } luckperms = { group = "net.luckperms", name = "api", version.ref = "luckperms" } essentialsx = { group = "net.essentialsx", name = "EssentialsX", version.ref = "essentialsx" } +faweCore = { group = "com.fastasyncworldedit", name = "FastAsyncWorldEdit-Core", version.ref = "fawe" } +faweBukkit = { group = "com.fastasyncworldedit", name = "FastAsyncWorldEdit-Bukkit", version.ref = "fawe" } # Third party prtree = { group = "com.intellectualsites.prtree", name = "PRTree", version.ref = "prtree" } @@ -43,9 +70,17 @@ cloudServices = { group = "cloud.commandframework", name = "cloud-services", ver mvdwapi = { group = "com.intellectualsites.mvdwplaceholderapi", name = "MVdWPlaceholderAPI", version.ref = "mvdwapi" } squirrelid = { group = "org.enginehub", name = "squirrelid", version.ref = "squirrelid" } arkitektonika = { group = "com.intellectualsites.arkitektonika", name = "Arkitektonika-Client", version.ref = "arkitektonika" } +paster = { group = "com.intellectualsites.paster", name = "Paster", version.ref = "paster" } +bstatsBase = { group = "org.bstats", name = "bstats-base", version.ref = "bstats" } +bstatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" } +informativeAnnotations = { group = "com.intellectualsites.informative-annotations", name = "informative-annotations", version.ref = "informative-annotations" } +paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" } +vault = { group = "com.github.MilkBowl", name = "VaultAPI", version.ref = "vault" } +serverlib = { group = "dev.notmyfault.serverlib", name = "ServerLib", version.ref = "serverlib" } [plugins] shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" } grgit = { id = "org.ajoberstar.grgit", version.ref = "grgit" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } nexus = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus" } +runPaper = { id = "xyz.jpenilla.run-paper", version.ref = "runPaper" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e2..7f93135c49 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0..3fa8f862f7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb43..1aa94a4269 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/javadocfooter.html b/javadocfooter.html index 2588b6b7a4..27be23b00b 100644 --- a/javadocfooter.html +++ b/javadocfooter.html @@ -1,4 +1,4 @@ Javadocs generated for PlotSquared | - Documentation | + Documentation | Visit us on our Discord server :)