mirror of
				https://github.com/IntellectualSites/PlotSquared.git
				synced 2025-10-31 01:23:44 +01:00 
			
		
		
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			fix/4647
			...
			feat/chore
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f2924f8a73 | ||
|   | 670c241d76 | ||
|   | 9c64c2e5fa | ||
|   | fb2165e4e0 | ||
|   | 30867354cc | ||
|   | 36de196d3d | ||
|   | 60f81c4732 | ||
|   | d9a46635fd | ||
|   | 424806c5f4 | 
							
								
								
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -29,18 +29,18 @@ jobs: | ||||
|           fi | ||||
|       - name: Publish Release | ||||
|         if: ${{ runner.os == 'Linux' && env.STATUS == 'release' && github.event_name == 'push' && github.ref == 'refs/heads/main'}} | ||||
|         run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache | ||||
|         run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository | ||||
|         env: | ||||
|           ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.CENTRAL_USERNAME }} | ||||
|           ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.CENTRAL_PASSWORD }} | ||||
|           ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} | ||||
|           ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} | ||||
|           ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} | ||||
|           ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} | ||||
|       - name: Publish Snapshot | ||||
|         if: ${{ runner.os == 'Linux' && env.STATUS != 'release' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} | ||||
|         run: ./gradlew publishAllPublicationsToMavenCentralRepository | ||||
|         run: ./gradlew publishToSonatype | ||||
|         env: | ||||
|           ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.CENTRAL_USERNAME }} | ||||
|           ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.CENTRAL_PASSWORD }} | ||||
|           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'}} | ||||
|         uses: cpina/github-action-push-to-another-repository@main | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar | ||||
| repositories { | ||||
|     maven { | ||||
|         name = "PlaceholderAPI" | ||||
|         url = uri("https://repo.extendedclip.com/releases/") | ||||
|         url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/") | ||||
|     } | ||||
|  | ||||
|     maven { | ||||
|   | ||||
| @@ -134,7 +134,7 @@ public class BlockEventListener117 implements Listener { | ||||
|     public void onBlockFertilize(BlockFertilizeEvent event) { | ||||
|         Block block = event.getBlock(); | ||||
|         List<org.bukkit.block.BlockState> blocks = event.getBlocks(); | ||||
|         Location location = BukkitUtil.adapt(block.getLocation()); | ||||
|         Location location = BukkitUtil.adapt(blocks.get(0).getLocation()); | ||||
|  | ||||
|         PlotArea area = location.getPlotArea(); | ||||
|         if (area == null) { | ||||
|   | ||||
| @@ -593,18 +593,14 @@ public class PlayerEventListener implements Listener { | ||||
|                     return; | ||||
|                 } | ||||
|                 Plot plot = area.getPlot(location); | ||||
|                 if (plot != null && !plot.equals(lastPlot)) { | ||||
|                 if (plot != null) { | ||||
|                     final boolean result = DenyTeleportFlag.allowsTeleport(pp, plot); | ||||
|                     // there is one possibility to still allow teleportation: | ||||
|                     // to is identical to the plot's home location, and untrusted-visit is true | ||||
|                     // i.e. untrusted-visit can override deny-teleport | ||||
|                     // this is acceptable, because otherwise it wouldn't make sense to have both flags set | ||||
|                     if (result || (plot.getFlag(UntrustedVisitFlag.class) && plot.getHomeSynchronous().equals(BukkitUtil.adaptComplete(to)))) { | ||||
|                         // returns false if the player is not allowed to enter the plot (if they are denied, for example) | ||||
|                         // don't let the move event cancel the entry after teleport, but rather catch and cancel early (#4647) | ||||
|                         if (!plotListener.plotEntry(pp, plot)) { | ||||
|                             event.setCancelled(true); | ||||
|                         } | ||||
|                         plotListener.plotEntry(pp, plot); | ||||
|                     } else { | ||||
|                         pp.sendMessage( | ||||
|                                 TranslatableCaption.of("deny.no_enter"), | ||||
|   | ||||
| @@ -44,7 +44,6 @@ import java.util.List; | ||||
| import java.util.Queue; | ||||
| import java.util.concurrent.LinkedBlockingQueue; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.TimeoutException; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| @@ -227,22 +226,16 @@ public final class BukkitChunkCoordinator extends ChunkCoordinator { | ||||
|             loadingChunks.incrementAndGet(); | ||||
|             PaperLib | ||||
|                     .getChunkAtAsync(this.bukkitWorld, chunk.getX(), chunk.getZ(), shouldGen, true) | ||||
|                     .orTimeout(10L, TimeUnit.SECONDS) | ||||
|                     .completeOnTimeout(null, 10L, TimeUnit.SECONDS) | ||||
|                     .whenComplete((chunkObject, throwable) -> { | ||||
|                         loadingChunks.decrementAndGet(); | ||||
|                         if (throwable != null) { | ||||
|                             if (throwable instanceof TimeoutException) { | ||||
|                                 LOGGER.warn("Timed out awaiting chunk load {}", chunk); | ||||
|                                 this.requestedChunks.offer(chunk); | ||||
|                             } else { | ||||
|                                 LOGGER.error("Failed to load chunk {}", chunk, throwable); | ||||
|                                 // We want one less because this couldn't be processed | ||||
|                                 this.expectedSize.decrementAndGet(); | ||||
|                             } | ||||
|                             LOGGER.error("Failed to load chunk {}", chunk, throwable); | ||||
|                             // We want one less because this couldn't be processed | ||||
|                             this.expectedSize.decrementAndGet(); | ||||
|                         } else if (chunkObject == null) { | ||||
|                             if (shouldGen) { | ||||
|                                 LOGGER.error("Null chunk returned for chunk at {}", chunk); | ||||
|                             } | ||||
|                             LOGGER.warn("Timed out awaiting chunk load {}", chunk); | ||||
|                             this.requestedChunks.offer(chunk); | ||||
|                         } else if (PlotSquared.get().isMainThread(Thread.currentThread())) { | ||||
|                             this.processChunk(chunkObject); | ||||
|                         } else { | ||||
|   | ||||
| @@ -713,8 +713,6 @@ public class PlotSquared { | ||||
|                 case CREATION_DATE_TIMESTAMP -> toReturn.addAll(sortPlotsByTimestamp(map.get(area))); | ||||
|                 case DISTANCE_FROM_ORIGIN -> toReturn.addAll(sortPlotsByHash(map.get(area))); | ||||
|                 case LAST_MODIFIED -> toReturn.addAll(sortPlotsByModified(map.get(area))); | ||||
|                 default -> { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return toReturn; | ||||
|   | ||||
| @@ -44,13 +44,8 @@ import org.apache.logging.log4j.Logger; | ||||
| import java.util.Arrays; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.Optional; | ||||
| import java.util.concurrent.CompletableFuture; | ||||
|  | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
|  | ||||
| /** | ||||
|  * PlotSquared command class. | ||||
|  */ | ||||
| @@ -152,7 +147,8 @@ public class MainCommand extends Command { | ||||
|                 try { | ||||
|                     injector.getInstance(command); | ||||
|                 } catch (final Exception e) { | ||||
|                     LOGGER.error("Failed to register command {}", command.getCanonicalName(), e); | ||||
|                     LOGGER.error("Failed to register command {}", command.getCanonicalName()); | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -240,171 +236,111 @@ public class MainCommand extends Command { | ||||
|             RunnableVal3<Command, Runnable, Runnable> confirm, | ||||
|             RunnableVal2<Command, CommandResult> whenDone | ||||
|     ) { | ||||
|         prepareArguments(new CommandExecutionData(player, args, confirm, whenDone, null)) | ||||
|                 .thenCompose(executionData -> { | ||||
|                     if (executionData.isEmpty()) { | ||||
|                         return CompletableFuture.completedFuture(false); | ||||
|         // Optional command scope // | ||||
|         Location location = null; | ||||
|         Plot plot = null; | ||||
|         boolean tp = false; | ||||
|         if (args.length >= 2) { | ||||
|             PlotArea area = player.getApplicablePlotArea(); | ||||
|             Plot newPlot = Plot.fromString(area, args[0]); | ||||
|             if (newPlot != null && (player instanceof ConsolePlayer || newPlot.getArea() | ||||
|                     .equals(area) || player.hasPermission(Permission.PERMISSION_ADMIN) | ||||
|                     || player.hasPermission(Permission.PERMISSION_ADMIN_AREA_SUDO)) | ||||
|                     && !newPlot.isDenied(player.getUUID())) { | ||||
|                 final Location newLoc; | ||||
|                 if (newPlot.getArea() instanceof SinglePlotArea) { | ||||
|                     newLoc = newPlot.isLoaded() ? newPlot.getCenterSynchronous() : Location.at("", 0, 0, 0); | ||||
|                 } else { | ||||
|                     newLoc = newPlot.getCenterSynchronous(); | ||||
|                 } | ||||
|                 if (player.canTeleport(newLoc)) { | ||||
|                     // Save meta | ||||
|                     try (final MetaDataAccess<Location> locationMetaDataAccess | ||||
|                                  = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) { | ||||
|                         location = locationMetaDataAccess.get().orElse(null); | ||||
|                         locationMetaDataAccess.set(newLoc); | ||||
|                     } | ||||
|                     var data = executionData.get(); | ||||
|                     try { | ||||
|                         return super.execute(data.player(), data.args(), data.confirm(), data.whenDone()); | ||||
|                     } catch (CommandException e) { | ||||
|                         throw e; | ||||
|                     } catch (Throwable e) { | ||||
|                         LOGGER.error("A error occurred while executing plot command", e); | ||||
|                         String message = e.getMessage(); | ||||
|                         if (message != null) { | ||||
|                             data.player().sendMessage( | ||||
|                                     TranslatableCaption.of("errors.error"), | ||||
|                                     TagResolver.resolver("value", Tag.inserting(Component.text(message))) | ||||
|                             ); | ||||
|                         } else { | ||||
|                             data.player().sendMessage( | ||||
|                                     TranslatableCaption.of("errors.error_console")); | ||||
|                         } | ||||
|                     } finally { | ||||
|                         if (data.postCommandData() != null) { | ||||
|                             resetCommandScope(data.player(), data.postCommandData()); | ||||
|                         } | ||||
|                     try (final MetaDataAccess<Plot> plotMetaDataAccess | ||||
|                                  = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { | ||||
|                         plot = plotMetaDataAccess.get().orElse(null); | ||||
|                         plotMetaDataAccess.set(newPlot); | ||||
|                     } | ||||
|                     return CompletableFuture.completedFuture(true); | ||||
|                 }); | ||||
|                     tp = true; | ||||
|                 } else { | ||||
|                     player.sendMessage(TranslatableCaption.of("border.denied")); | ||||
|                     return CompletableFuture.completedFuture(false); | ||||
|                 } | ||||
|                 // Trim command | ||||
|                 args = Arrays.copyOfRange(args, 1, args.length); | ||||
|             } | ||||
|             if (args.length >= 2 && !args[0].isEmpty() && args[0].charAt(0) == '-') { | ||||
|                 if ("f".equals(args[0].substring(1))) { | ||||
|                     confirm = new RunnableVal3<>() { | ||||
|                         @Override | ||||
|                         public void run(Command cmd, Runnable success, Runnable failure) { | ||||
|                             if (area != null && PlotSquared.platform().econHandler().isEnabled(area)) { | ||||
|                                 PlotExpression priceEval = | ||||
|                                         area.getPrices().get(cmd.getFullId()); | ||||
|                                 double price = priceEval != null ? priceEval.evaluate(0d) : 0d; | ||||
|                                 if (price != 0d | ||||
|                                         && PlotSquared.platform().econHandler().getMoney(player) < price) { | ||||
|                                     if (failure != null) { | ||||
|                                         failure.run(); | ||||
|                                     } | ||||
|                                     return; | ||||
|                                 } | ||||
|                             } | ||||
|                             if (success != null) { | ||||
|                                 success.run(); | ||||
|                             } | ||||
|                         } | ||||
|                     }; | ||||
|                     args = Arrays.copyOfRange(args, 1, args.length); | ||||
|                 } else { | ||||
|                     player.sendMessage(TranslatableCaption.of("errors.invalid_command_flag")); | ||||
|                     return CompletableFuture.completedFuture(false); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         try { | ||||
|             super.execute(player, args, confirm, whenDone); | ||||
|         } catch (CommandException e) { | ||||
|             throw e; | ||||
|         } catch (Throwable e) { | ||||
|             e.printStackTrace(); | ||||
|             String message = e.getMessage(); | ||||
|             if (message != null) { | ||||
|                 player.sendMessage( | ||||
|                         TranslatableCaption.of("errors.error"), | ||||
|                         TagResolver.resolver("value", Tag.inserting(Component.text(message))) | ||||
|                 ); | ||||
|             } else { | ||||
|                 player.sendMessage( | ||||
|                         TranslatableCaption.of("errors.error_console")); | ||||
|             } | ||||
|         } | ||||
|         // Reset command scope // | ||||
|         if (tp && !(player instanceof ConsolePlayer)) { | ||||
|             try (final MetaDataAccess<Location> locationMetaDataAccess | ||||
|                          = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) { | ||||
|                 if (location == null) { | ||||
|                     locationMetaDataAccess.remove(); | ||||
|                 } else { | ||||
|                     locationMetaDataAccess.set(location); | ||||
|                 } | ||||
|             } | ||||
|             try (final MetaDataAccess<Plot> plotMetaDataAccess | ||||
|                          = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { | ||||
|                 if (plot == null) { | ||||
|                     plotMetaDataAccess.remove(); | ||||
|                 } else { | ||||
|                     plotMetaDataAccess.set(plot); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return CompletableFuture.completedFuture(true); | ||||
|     } | ||||
|  | ||||
|     private CompletableFuture<Optional<CommandExecutionData>> prepareArguments(CommandExecutionData data) { | ||||
|         if (data.args().length >= 2) { | ||||
|             PlotArea area = data.player().getApplicablePlotArea(); | ||||
|             Plot newPlot = Plot.fromString(area, data.args()[0]); | ||||
|             return preparePlotArgument(newPlot, data, area) | ||||
|                     .thenApply(d -> d.flatMap(x -> prepareFlagArgument(x, area))); | ||||
|         } else { | ||||
|             return CompletableFuture.completedFuture(Optional.of(data)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private CompletableFuture<Optional<CommandExecutionData>> preparePlotArgument(@Nullable Plot newPlot, | ||||
|                                                                         @Nonnull CommandExecutionData data, | ||||
|                                                                                   @Nullable PlotArea area) { | ||||
|         if (newPlot != null && (data.player() instanceof ConsolePlayer | ||||
|                 || (area != null && area.equals(newPlot.getArea())) | ||||
|                 || data.player().hasPermission(Permission.PERMISSION_ADMIN) | ||||
|                 || data.player().hasPermission(Permission.PERMISSION_ADMIN_AREA_SUDO)) | ||||
|                 && !newPlot.isDenied(data.player().getUUID())) { | ||||
|             return fetchPlotCenterLocation(newPlot) | ||||
|                     .thenApply(newLoc -> { | ||||
|                         if (!data.player().canTeleport(newLoc)) { | ||||
|                             data.player().sendMessage(TranslatableCaption.of("border.denied")); | ||||
|                             return Optional.empty(); | ||||
|                         } | ||||
|                         // Save meta | ||||
|                         var originalCommandMeta = setCommandScope(data.player(), new TemporaryCommandMeta(newLoc, newPlot)); | ||||
|                         return Optional.of(new CommandExecutionData( | ||||
|                                 data.player(), | ||||
|                                 Arrays.copyOfRange(data.args(), 1, data.args().length), // Trimmed command | ||||
|                                 data.confirm(), | ||||
|                                 data.whenDone(), | ||||
|                                 originalCommandMeta | ||||
|                         )); | ||||
|                     }); | ||||
|         } | ||||
|         return CompletableFuture.completedFuture(Optional.of(data)); | ||||
|     } | ||||
|  | ||||
|     private Optional<CommandExecutionData> prepareFlagArgument(@Nonnull CommandExecutionData data, @Nonnull PlotArea area) { | ||||
|         if (data.args().length >= 2 && !data.args()[0].isEmpty() && data.args()[0].charAt(0) == '-') { | ||||
|             if ("f".equals(data.args()[0].substring(1))) { | ||||
|                 return Optional.of(new CommandExecutionData( | ||||
|                         data.player(), | ||||
|                         Arrays.copyOfRange(data.args(), 1, data.args().length), // Trimmed command | ||||
|                         createForcedConfirmation(data.player(), area), | ||||
|                         data.whenDone(), | ||||
|                         data.postCommandData() | ||||
|                 )); | ||||
|             } else { | ||||
|                 data.player().sendMessage(TranslatableCaption.of("errors.invalid_command_flag")); | ||||
|                 return Optional.empty(); | ||||
|             } | ||||
|         } | ||||
|         return Optional.of(data); | ||||
|     } | ||||
|  | ||||
|     private CompletableFuture<Location> fetchPlotCenterLocation(Plot plot) { | ||||
|         if (plot.getArea() instanceof SinglePlotArea && !plot.isLoaded()) { | ||||
|             return CompletableFuture.completedFuture(Location.at("", 0, 0, 0)); | ||||
|         } | ||||
|         CompletableFuture<Location> future = new CompletableFuture<>(); | ||||
|         plot.getCenter(future::complete); | ||||
|         return future; | ||||
|     } | ||||
|  | ||||
|     private @Nonnull RunnableVal3<Command, Runnable, Runnable> createForcedConfirmation(@Nonnull PlotPlayer<?> player, | ||||
|                                                                                         @Nullable PlotArea area) { | ||||
|         return new RunnableVal3<>() { | ||||
|             @Override | ||||
|             public void run(Command cmd, Runnable success, Runnable failure) { | ||||
|                 if (area != null && PlotSquared.platform().econHandler().isEnabled(area) | ||||
|                         && Optional.of(area.getPrices().get(cmd.getFullId())) | ||||
|                         .map(priceEval -> priceEval.evaluate(0d)) | ||||
|                         .filter(price -> price != 0d) | ||||
|                         .filter(price -> PlotSquared.platform().econHandler().getMoney(player) < price) | ||||
|                         .isPresent()) { | ||||
|                     if (failure != null) { | ||||
|                         failure.run(); | ||||
|                     } | ||||
|                     return; | ||||
|                 } | ||||
|                 if (success != null) { | ||||
|                     success.run(); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     private @Nonnull TemporaryCommandMeta setCommandScope(@Nonnull PlotPlayer<?> player, @Nonnull TemporaryCommandMeta commandMeta) { | ||||
|         Objects.requireNonNull(commandMeta.location()); | ||||
|         Objects.requireNonNull(commandMeta.plot()); | ||||
|         Location location; | ||||
|         Plot plot; | ||||
|         try (final MetaDataAccess<Location> locationMetaDataAccess | ||||
|                      = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) { | ||||
|             location = locationMetaDataAccess.get().orElse(null); | ||||
|             locationMetaDataAccess.set(commandMeta.location()); | ||||
|         } | ||||
|         try (final MetaDataAccess<Plot> plotMetaDataAccess | ||||
|                      = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { | ||||
|             plot = plotMetaDataAccess.get().orElse(null); | ||||
|             plotMetaDataAccess.set(commandMeta.plot()); | ||||
|         } | ||||
|         return new TemporaryCommandMeta(location, plot); | ||||
|     } | ||||
|  | ||||
|     private void resetCommandScope(@Nonnull PlotPlayer<?> player, @Nonnull TemporaryCommandMeta commandMeta) { | ||||
|         try (final MetaDataAccess<Location> locationMetaDataAccess | ||||
|                      = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LOCATION)) { | ||||
|             if (commandMeta.location() == null) { | ||||
|                 locationMetaDataAccess.remove(); | ||||
|             } else { | ||||
|                 locationMetaDataAccess.set(commandMeta.location()); | ||||
|             } | ||||
|         } | ||||
|         try (final MetaDataAccess<Plot> plotMetaDataAccess | ||||
|                      = player.accessTemporaryMetaData(PlayerMetaDataKeys.TEMPORARY_LAST_PLOT)) { | ||||
|             if (commandMeta.plot() == null) { | ||||
|                 plotMetaDataAccess.remove(); | ||||
|             } else { | ||||
|                 plotMetaDataAccess.set(commandMeta.plot()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private record CommandExecutionData(@Nonnull PlotPlayer<?> player, @Nonnull String[] args, | ||||
|                     @Nonnull RunnableVal3<Command, Runnable, Runnable> confirm, | ||||
|                     @Nonnull RunnableVal2<Command, CommandResult> whenDone, | ||||
|                     @Nullable TemporaryCommandMeta postCommandData) {} | ||||
|  | ||||
|     private record TemporaryCommandMeta(@Nullable Location location, @Nullable Plot plot) {} | ||||
|  | ||||
|     @Override | ||||
|     public boolean canExecute(PlotPlayer<?> player, boolean message) { | ||||
|         return true; | ||||
|   | ||||
| @@ -67,13 +67,10 @@ public class Visit extends Command { | ||||
|  | ||||
|     private void visit( | ||||
|             final @NonNull PlotPlayer<?> player, final @NonNull PlotQuery query, final PlotArea sortByArea, | ||||
|             final RunnableVal3<Command, Runnable, Runnable> confirm, final RunnableVal2<Command, CommandResult> whenDone, int page | ||||
|             final RunnableVal3<Command, Runnable, Runnable> confirm, final RunnableVal2<Command, CommandResult> whenDone, | ||||
|             int page, final boolean isQueryingBasePlot | ||||
|     ) { | ||||
|         // We get the query once, | ||||
|         // then we get it another time further on | ||||
|         final List<Plot> unsorted = query.asList(); | ||||
|  | ||||
|         if (unsorted.size() > 1) { | ||||
|         if (!isQueryingBasePlot && query.hasMinimumMatches(2)) { | ||||
|             query.whereBasePlot(); | ||||
|         } | ||||
|  | ||||
| @@ -237,7 +234,8 @@ public class Visit extends Command { | ||||
|                                     finalSortByArea, | ||||
|                                     confirm, | ||||
|                                     whenDone, | ||||
|                                     finalPage1 | ||||
|                                     finalPage1, | ||||
|                                     true | ||||
|                             ); | ||||
|                         } | ||||
|                     }); | ||||
| @@ -261,12 +259,9 @@ public class Visit extends Command { | ||||
|                         if (throwable instanceof TimeoutException) { | ||||
|                             // The request timed out | ||||
|                             player.sendMessage(TranslatableCaption.of("players.fetching_players_timeout")); | ||||
|                         } else if (uuid != null && (Settings.Teleport.VISIT_MERGED_OWNERS | ||||
|                                 ? !PlotQuery.newQuery().ownersInclude(uuid).anyMatch() | ||||
|                                 : !PlotQuery.newQuery().ownedBy(uuid).anyMatch())) { | ||||
|                             // It was a valid UUID but the player has no plots | ||||
|                             player.sendMessage(TranslatableCaption.of("errors.player_no_plots")); | ||||
|                         } else if (uuid == null) { | ||||
|                             return; | ||||
|                         } | ||||
|                         if (uuid == null){ | ||||
|                             // player not found, so we assume it's an alias if no page was provided | ||||
|                             if (finalPage == Integer.MIN_VALUE) { | ||||
|                                 this.visit( | ||||
| @@ -275,7 +270,8 @@ public class Visit extends Command { | ||||
|                                         player.getApplicablePlotArea(), | ||||
|                                         confirm, | ||||
|                                         whenDone, | ||||
|                                         1 | ||||
|                                         1, | ||||
|                                         false | ||||
|                                 ); | ||||
|                             } else { | ||||
|                                 player.sendMessage( | ||||
| @@ -283,24 +279,23 @@ public class Visit extends Command { | ||||
|                                         TagResolver.resolver("value", Tag.inserting(Component.text(finalArgs[0]))) | ||||
|                                 ); | ||||
|                             } | ||||
|                         } else { | ||||
|                             this.visit( | ||||
|                                     player, | ||||
|                                     Settings.Teleport.VISIT_MERGED_OWNERS | ||||
|                                             ? PlotQuery.newQuery().ownersInclude(uuid).whereBasePlot() | ||||
|                                             : PlotQuery.newQuery().ownedBy(uuid).whereBasePlot(), | ||||
|                                     null, | ||||
|                                     confirm, | ||||
|                                     whenDone, | ||||
|                                     finalPage | ||||
|                             ); | ||||
|                             return; | ||||
|                         } | ||||
|                         final PlotQuery query = Settings.Teleport.VISIT_MERGED_OWNERS | ||||
|                                 ? PlotQuery.newQuery().ownersInclude(uuid) | ||||
|                                 : PlotQuery.newQuery().ownedBy(uuid); | ||||
|                         if (!query.anyMatch()) { | ||||
|                             // It was a valid UUID but the player has no plots | ||||
|                             player.sendMessage(TranslatableCaption.of("errors.player_no_plots")); | ||||
|                             return; | ||||
|                         } | ||||
|                         this.visit(player, query.whereBasePlot(), null, confirm, whenDone, finalPage, true); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     // Try to parse a plot | ||||
|                     final Plot plot = Plot.getPlotFromString(player, finalArgs[0], true); | ||||
|                     if (plot != null) { | ||||
|                         this.visit(player, PlotQuery.newQuery().withPlot(plot), null, confirm, whenDone, 1); | ||||
|                         this.visit(player, PlotQuery.newQuery().withPlot(plot), null, confirm, whenDone, 1, false); | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|   | ||||
| @@ -444,7 +444,7 @@ public abstract class PlotPlayer<P> implements CommandCaller, OfflinePlotPlayer, | ||||
|  | ||||
|     /** | ||||
|      * Get this player's UUID. | ||||
|      * <p>=== !IMPORTANT ===</p> | ||||
|      * === !IMPORTANT ===<br> | ||||
|      * The UUID is dependent on the mode chosen in the settings.yml and may not be the same as Bukkit has | ||||
|      * (especially if using an old version of Bukkit that does not support UUIDs) | ||||
|      * | ||||
|   | ||||
| @@ -0,0 +1,50 @@ | ||||
| /* | ||||
|  * PlotSquared, a land and world management plugin for Minecraft. | ||||
|  * Copyright (C) IntellectualSites <https://intellectualsites.com> | ||||
|  * 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 <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| package com.plotsquared.core.util.comparator; | ||||
|  | ||||
| import com.plotsquared.core.plot.Plot; | ||||
|  | ||||
| import java.util.Comparator; | ||||
|  | ||||
| /** | ||||
|  * Sort plots by {@link Plot#temp} (being the auto increment id in database) in natural order for {@code temp > 0}. | ||||
|  * For {@code temp < 1} sort by {@link Plot#hashCode()} | ||||
|  */ | ||||
| public class PlotByCreationDateComparator implements Comparator<Plot> { | ||||
|  | ||||
|     public static final Comparator<Plot> INSTANCE = new PlotByCreationDateComparator(); | ||||
|  | ||||
|     private PlotByCreationDateComparator() { | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @SuppressWarnings("deprecation") // Plot#temp | ||||
|     public int compare(final Plot first, final Plot second) { | ||||
|         if (first.temp > 0 && second.temp > 0) { | ||||
|             return Integer.compare(first.temp, second.temp); | ||||
|         } | ||||
|         // second is implicitly `< 1` (due to previous condition) | ||||
|         if (first.temp > 0) { | ||||
|             return 1; | ||||
|         } | ||||
|         // sort dangling plots (temp < 1) by their hashcode | ||||
|         return Integer.compare(first.hashCode(), second.hashCode()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,52 @@ | ||||
| /* | ||||
|  * PlotSquared, a land and world management plugin for Minecraft. | ||||
|  * Copyright (C) IntellectualSites <https://intellectualsites.com> | ||||
|  * 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 <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| package com.plotsquared.core.util.comparator; | ||||
|  | ||||
| import com.plotsquared.core.plot.Plot; | ||||
| import com.plotsquared.core.plot.flag.implementations.DoneFlag; | ||||
| import com.plotsquared.core.util.MathMan; | ||||
|  | ||||
| import java.util.Comparator; | ||||
|  | ||||
| /** | ||||
|  * Sort plots by their {@link DoneFlag} in reverse numeric natural order. (more recent "finished plots" first) | ||||
|  * <br> | ||||
|  * Non-finished plots last, unsorted. | ||||
|  */ | ||||
| public class PlotByDoneComparator implements Comparator<Plot> { | ||||
|  | ||||
|     public static final PlotByDoneComparator INSTANCE = new PlotByDoneComparator(); | ||||
|  | ||||
|     private PlotByDoneComparator() { | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int compare(final Plot first, final Plot second) { | ||||
|         String firstDone = first.getFlag(DoneFlag.class); | ||||
|         String lastDone = second.getFlag(DoneFlag.class); | ||||
|         if (MathMan.isInteger(firstDone)) { | ||||
|             if (MathMan.isInteger(lastDone)) { | ||||
|                 return Integer.parseInt(lastDone) - Integer.parseInt(firstDone); | ||||
|             } | ||||
|             return -1; // only "first" is finished, so sort "second" after "first" | ||||
|         } | ||||
|         return 0; // neither is finished | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,61 @@ | ||||
| /* | ||||
|  * PlotSquared, a land and world management plugin for Minecraft. | ||||
|  * Copyright (C) IntellectualSites <https://intellectualsites.com> | ||||
|  * 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 <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| package com.plotsquared.core.util.comparator; | ||||
|  | ||||
| import com.plotsquared.core.plot.Plot; | ||||
| import com.plotsquared.core.plot.Rating; | ||||
|  | ||||
| import java.util.Comparator; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
|  | ||||
| public class PlotByRatingComparator implements Comparator<Plot> { | ||||
|  | ||||
|     public static final PlotByRatingComparator INSTANCE = new PlotByRatingComparator(); | ||||
|  | ||||
|     PlotByRatingComparator() { | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int compare(final Plot p1, final Plot p2) { | ||||
|         double v1 = 0; | ||||
|         int p1s = p1.getSettings().getRatings().size(); | ||||
|         int p2s = p2.getRatings().size(); | ||||
|         if (!p1.getSettings().getRatings().isEmpty()) { | ||||
|             v1 = p1.getRatings().values().stream().mapToDouble(Rating::getAverageRating) | ||||
|                     .map(av -> av * av).sum(); | ||||
|             v1 /= p1s; | ||||
|             v1 += p1s; | ||||
|         } | ||||
|         double v2 = 0; | ||||
|         if (!p2.getSettings().getRatings().isEmpty()) { | ||||
|             for (Map.Entry<UUID, Rating> entry : p2.getRatings().entrySet()) { | ||||
|                 double av = entry.getValue().getAverageRating(); | ||||
|                 v2 += av * av; | ||||
|             } | ||||
|             v2 /= p2s; | ||||
|             v2 += p2s; | ||||
|         } | ||||
|         if (v2 == v1 && v2 != 0) { | ||||
|             return p2s - p1s; | ||||
|         } | ||||
|         return (int) Math.signum(v2 - v1); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,49 @@ | ||||
| /* | ||||
|  * PlotSquared, a land and world management plugin for Minecraft. | ||||
|  * Copyright (C) IntellectualSites <https://intellectualsites.com> | ||||
|  * 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 <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| package com.plotsquared.core.util.comparator; | ||||
|  | ||||
| import com.plotsquared.core.plot.Plot; | ||||
| import com.plotsquared.core.plot.PlotArea; | ||||
|  | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Comparator; | ||||
|  | ||||
| public class PlotInPrioritizedAreaComparator implements Comparator<Plot> { | ||||
|  | ||||
|     private final PlotArea priorityArea; | ||||
|  | ||||
|     public PlotInPrioritizedAreaComparator(@Nullable final PlotArea area) { | ||||
|         this.priorityArea = area; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int compare(final Plot first, final Plot second) { | ||||
|         if (this.priorityArea == null) { | ||||
|             return 0; // no defined priority? don't sort | ||||
|         } | ||||
|         if (this.priorityArea.equals(first.getArea())) { | ||||
|             return -1; | ||||
|         } | ||||
|         if (this.priorityArea.equals(second.getArea())) { | ||||
|             return 1; | ||||
|         } | ||||
|         return 0; // same area, don't sort | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -24,6 +24,7 @@ import com.plotsquared.core.plot.PlotArea; | ||||
| import java.util.Collection; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| class AreaLimitedPlotProvider implements PlotProvider { | ||||
|  | ||||
| @@ -42,4 +43,9 @@ class AreaLimitedPlotProvider implements PlotProvider { | ||||
|         return plots; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Stream<Plot> streamPlots() { | ||||
|         return streamPlotsInPlotAreas(this.areas.toArray(PlotArea[]::new)); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -22,6 +22,7 @@ import com.plotsquared.core.PlotSquared; | ||||
| import com.plotsquared.core.plot.Plot; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| class ExpiredPlotProvider implements PlotProvider { | ||||
|  | ||||
| @@ -30,4 +31,9 @@ class ExpiredPlotProvider implements PlotProvider { | ||||
|         return PlotSquared.platform().expireManager().getPendingExpired(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Stream<Plot> streamPlots() { | ||||
|         return getPlots().stream(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -23,6 +23,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| class FixedPlotProvider implements PlotProvider { | ||||
|  | ||||
| @@ -37,4 +38,9 @@ class FixedPlotProvider implements PlotProvider { | ||||
|         return Collections.singleton(plot); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Stream<Plot> streamPlots() { | ||||
|         return Stream.of(plot); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| class GlobalPlotProvider implements PlotProvider { | ||||
|  | ||||
| @@ -44,4 +45,9 @@ class GlobalPlotProvider implements PlotProvider { | ||||
|         return plots; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Stream<Plot> streamPlots() { | ||||
|         return streamPlotsInPlotAreas(this.plotAreaManager.getAllPlotAreas()); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -22,6 +22,7 @@ import com.plotsquared.core.plot.Plot; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| class NullProvider implements PlotProvider { | ||||
|  | ||||
| @@ -30,4 +31,9 @@ class NullProvider implements PlotProvider { | ||||
|         return Collections.emptyList(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Stream<Plot> streamPlots() { | ||||
|         return Stream.empty(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -23,17 +23,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; | ||||
|  | ||||
| import java.util.UUID; | ||||
|  | ||||
| class OwnersIncludeFilter implements PlotFilter { | ||||
|  | ||||
|     private final UUID owner; | ||||
|  | ||||
|     OwnersIncludeFilter(final @NonNull UUID owner) { | ||||
|         this.owner = owner; | ||||
|     } | ||||
| record OwnersIncludeFilter(UUID owner) implements PlotFilter { | ||||
|  | ||||
|     @Override | ||||
|     public boolean accepts(final @NonNull Plot plot) { | ||||
|         return plot.isBasePlot() && plot.getOwners().size() > 0 && plot.getOwners().contains(owner); | ||||
|         return plot.isBasePlot() && plot.isOwner(owner); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -19,12 +19,44 @@ | ||||
| package com.plotsquared.core.util.query; | ||||
|  | ||||
| import com.plotsquared.core.plot.Plot; | ||||
| import com.plotsquared.core.plot.PlotArea; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Iterator; | ||||
| import java.util.Spliterator; | ||||
| import java.util.Spliterators; | ||||
| import java.util.stream.Stream; | ||||
| import java.util.stream.StreamSupport; | ||||
|  | ||||
| @FunctionalInterface | ||||
| interface PlotProvider { | ||||
|  | ||||
|     Collection<Plot> getPlots(); | ||||
|  | ||||
|     Stream<Plot> streamPlots(); | ||||
|  | ||||
|     default Stream<Plot> streamPlotsInPlotAreas(PlotArea[] areas) { | ||||
|         if (areas == null || areas.length == 0) { | ||||
|             return Stream.of(); | ||||
|         } | ||||
|         return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<>() { | ||||
|             private int areaIndex = -1; | ||||
|             private Iterator<Plot> currentAreaPlots; | ||||
|             @Override | ||||
|             public boolean hasNext() { | ||||
|                 if (currentAreaPlots == null || !currentAreaPlots.hasNext()) { | ||||
|                     if (areaIndex >= areas.length - 1) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     currentAreaPlots = areas[++areaIndex].getPlots().iterator(); | ||||
|                 } | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public Plot next() { | ||||
|                 return currentAreaPlots.next(); | ||||
|             } | ||||
|         }, Spliterator.IMMUTABLE), false); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -23,10 +23,11 @@ import com.plotsquared.core.PlotSquared; | ||||
| import com.plotsquared.core.player.PlotPlayer; | ||||
| import com.plotsquared.core.plot.Plot; | ||||
| import com.plotsquared.core.plot.PlotArea; | ||||
| import com.plotsquared.core.plot.Rating; | ||||
| import com.plotsquared.core.plot.flag.implementations.DoneFlag; | ||||
| import com.plotsquared.core.plot.world.PlotAreaManager; | ||||
| import com.plotsquared.core.util.MathMan; | ||||
| import com.plotsquared.core.util.comparator.PlotByDoneComparator; | ||||
| import com.plotsquared.core.util.comparator.PlotByRatingComparator; | ||||
| import com.plotsquared.core.util.comparator.PlotByCreationDateComparator; | ||||
| import com.plotsquared.core.util.comparator.PlotInPrioritizedAreaComparator; | ||||
| import org.checkerframework.checker.nullness.qual.NonNull; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| @@ -37,7 +38,6 @@ import java.util.HashSet; | ||||
| import java.util.Iterator; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import java.util.UUID; | ||||
| import java.util.function.Predicate; | ||||
| @@ -229,6 +229,7 @@ public final class PlotQuery implements Iterable<Plot> { | ||||
|     public @NonNull PlotQuery hasOwner() { | ||||
|         return this.addFilter(new HasOwnerFilter()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Query for plots with a specific alias | ||||
|      * | ||||
| @@ -307,7 +308,15 @@ public final class PlotQuery implements Iterable<Plot> { | ||||
|      * @return Matching plots | ||||
|      */ | ||||
|     public @NonNull Stream<Plot> asStream() { | ||||
|         return this.asList().stream(); | ||||
|         Stream<Plot> stream = this.plotProvider.streamPlots().filter(testPlotAgainstFilters()); | ||||
|         if (this.sortingStrategy == SortingStrategy.NO_SORTING) { | ||||
|             return stream; | ||||
|         } | ||||
|         stream = stream.sorted(getConfiguredComparator()); | ||||
|         if (this.priorityArea != null) { | ||||
|             stream = stream.sorted(new PlotInPrioritizedAreaComparator(this.priorityArea)); | ||||
|         } | ||||
|         return stream; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -334,49 +343,10 @@ public final class PlotQuery implements Iterable<Plot> { | ||||
|         } | ||||
|         if (this.sortingStrategy == SortingStrategy.NO_SORTING) { | ||||
|             return result; | ||||
|         } else if (this.sortingStrategy == SortingStrategy.SORT_BY_TEMP) { | ||||
|             return PlotSquared.get().sortPlotsByTemp(result); | ||||
|         } else if (this.sortingStrategy == SortingStrategy.SORT_BY_DONE) { | ||||
|             result.sort((a, b) -> { | ||||
|                 String va = a.getFlag(DoneFlag.class); | ||||
|                 String vb = b.getFlag(DoneFlag.class); | ||||
|                 if (MathMan.isInteger(va)) { | ||||
|                     if (MathMan.isInteger(vb)) { | ||||
|                         return Integer.parseInt(vb) - Integer.parseInt(va); | ||||
|                     } | ||||
|                     return -1; | ||||
|                 } | ||||
|                 return 1; | ||||
|             }); | ||||
|         } else if (this.sortingStrategy == SortingStrategy.SORT_BY_RATING) { | ||||
|             result.sort((p1, p2) -> { | ||||
|                 double v1 = 0; | ||||
|                 int p1s = p1.getSettings().getRatings().size(); | ||||
|                 int p2s = p2.getRatings().size(); | ||||
|                 if (!p1.getSettings().getRatings().isEmpty()) { | ||||
|                     v1 = p1.getRatings().values().stream().mapToDouble(Rating::getAverageRating) | ||||
|                             .map(av -> av * av).sum(); | ||||
|                     v1 /= p1s; | ||||
|                     v1 += p1s; | ||||
|                 } | ||||
|                 double v2 = 0; | ||||
|                 if (!p2.getSettings().getRatings().isEmpty()) { | ||||
|                     for (Map.Entry<UUID, Rating> entry : p2.getRatings().entrySet()) { | ||||
|                         double av = entry.getValue().getAverageRating(); | ||||
|                         v2 += av * av; | ||||
|                     } | ||||
|                     v2 /= p2s; | ||||
|                     v2 += p2s; | ||||
|                 } | ||||
|                 if (v2 == v1 && v2 != 0) { | ||||
|                     return p2s - p1s; | ||||
|                 } | ||||
|                 return (int) Math.signum(v2 - v1); | ||||
|             }); | ||||
|         } else if (this.sortingStrategy == SortingStrategy.SORT_BY_CREATION) { | ||||
|             return PlotSquared.get().sortPlots(result, PlotSquared.SortType.CREATION_DATE, this.priorityArea); | ||||
|         } else if (this.sortingStrategy == SortingStrategy.COMPARATOR) { | ||||
|             result.sort(this.plotComparator); | ||||
|         } | ||||
|         result.sort(getConfiguredComparator()); | ||||
|         if (this.priorityArea != null) { | ||||
|             result.sort(new PlotInPrioritizedAreaComparator(this.priorityArea)); | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| @@ -427,28 +397,22 @@ public final class PlotQuery implements Iterable<Plot> { | ||||
|      * @return {@code true} if any provided plot matches the filters. | ||||
|      */ | ||||
|     public boolean anyMatch() { | ||||
|         if (this.filters.isEmpty()) { | ||||
|             return !this.plotProvider.getPlots().isEmpty(); | ||||
|         } else { | ||||
|             final Collection<Plot> plots = this.plotProvider.getPlots(); | ||||
|             outer: | ||||
|             for (final Plot plot : plots) { | ||||
|                 // a plot must pass all filters to match the criteria | ||||
|                 for (final PlotFilter filter : this.filters) { | ||||
|                     if (!filter.accepts(plot)) { | ||||
|                         continue outer; | ||||
|                     } | ||||
|                 } | ||||
|                 return true; // a plot passed all filters, so we have a match | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         return hasMinimumMatches(1); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     private PlotQuery addFilter(final @NonNull PlotFilter filter) { | ||||
|         this.filters.add(filter); | ||||
|         return this; | ||||
|     /** | ||||
|      * Get whether this query matches at least {@code minimum} plots. | ||||
|      * <br /> | ||||
|      * Should be prioritized over {@link #count()}, if possible. | ||||
|      * This method only queries as many plots and areas as required without applying sorting. (short-circuiting on the stream) | ||||
|      * | ||||
|      * @param minimum the minimum amount of matches plots expected (inclusive) | ||||
|      * @return {@code true} if this query's result contains at least {@code minimum} plots (after optional filters). | ||||
|      * | ||||
|      * @since TODO | ||||
|      */ | ||||
|     public boolean hasMinimumMatches(int minimum) { | ||||
|         return this.plotProvider.streamPlots().filter(testPlotAgainstFilters()).limit(minimum).count() == minimum; | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
| @@ -457,4 +421,34 @@ public final class PlotQuery implements Iterable<Plot> { | ||||
|         return this.asCollection().iterator(); | ||||
|     } | ||||
|  | ||||
|     @NonNull | ||||
|     private PlotQuery addFilter(final @NonNull PlotFilter filter) { | ||||
|         this.filters.add(filter); | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     private Comparator<Plot> getConfiguredComparator() { | ||||
|         return switch (sortingStrategy) { | ||||
|             case NO_SORTING -> (p1, p2) -> 0; | ||||
|             case SORT_BY_TEMP, SORT_BY_CREATION -> PlotByCreationDateComparator.INSTANCE; | ||||
|             case SORT_BY_DONE -> PlotByDoneComparator.INSTANCE; | ||||
|             case SORT_BY_RATING -> PlotByRatingComparator.INSTANCE; | ||||
|             case COMPARATOR -> plotComparator; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     private Predicate<Plot> testPlotAgainstFilters() { | ||||
|         if (this.filters.isEmpty()) { | ||||
|             return plot -> true; | ||||
|         } | ||||
|         return plot -> { | ||||
|             for (final PlotFilter filter : filters) { | ||||
|                 if (!filter.accepts(plot)) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             return true; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -31,6 +31,7 @@ import java.util.List; | ||||
| import java.util.UUID; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.stream.IntStream; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| class SearchPlotProvider implements PlotProvider { | ||||
|  | ||||
| @@ -115,4 +116,9 @@ class SearchPlotProvider implements PlotProvider { | ||||
|         return getPlotsBySearch(this.searchTerm); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Stream<Plot> streamPlots() { | ||||
|         return getPlots().stream(); // TODO: look into potentially optimizations here? | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										154
									
								
								build.gradle.kts
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								build.gradle.kts
									
									
									
									
									
								
							| @@ -1,6 +1,5 @@ | ||||
| import com.diffplug.gradle.spotless.SpotlessPlugin | ||||
| import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin | ||||
| import com.vanniktech.maven.publish.SonatypeHost | ||||
| import groovy.json.JsonSlurper | ||||
| import xyz.jpenilla.runpaper.task.RunServer | ||||
| import java.net.URI | ||||
| @@ -8,12 +7,13 @@ import java.net.URI | ||||
| plugins { | ||||
|     java | ||||
|     `java-library` | ||||
|     `maven-publish` | ||||
|     signing | ||||
|  | ||||
|     alias(libs.plugins.shadow) | ||||
|     alias(libs.plugins.spotless) | ||||
|     alias(libs.plugins.grgit) | ||||
|     alias(libs.plugins.publish) | ||||
|     alias(libs.plugins.nexus) | ||||
|  | ||||
|     eclipse | ||||
|     idea | ||||
| @@ -22,7 +22,7 @@ plugins { | ||||
| } | ||||
|  | ||||
| group = "com.intellectualsites.plotsquared" | ||||
| version = "7.5.3-SNAPSHOT" | ||||
| version = "7.5.1-SNAPSHOT" | ||||
|  | ||||
| if (!File("$rootDir/.git").exists()) { | ||||
|     logger.lifecycle(""" | ||||
| @@ -41,6 +41,16 @@ subprojects { | ||||
|     repositories { | ||||
|         mavenCentral() | ||||
|  | ||||
|         maven { | ||||
|             name = "Sonatype OSS" | ||||
|             url = uri("https://oss.sonatype.org/content/repositories/snapshots/") | ||||
|         } | ||||
|  | ||||
|         maven { | ||||
|             name = "Sonatype OSS (S01)" | ||||
|             url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") | ||||
|         } | ||||
|  | ||||
|         maven { | ||||
|             name = "Jitpack" | ||||
|             url = uri("https://jitpack.io") | ||||
| @@ -58,7 +68,7 @@ subprojects { | ||||
|     apply { | ||||
|         plugin<JavaPlugin>() | ||||
|         plugin<JavaLibraryPlugin>() | ||||
|         plugin<com.vanniktech.maven.publish.MavenPublishPlugin>() | ||||
|         plugin<MavenPublishPlugin>() | ||||
|         plugin<ShadowPlugin>() | ||||
|         plugin<SpotlessPlugin>() | ||||
|         plugin<SigningPlugin>() | ||||
| @@ -69,8 +79,8 @@ subprojects { | ||||
|  | ||||
|     dependencies { | ||||
|         // Tests | ||||
|         testImplementation("org.junit.jupiter:junit-jupiter:5.12.2") | ||||
|         testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.12.2") | ||||
|         testImplementation("org.junit.jupiter:junit-jupiter:5.12.0") | ||||
|         testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.12.0") | ||||
|     } | ||||
|  | ||||
|     plugins.withId("java") { | ||||
| @@ -97,6 +107,11 @@ subprojects { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     java { | ||||
|         withSourcesJar() | ||||
|         withJavadocJar() | ||||
|     } | ||||
|  | ||||
|     val javaComponent = components["java"] as AdhocComponentWithVariants | ||||
|     javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { | ||||
|         skip() | ||||
| @@ -112,67 +127,66 @@ subprojects { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     mavenPublishing { | ||||
|         coordinates( | ||||
|             groupId = "$group", | ||||
|             artifactId = project.name, | ||||
|             version = "${project.version}", | ||||
|         ) | ||||
|     publishing { | ||||
|         publications { | ||||
|             create<MavenPublication>("maven") { | ||||
|                 from(components["java"]) | ||||
|  | ||||
|         pom { | ||||
|             name.set(project.name) | ||||
|             description.set("PlotSquared, a land and world management plugin for Minecraft.") | ||||
|             url.set("https://github.com/IntellectualSites/PlotSquared") | ||||
|                 pom { | ||||
|  | ||||
|             licenses { | ||||
|                 license { | ||||
|                     name.set("GNU General Public License, Version 3.0") | ||||
|                     url.set("https://www.gnu.org/licenses/gpl-3.0.html") | ||||
|                     distribution.set("repo") | ||||
|                     name.set(project.name + " " + project.version) | ||||
|                     description.set("PlotSquared, a land and world management plugin for Minecraft.") | ||||
|                     url.set("https://github.com/IntellectualSites/PlotSquared") | ||||
|  | ||||
|                     licenses { | ||||
|                         license { | ||||
|                             name.set("GNU General Public License, Version 3.0") | ||||
|                             url.set("https://www.gnu.org/licenses/gpl-3.0.html") | ||||
|                             distribution.set("repo") | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     developers { | ||||
|                         developer { | ||||
|                             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:git:https://github.com/IntellectualSites/PlotSquared.git") | ||||
|                         developerConnection.set("scm:git:git@github.com:IntellectualSites/PlotSquared.git") | ||||
|                         tag.set("${project.version}") | ||||
|                     } | ||||
|  | ||||
|                     issueManagement { | ||||
|                         system.set("GitHub") | ||||
|                         url.set("https://github.com/IntellectualSites/PlotSquared/issues") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             developers { | ||||
|                 developer { | ||||
|                     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:git:https://github.com/IntellectualSites/PlotSquared.git") | ||||
|                 developerConnection.set("scm:git:git@github.com:IntellectualSites/PlotSquared.git") | ||||
|                 tag.set("${project.version}") | ||||
|             } | ||||
|  | ||||
|             issueManagement { | ||||
|                 system.set("GitHub") | ||||
|                 url.set("https://github.com/IntellectualSites/PlotSquared/issues") | ||||
|             } | ||||
|  | ||||
|             publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -180,6 +194,7 @@ subprojects { | ||||
|  | ||||
|         compileJava { | ||||
|             options.compilerArgs.add("-parameters") | ||||
|             options.isDeprecation = true | ||||
|             options.encoding = "UTF-8" | ||||
|         } | ||||
|  | ||||
| @@ -202,18 +217,27 @@ subprojects { | ||||
|     } | ||||
| } | ||||
|  | ||||
| nexusPublishing { | ||||
|     this.repositories { | ||||
|         sonatype { | ||||
|             nexusUrl.set(URI.create("https://s01.oss.sonatype.org/service/local/")) | ||||
|             snapshotRepositoryUrl.set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| tasks.getByName<Jar>("jar") { | ||||
|     enabled = false | ||||
| } | ||||
|  | ||||
| val supportedVersions = listOf("1.19.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4", "1.21.5") | ||||
| val supportedVersions = listOf("1.19.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4") | ||||
| 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("Paper") } | ||||
|                 .first { it -> it.contains("Bukkit") } | ||||
|         project.ext["faweArtifact"] = artifact | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -2,13 +2,13 @@ | ||||
| # Platform expectations | ||||
| paper = "1.20.4-R0.1-SNAPSHOT" | ||||
| guice = "7.0.0" | ||||
| spotbugs = "4.9.3" | ||||
| checkerqual = "3.49.3" | ||||
| spotbugs = "4.9.1" | ||||
| checkerqual = "3.49.0" | ||||
| gson = "2.10" | ||||
| guava = "31.1-jre" | ||||
| snakeyaml = "2.0" | ||||
| adventure = "4.21.0" | ||||
| adventure-bukkit = "4.4.0" | ||||
| adventure = "4.19.0" | ||||
| adventure-bukkit = "4.3.4" | ||||
| log4j = "2.19.0" | ||||
|  | ||||
| # Plugins | ||||
| @@ -16,27 +16,27 @@ worldedit = "7.2.20" | ||||
| fawe = "2.13.0" | ||||
| placeholderapi = "2.11.6" | ||||
| luckperms = "5.4" | ||||
| essentialsx = "2.21.0" | ||||
| essentialsx = "2.20.1" | ||||
| mvdwapi = "3.1.1" | ||||
|  | ||||
| # Third party | ||||
| prtree = "2.0.1" | ||||
| aopalliance = "1.0" | ||||
| cloud-services = "1.8.4" | ||||
| arkitektonika = "2.1.4" | ||||
| arkitektonika = "2.1.3" | ||||
| squirrelid = "0.3.2" | ||||
| paster = "1.1.7" | ||||
| paster = "1.1.6" | ||||
| bstats = "3.1.0" | ||||
| paperlib = "1.0.8" | ||||
| informative-annotations = "1.6" | ||||
| informative-annotations = "1.5" | ||||
| vault = "1.7.1" | ||||
| serverlib = "2.3.7" | ||||
| serverlib = "2.3.6" | ||||
|  | ||||
| # Gradle plugins | ||||
| shadow = "8.3.6" | ||||
| grgit = "4.1.1" | ||||
| spotless = "7.0.3" | ||||
| publish = "0.32.0" | ||||
| spotless = "7.0.2" | ||||
| nexus = "2.0.0" | ||||
| runPaper = "2.3.1" | ||||
|  | ||||
| [libraries] | ||||
| @@ -81,5 +81,5 @@ serverlib = { group = "dev.notmyfault.serverlib", name = "ServerLib", version.re | ||||
| shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } | ||||
| grgit = { id = "org.ajoberstar.grgit", version.ref = "grgit" } | ||||
| spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } | ||||
| publish = { id = "com.vanniktech.maven.publish", version.ref = "publish" } | ||||
| nexus = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus" } | ||||
| runPaper = { id = "xyz.jpenilla.run-paper", version.ref = "runPaper" } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip | ||||
| networkTimeout=10000 | ||||
| validateDistributionUrl=true | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
|   | ||||
							
								
								
									
										6
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							| @@ -114,7 +114,7 @@ case "$( uname )" in                #( | ||||
|   NONSTOP* )        nonstop=true ;; | ||||
| esac | ||||
|  | ||||
| CLASSPATH="\\\"\\\"" | ||||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||
|  | ||||
|  | ||||
| # Determine the Java command to use to start the JVM. | ||||
| @@ -205,7 +205,7 @@ fi | ||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||
|  | ||||
| # Collect all arguments for the java command: | ||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||||
| #   * 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. | ||||
| @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||
| set -- \ | ||||
|         "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||||
|         -classpath "$CLASSPATH" \ | ||||
|         -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ | ||||
|         org.gradle.wrapper.GradleWrapperMain \ | ||||
|         "$@" | ||||
|  | ||||
| # Stop when "xargs" is not available. | ||||
|   | ||||
							
								
								
									
										4
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							| @@ -70,11 +70,11 @@ goto fail | ||||
| :execute | ||||
| @rem Setup the command line | ||||
|  | ||||
| set CLASSPATH= | ||||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||
|  | ||||
|  | ||||
| @rem Execute Gradle | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | ||||
|  | ||||
| :end | ||||
| @rem End local scope for the variables with windows NT shell | ||||
|   | ||||
		Reference in New Issue
	
	Block a user