├── .github ├── FUNDING.yml └── workflows │ └── gradle.yml ├── .gitignore ├── CHANGELOG.md ├── Insights-API ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── dev │ │ └── frankheijden │ │ └── insights │ │ └── api │ │ ├── InsightsMain.java │ │ ├── InsightsPlugin.java │ │ ├── addons │ │ ├── AddonException.java │ │ ├── AddonManager.java │ │ ├── CuboidRegion.java │ │ ├── InsightsAddon.java │ │ ├── MultiCuboidRegion.java │ │ ├── Region.java │ │ ├── SimpleCuboidRegion.java │ │ └── SimpleMultiCuboidRegion.java │ │ ├── annotations │ │ ├── AllowDisabling.java │ │ └── AllowPriorityOverride.java │ │ ├── commands │ │ └── InsightsCommand.java │ │ ├── concurrent │ │ ├── ChunkContainerExecutor.java │ │ ├── ChunkTeleport.java │ │ ├── ContainerExecutor.java │ │ ├── PlayerList.java │ │ ├── ScanOptions.java │ │ ├── containers │ │ │ ├── ChunkContainer.java │ │ │ ├── Container.java │ │ │ ├── LoadedChunkContainer.java │ │ │ ├── RunnableContainer.java │ │ │ ├── SupplierContainer.java │ │ │ └── UnloadedChunkContainer.java │ │ ├── count │ │ │ ├── IntegerCount.java │ │ │ ├── RedstoneUpdateCount.java │ │ │ └── TickResetCount.java │ │ ├── storage │ │ │ ├── AddonStorage.java │ │ │ ├── ChunkStorage.java │ │ │ ├── Distribution.java │ │ │ ├── DistributionStorage.java │ │ │ ├── ScanHistory.java │ │ │ ├── Storage.java │ │ │ └── WorldStorage.java │ │ └── tracker │ │ │ ├── AddonScanTracker.java │ │ │ ├── ChunkScanTracker.java │ │ │ ├── ScanTracker.java │ │ │ └── WorldChunkScanTracker.java │ │ ├── config │ │ ├── ConfigError.java │ │ ├── LimitEnvironment.java │ │ ├── Limits.java │ │ ├── Messages.java │ │ ├── Monad.java │ │ ├── Notifications.java │ │ ├── Settings.java │ │ ├── limits │ │ │ ├── GroupLimit.java │ │ │ ├── Limit.java │ │ │ ├── LimitInfo.java │ │ │ ├── LimitParseException.java │ │ │ ├── LimitSettings.java │ │ │ ├── LimitType.java │ │ │ ├── PermissionLimit.java │ │ │ └── TileLimit.java │ │ ├── notifications │ │ │ ├── AbstractNotificationFactory.java │ │ │ ├── ActionBarNotification.java │ │ │ ├── ActionBarProgressNotification.java │ │ │ ├── BossBarNotification.java │ │ │ ├── BossBarProgressNotification.java │ │ │ ├── EmptyNotification.java │ │ │ ├── EmptyProgressNotification.java │ │ │ ├── Notification.java │ │ │ ├── NotificationFactory.java │ │ │ ├── ProgressNotification.java │ │ │ ├── ProgressNotificationFactory.java │ │ │ └── SendableNotification.java │ │ └── parser │ │ │ ├── PassiveYamlParser.java │ │ │ ├── SensitiveYamlParser.java │ │ │ ├── YamlParseException.java │ │ │ └── YamlParser.java │ │ ├── events │ │ └── EntityRemoveFromWorldEvent.java │ │ ├── exceptions │ │ ├── ChunkCuboidOutOfBoundsException.java │ │ └── ChunkIOException.java │ │ ├── listeners │ │ ├── InsightsListener.java │ │ └── manager │ │ │ └── InsightsListenerManager.java │ │ ├── metrics │ │ ├── IntegerMetric.java │ │ └── MetricsManager.java │ │ ├── objects │ │ ├── InsightsBase.java │ │ ├── chunk │ │ │ ├── ChunkCuboid.java │ │ │ ├── ChunkLocation.java │ │ │ ├── ChunkPart.java │ │ │ └── ChunkVector.java │ │ ├── math │ │ │ ├── Cuboid.java │ │ │ └── Vector3.java │ │ └── wrappers │ │ │ └── ScanObject.java │ │ ├── reflection │ │ └── RTileEntityTypes.java │ │ ├── tasks │ │ ├── InsightsAsyncTask.java │ │ ├── InsightsTask.java │ │ ├── ScanTask.java │ │ └── UpdateCheckerTask.java │ │ ├── util │ │ ├── LazyChunkPartRadiusIterator.java │ │ ├── MaterialTags.java │ │ ├── SetCollector.java │ │ └── TriConsumer.java │ │ └── utils │ │ ├── BlockUtils.java │ │ ├── ChunkUtils.java │ │ ├── ColorUtils.java │ │ ├── Constants.java │ │ ├── EnumUtils.java │ │ ├── IOUtils.java │ │ ├── LocationUtils.java │ │ ├── MapUtils.java │ │ ├── SetUtils.java │ │ ├── StringUtils.java │ │ ├── VersionUtils.java │ │ └── YamlUtils.java │ └── test │ └── java │ └── dev │ └── frankheijden │ └── insights │ └── api │ └── util │ └── LazyChunkPartRadiusIteratorTest.java ├── Insights-NMS ├── Core │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── dev │ │ └── frankheijden │ │ └── insights │ │ └── nms │ │ └── core │ │ ├── ChunkEntity.java │ │ ├── ChunkReflectionException.java │ │ ├── ChunkSection.java │ │ ├── InsightsNMS.java │ │ └── ReflectionUtils.java └── Current │ ├── build.gradle.kts │ └── src │ └── main │ └── java │ └── dev │ └── frankheijden │ └── insights │ └── nms │ └── impl │ └── InsightsNMSImpl.java ├── Insights ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── frankheijden │ │ └── insights │ │ ├── Insights.java │ │ ├── commands │ │ ├── CommandCancelScan.java │ │ ├── CommandInsights.java │ │ ├── CommandScan.java │ │ ├── CommandScanCache.java │ │ ├── CommandScanHistory.java │ │ ├── CommandScanRegion.java │ │ ├── CommandScanWorld.java │ │ ├── CommandTeleportChunk.java │ │ ├── parser │ │ │ ├── LimitParser.java │ │ │ ├── ScanHistoryPageParser.java │ │ │ ├── ScanObjectArrayParser.java │ │ │ └── WorldParser.java │ │ └── util │ │ │ └── CommandSenderMapper.java │ │ ├── concurrent │ │ └── ContainerExecutorService.java │ │ ├── listeners │ │ ├── BlockListener.java │ │ ├── ChunkListener.java │ │ ├── EntityListener.java │ │ ├── PaperBlockListener.java │ │ ├── PaperEntityListener.java │ │ ├── PistonListener.java │ │ ├── PlayerListener.java │ │ ├── WorldListener.java │ │ └── manager │ │ │ └── ListenerManager.java │ │ ├── placeholders │ │ └── InsightsPlaceholderExpansion.java │ │ └── tasks │ │ └── PlayerTrackerTask.java │ └── resources │ ├── bed-limit.yml │ ├── config.yml │ ├── messages.yml │ ├── redstone-limit.yml │ └── tile-limit.yml ├── LICENSE ├── README.md ├── build.gradle.kts ├── config └── checkstyle │ ├── checkstyle.xml │ └── suppressions.xml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── AutoScan.png ├── CustomLimit.png ├── CustomScan.png ├── GroupLimit.png ├── RegionDisallow.png ├── ScanRadius.png ├── TileLimit.png ├── TileScan.png └── WorldEditLimit.png └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.paypal.me/frankheijden'] 2 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI with Gradle 2 | on: 3 | push: 4 | branches: [ main ] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | java-version: [ 21 ] 11 | fail-fast: true 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Setup JDK ${{ matrix.java-version }} 15 | uses: actions/setup-java@v3 16 | with: 17 | distribution: temurin 18 | java-version: ${{ matrix.java-version }} 19 | - name: Grant execute permission for gradlew 20 | run: chmod +x gradlew 21 | - uses: actions/cache@v3 22 | with: 23 | path: '**/.gradle' 24 | key: ${{ runner.os }}-gradle-${{ vars.CACHE_VERSION }}-${{ hashFiles('**/VersionConstants.kt', '**/build.gradle.kts', '**/gradle-wrapper.properties') }} 25 | - name: Build 26 | id: build 27 | run: | 28 | ./gradlew clean build --stacktrace 29 | echo "is-release=$(./gradlew -q printIsRelease)" >> "$GITHUB_OUTPUT" 30 | echo "version=$(./gradlew -q printVersion)" >> "$GITHUB_OUTPUT" 31 | - name: Upload artifacts 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: Insights 35 | path: jars/*.jar 36 | - name: Publish to Maven Repository 37 | if: ${{ github.event_name == 'push' }} 38 | env: 39 | FVDH_USERNAME: ${{ secrets.FVDH_USERNAME }} 40 | FVDH_TOKEN: ${{ secrets.FVDH_TOKEN }} 41 | run: ./gradlew publish --stacktrace 42 | - uses: Kir-Antipov/mc-publish@v3.3 43 | if: ${{ github.event_name == 'push' && steps.build.outputs.is-release == 'true' }} 44 | with: 45 | name: ${{ format('Insights {0}', steps.build.outputs.version) }} 46 | version: ${{ steps.build.outputs.version }} 47 | modrinth-id: V27CDDh1 48 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 49 | changelog-file: CHANGELOG.md 50 | github-token: ${{ secrets.GITHUB_TOKEN }} 51 | files: | 52 | jars/*.jar 53 | loaders: | 54 | paper 55 | purpur 56 | game-versions: | 57 | >=1.21 58 | game-version-filter: releases 59 | java: 21 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vs/ 3 | .gradle/ 4 | build/ 5 | jars/ 6 | *.class 7 | *.jar 8 | .java-version 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | - 1.21.3 Support by @Jsinco (removes support for <= 1.21.2) 3 | -------------------------------------------------------------------------------- /Insights-API/build.gradle.kts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InsightsPlugin/Insights/3dfa42079b7c3fe76a88b066a6a01d951845f725/Insights-API/build.gradle.kts -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/InsightsMain.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api; 2 | 3 | import dev.frankheijden.insights.api.addons.AddonManager; 4 | import dev.frankheijden.insights.api.concurrent.ChunkContainerExecutor; 5 | import dev.frankheijden.insights.api.concurrent.ChunkTeleport; 6 | import dev.frankheijden.insights.api.concurrent.ContainerExecutor; 7 | import dev.frankheijden.insights.api.concurrent.PlayerList; 8 | import dev.frankheijden.insights.api.concurrent.count.RedstoneUpdateCount; 9 | import dev.frankheijden.insights.api.concurrent.storage.AddonStorage; 10 | import dev.frankheijden.insights.api.concurrent.storage.ScanHistory; 11 | import dev.frankheijden.insights.api.concurrent.storage.WorldStorage; 12 | import dev.frankheijden.insights.api.concurrent.tracker.AddonScanTracker; 13 | import dev.frankheijden.insights.api.concurrent.tracker.WorldChunkScanTracker; 14 | import dev.frankheijden.insights.api.config.Limits; 15 | import dev.frankheijden.insights.api.config.Messages; 16 | import dev.frankheijden.insights.api.config.Notifications; 17 | import dev.frankheijden.insights.api.config.Settings; 18 | import dev.frankheijden.insights.api.listeners.manager.InsightsListenerManager; 19 | import dev.frankheijden.insights.api.metrics.MetricsManager; 20 | import dev.frankheijden.insights.nms.core.InsightsNMS; 21 | 22 | public interface InsightsMain { 23 | 24 | void reloadSettings(); 25 | 26 | void reloadMessages(); 27 | 28 | void reloadNotifications(); 29 | 30 | void reloadLimits(); 31 | 32 | Settings getSettings(); 33 | 34 | Messages getMessages(); 35 | 36 | Notifications getNotifications(); 37 | 38 | Limits getLimits(); 39 | 40 | AddonManager getAddonManager(); 41 | 42 | ContainerExecutor getExecutor(); 43 | 44 | ChunkContainerExecutor getChunkContainerExecutor(); 45 | 46 | PlayerList getPlayerList(); 47 | 48 | WorldStorage getWorldStorage(); 49 | 50 | AddonStorage getAddonStorage(); 51 | 52 | WorldChunkScanTracker getWorldChunkScanTracker(); 53 | 54 | AddonScanTracker getAddonScanTracker(); 55 | 56 | MetricsManager getMetricsManager(); 57 | 58 | ScanHistory getScanHistory(); 59 | 60 | InsightsListenerManager getListenerManager(); 61 | 62 | RedstoneUpdateCount getRedstoneUpdateCount(); 63 | 64 | ChunkTeleport getChunkTeleport(); 65 | 66 | InsightsNMS getNMS(); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/InsightsPlugin.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | public abstract class InsightsPlugin extends JavaPlugin implements InsightsMain { 7 | 8 | protected static InsightsPlugin instance; 9 | 10 | public static InsightsPlugin getInstance() { 11 | return instance; 12 | } 13 | 14 | /** 15 | * Reloads all configurations. 16 | */ 17 | public void reloadConfigs() { 18 | if (!getDataFolder().exists()) { 19 | getDataFolder().mkdirs(); 20 | } 21 | reloadSettings(); 22 | reloadMessages(); 23 | reloadNotifications(); 24 | reloadLimits(); 25 | } 26 | 27 | public abstract void reload(); 28 | 29 | public boolean isAvailable(String pluginName) { 30 | Plugin plugin = getServer().getPluginManager().getPlugin(pluginName); 31 | return plugin != null && plugin.isEnabled(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/addons/AddonException.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.addons; 2 | 3 | import java.io.IOException; 4 | 5 | public class AddonException extends IOException { 6 | 7 | public AddonException(String msg) { 8 | super(msg); 9 | } 10 | 11 | public AddonException(Throwable cause) { 12 | super(cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/addons/CuboidRegion.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.addons; 2 | 3 | import dev.frankheijden.insights.api.objects.math.Cuboid; 4 | import dev.frankheijden.insights.api.objects.math.Vector3; 5 | import org.bukkit.World; 6 | 7 | public abstract class CuboidRegion extends Cuboid implements Region { 8 | 9 | /** 10 | * Constructs a new cuboid in given world, with given minimum and maximum vectors. 11 | */ 12 | protected CuboidRegion(World world, Vector3 min, Vector3 max) { 13 | super(world, min, max); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/addons/InsightsAddon.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.addons; 2 | 3 | import org.bukkit.Location; 4 | import java.util.Optional; 5 | 6 | public interface InsightsAddon { 7 | 8 | String getPluginName(); 9 | 10 | String getAreaName(); 11 | 12 | String getVersion(); 13 | 14 | Optional getRegion(Location location); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/addons/MultiCuboidRegion.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.addons; 2 | 3 | import dev.frankheijden.insights.api.objects.chunk.ChunkPart; 4 | import dev.frankheijden.insights.api.objects.math.Cuboid; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | 8 | public abstract class MultiCuboidRegion implements Region { 9 | 10 | protected final List cuboids; 11 | 12 | protected MultiCuboidRegion(List cuboids) { 13 | this.cuboids = cuboids; 14 | } 15 | 16 | @Override 17 | public List toChunkParts() { 18 | return cuboids.stream().flatMap(cuboid -> cuboid.toChunkParts().stream()).collect(Collectors.toList()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/addons/Region.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.addons; 2 | 3 | import dev.frankheijden.insights.api.objects.chunk.ChunkPart; 4 | import java.util.List; 5 | 6 | public interface Region { 7 | 8 | String getAddon(); 9 | 10 | String getKey(); 11 | 12 | List toChunkParts(); 13 | } 14 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/addons/SimpleCuboidRegion.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.addons; 2 | 3 | import dev.frankheijden.insights.api.objects.math.Vector3; 4 | import org.bukkit.World; 5 | import java.util.Objects; 6 | 7 | public class SimpleCuboidRegion extends CuboidRegion { 8 | 9 | protected final String addon; 10 | protected final String key; 11 | 12 | /** 13 | * Constructs a new cuboid in given world, with given minimum and maximum vectors. 14 | * The `addon` parameter is simply the addon plugin, and the `key` must be a unique string to identify this cache. 15 | */ 16 | public SimpleCuboidRegion(World world, Vector3 min, Vector3 max, String addon, String key) { 17 | super(world, min, max); 18 | this.addon = addon; 19 | this.key = key; 20 | } 21 | 22 | @Override 23 | public String getAddon() { 24 | return addon; 25 | } 26 | 27 | @Override 28 | public String getKey() { 29 | return key; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (o == null || getClass() != o.getClass()) return false; 36 | SimpleCuboidRegion that = (SimpleCuboidRegion) o; 37 | return addon.equals(that.addon) && key.equals(that.key); 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return Objects.hash(addon, key); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/addons/SimpleMultiCuboidRegion.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.addons; 2 | 3 | import dev.frankheijden.insights.api.objects.math.Cuboid; 4 | import java.util.List; 5 | import java.util.Objects; 6 | 7 | public class SimpleMultiCuboidRegion extends MultiCuboidRegion { 8 | 9 | protected final String addon; 10 | protected final String key; 11 | 12 | /** 13 | * Constructs a new multi cuboid (simply a list of cuboids). 14 | * The `addon` parameter is simply the addon plugin, and the `key` must be a unique string to identify this cache. 15 | */ 16 | public SimpleMultiCuboidRegion(List cuboids, String addon, String key) { 17 | super(cuboids); 18 | this.addon = addon; 19 | this.key = key; 20 | } 21 | 22 | @Override 23 | public String getAddon() { 24 | return addon; 25 | } 26 | 27 | @Override 28 | public String getKey() { 29 | return key; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (o == null || getClass() != o.getClass()) return false; 36 | SimpleMultiCuboidRegion that = (SimpleMultiCuboidRegion) o; 37 | return addon.equals(that.addon) && key.equals(that.key); 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return Objects.hash(addon, key); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/annotations/AllowDisabling.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface AllowDisabling { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/annotations/AllowPriorityOverride.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface AllowPriorityOverride { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/commands/InsightsCommand.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.commands; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | 5 | public class InsightsCommand { 6 | 7 | protected final InsightsPlugin plugin; 8 | 9 | public InsightsCommand(InsightsPlugin plugin) { 10 | this.plugin = plugin; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/ChunkContainerExecutor.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.concurrent.containers.ChunkContainer; 5 | import dev.frankheijden.insights.api.concurrent.containers.LoadedChunkContainer; 6 | import dev.frankheijden.insights.api.concurrent.containers.RunnableContainer; 7 | import dev.frankheijden.insights.api.concurrent.containers.SupplierContainer; 8 | import dev.frankheijden.insights.api.concurrent.containers.UnloadedChunkContainer; 9 | import dev.frankheijden.insights.api.concurrent.storage.Storage; 10 | import dev.frankheijden.insights.api.concurrent.storage.WorldStorage; 11 | import dev.frankheijden.insights.api.concurrent.tracker.WorldChunkScanTracker; 12 | import dev.frankheijden.insights.api.exceptions.ChunkCuboidOutOfBoundsException; 13 | import dev.frankheijden.insights.api.objects.chunk.ChunkCuboid; 14 | import dev.frankheijden.insights.nms.core.InsightsNMS; 15 | import org.bukkit.Chunk; 16 | import org.bukkit.World; 17 | import java.util.UUID; 18 | import java.util.concurrent.CompletableFuture; 19 | 20 | /** 21 | * Decorator class for ContainerExecutor to add chunk scanning functionality. 22 | */ 23 | public class ChunkContainerExecutor implements ContainerExecutor { 24 | 25 | private final InsightsNMS nms; 26 | private final ContainerExecutor containerExecutor; 27 | private final WorldStorage worldStorage; 28 | private final WorldChunkScanTracker scanTracker; 29 | 30 | /** 31 | * Constructs a new ChunkContainerExecutor. 32 | */ 33 | public ChunkContainerExecutor( 34 | InsightsNMS nms, 35 | ContainerExecutor containerExecutor, 36 | WorldStorage worldStorage, 37 | WorldChunkScanTracker scanTracker 38 | ) { 39 | this.nms = nms; 40 | this.containerExecutor = containerExecutor; 41 | this.worldStorage = worldStorage; 42 | this.scanTracker = scanTracker; 43 | } 44 | 45 | public CompletableFuture submit(Chunk chunk) { 46 | return submit(chunk, ScanOptions.all()); 47 | } 48 | 49 | public CompletableFuture submit(World world, int x, int z) { 50 | return submit(world, x, z, ScanOptions.all()); 51 | } 52 | 53 | public CompletableFuture submit(Chunk chunk, ScanOptions options) { 54 | return submit(chunk, ChunkCuboid.maxCuboid(chunk.getWorld()), options); 55 | } 56 | 57 | public CompletableFuture submit(World world, int x, int z, ScanOptions options) { 58 | return submit(world, x, z, ChunkCuboid.maxCuboid(world), options); 59 | } 60 | 61 | public CompletableFuture submit(Chunk chunk, ChunkCuboid cuboid, ScanOptions options) { 62 | return submit(new LoadedChunkContainer(nms, chunk, cuboid, options), options); 63 | } 64 | 65 | public CompletableFuture submit(World world, int x, int z, ChunkCuboid cuboid, ScanOptions options) { 66 | return submit(new UnloadedChunkContainer(nms, world, x, z, cuboid, options), options); 67 | } 68 | 69 | /** 70 | * Submits a ChunkContainer for scanning, returning a DistributionStorage object. 71 | * DistributionStorage is essentially merged from the scan result and given entities Distribution. 72 | */ 73 | public CompletableFuture submit(ChunkContainer container, ScanOptions options) { 74 | var world = container.getWorld(); 75 | var maxCuboid = ChunkCuboid.maxCuboid(world); 76 | if (!maxCuboid.contains(container.getChunkCuboid())) { 77 | return CompletableFuture.failedFuture(new ChunkCuboidOutOfBoundsException( 78 | maxCuboid, 79 | container.getChunkCuboid() 80 | )); 81 | } 82 | 83 | UUID worldUid = world.getUID(); 84 | long chunkKey = container.getChunkKey(); 85 | if (options.track()) { 86 | scanTracker.set(worldUid, chunkKey, true); 87 | } 88 | 89 | return submit(container).thenApply(storage -> { 90 | if (options.save()) worldStorage.getWorld(worldUid).put(chunkKey, storage); 91 | if (options.track()) scanTracker.set(worldUid, chunkKey, false); 92 | 93 | var metricsManager = InsightsPlugin.getInstance().getMetricsManager(); 94 | metricsManager.getChunkScanMetric().increment(); 95 | metricsManager.getTotalBlocksScanned().add(container.getChunkCuboid().getVolume()); 96 | 97 | return storage; 98 | }); 99 | } 100 | 101 | @Override 102 | public CompletableFuture submit(SupplierContainer container) { 103 | return containerExecutor.submit(container); 104 | } 105 | 106 | @Override 107 | public CompletableFuture submit(RunnableContainer container) { 108 | return containerExecutor.submit(container); 109 | } 110 | 111 | @Override 112 | public void shutdown() { 113 | containerExecutor.shutdown(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/ChunkTeleport.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import io.papermc.lib.PaperLib; 5 | import java.util.concurrent.CompletableFuture; 6 | import org.bukkit.HeightMap; 7 | import org.bukkit.Location; 8 | import org.bukkit.World; 9 | import org.bukkit.entity.Player; 10 | 11 | public class ChunkTeleport { 12 | 13 | private final InsightsPlugin plugin; 14 | 15 | public ChunkTeleport(InsightsPlugin plugin) { 16 | this.plugin = plugin; 17 | } 18 | 19 | /** 20 | * Teleports a player to a chunk. 21 | */ 22 | public CompletableFuture teleportPlayerToChunk(Player player, World world, int x, int z, boolean gen) { 23 | CompletableFuture resultFuture = new CompletableFuture<>(); 24 | PaperLib.getChunkAtAsync(world, x, z, gen).whenComplete((chunk, chunkErr) -> { 25 | if (chunkErr != null) { 26 | resultFuture.completeExceptionally(chunkErr); 27 | return; 28 | } else if (chunk == null) { 29 | resultFuture.complete(Result.NOT_GENERATED); 30 | return; 31 | } 32 | 33 | plugin.getServer().getScheduler().runTask(plugin, () -> { 34 | int blockX = (x << 4) + 8; 35 | int blockZ = (z << 4) + 8; 36 | int blockY = world.getHighestBlockYAt(blockX, blockZ, HeightMap.MOTION_BLOCKING) + 1; 37 | var loc = new Location(world, blockX + .5, blockY, blockZ + .5); 38 | PaperLib.teleportAsync(player, loc).whenComplete((success, tpErr) -> { 39 | if (tpErr != null) { 40 | resultFuture.completeExceptionally(tpErr); 41 | } else if (Boolean.FALSE.equals(success)) { 42 | resultFuture.complete(Result.FAILED); 43 | } else { 44 | resultFuture.complete(Result.SUCCESS); 45 | } 46 | }); 47 | }); 48 | }); 49 | 50 | return resultFuture; 51 | } 52 | 53 | public enum Result { 54 | NOT_GENERATED, 55 | FAILED, 56 | SUCCESS 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/ContainerExecutor.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent; 2 | 3 | import dev.frankheijden.insights.api.concurrent.containers.RunnableContainer; 4 | import dev.frankheijden.insights.api.concurrent.containers.SupplierContainer; 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | public interface ContainerExecutor { 8 | 9 | CompletableFuture submit(SupplierContainer container); 10 | 11 | CompletableFuture submit(RunnableContainer container); 12 | 13 | void shutdown(); 14 | } 15 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/PlayerList.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent; 2 | 3 | import org.bukkit.entity.Player; 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | public class PlayerList implements Iterable> { 11 | 12 | private final Map players; 13 | 14 | /** 15 | * Constructs a new Thread-safe PlayerList with given online players. 16 | */ 17 | public PlayerList(Collection players) { 18 | this.players = new ConcurrentHashMap<>(); 19 | for (Player player : players) { 20 | addPlayer(player); 21 | } 22 | } 23 | 24 | public void addPlayer(Player player) { 25 | this.players.put(player.getUniqueId(), player); 26 | } 27 | 28 | public void removePlayer(Player player) { 29 | this.players.remove(player.getUniqueId()); 30 | } 31 | 32 | public int size() { 33 | return this.players.size(); 34 | } 35 | 36 | @Override 37 | public Iterator> iterator() { 38 | return players.entrySet().iterator(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/ScanOptions.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent; 2 | 3 | public final class ScanOptions { 4 | 5 | private static final ScanOptions ALL = new ScanOptions(true, true, true, true); 6 | private static final ScanOptions SCAN = new ScanOptions(false, false, true, true); 7 | private static final ScanOptions MATERIALS = new ScanOptions(false, false, true, false); 8 | private static final ScanOptions ENTITIES = new ScanOptions(false, false, false, true); 9 | 10 | private final boolean save; 11 | private final boolean track; 12 | private final boolean materials; 13 | private final boolean entities; 14 | 15 | private ScanOptions(boolean save, boolean track, boolean materials, boolean entities) { 16 | this.save = save; 17 | this.track = track; 18 | this.materials = materials; 19 | this.entities = entities; 20 | } 21 | 22 | public static ScanOptions all() { 23 | return ALL; 24 | } 25 | 26 | public static ScanOptions scanOnly() { 27 | return SCAN; 28 | } 29 | 30 | public static ScanOptions materialsOnly() { 31 | return MATERIALS; 32 | } 33 | 34 | public static ScanOptions entitiesOnly() { 35 | return ENTITIES; 36 | } 37 | 38 | public static Builder newBuilder() { 39 | return new Builder(); 40 | } 41 | 42 | public boolean save() { 43 | return save; 44 | } 45 | 46 | public boolean track() { 47 | return track; 48 | } 49 | 50 | public boolean materials() { 51 | return materials; 52 | } 53 | 54 | public boolean entities() { 55 | return entities; 56 | } 57 | 58 | public static class Builder { 59 | 60 | private boolean save = false; 61 | private boolean track = false; 62 | private boolean materials = false; 63 | private boolean entities = false; 64 | 65 | public Builder() {} 66 | 67 | public Builder save() { 68 | this.save = true; 69 | return this; 70 | } 71 | 72 | public Builder track() { 73 | this.track = true; 74 | return this; 75 | } 76 | 77 | public Builder materials() { 78 | this.materials = true; 79 | return this; 80 | } 81 | 82 | public Builder entities() { 83 | this.entities = true; 84 | return this; 85 | } 86 | 87 | public ScanOptions build() { 88 | return new ScanOptions(save, track, materials, entities); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/containers/Container.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.containers; 2 | 3 | public interface Container { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/containers/LoadedChunkContainer.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.containers; 2 | 3 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 4 | import dev.frankheijden.insights.api.objects.chunk.ChunkCuboid; 5 | import dev.frankheijden.insights.nms.core.ChunkEntity; 6 | import dev.frankheijden.insights.nms.core.ChunkSection; 7 | import dev.frankheijden.insights.nms.core.InsightsNMS; 8 | import org.bukkit.Chunk; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | import java.util.function.Consumer; 12 | 13 | public class LoadedChunkContainer extends ChunkContainer { 14 | 15 | private final Chunk chunk; 16 | 17 | /** 18 | * Constructs a new LoadedChunkContainer, for scanning of a loaded chunk. 19 | */ 20 | public LoadedChunkContainer(InsightsNMS nms, Chunk chunk, ChunkCuboid cuboid, ScanOptions options) { 21 | super(nms, chunk.getWorld(), chunk.getX(), chunk.getZ(), cuboid, options); 22 | this.chunk = chunk; 23 | } 24 | 25 | @Override 26 | public void getChunkSections(Consumer<@Nullable ChunkSection> sectionConsumer) { 27 | nms.getLoadedChunkSections(chunk, sectionConsumer); 28 | } 29 | 30 | @Override 31 | public void getChunkEntities(Consumer<@NotNull ChunkEntity> entityConsumer) { 32 | nms.getLoadedChunkEntities(chunk, entityConsumer); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/containers/RunnableContainer.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.containers; 2 | 3 | public interface RunnableContainer extends Runnable, Container { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/containers/SupplierContainer.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.containers; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public interface SupplierContainer extends Supplier, Container { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/containers/UnloadedChunkContainer.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.containers; 2 | 3 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 4 | import dev.frankheijden.insights.api.objects.chunk.ChunkCuboid; 5 | import dev.frankheijden.insights.nms.core.ChunkEntity; 6 | import dev.frankheijden.insights.nms.core.ChunkSection; 7 | import dev.frankheijden.insights.nms.core.InsightsNMS; 8 | import org.bukkit.World; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | import java.io.IOException; 12 | import java.util.function.Consumer; 13 | 14 | public class UnloadedChunkContainer extends ChunkContainer { 15 | 16 | /** 17 | * Constructs a new UnloadedChunkContainer, for scanning of an unloaded chunk. 18 | */ 19 | public UnloadedChunkContainer( 20 | InsightsNMS nms, 21 | World world, 22 | int chunkX, 23 | int chunkZ, 24 | ChunkCuboid cuboid, 25 | ScanOptions options 26 | ) { 27 | super(nms, world, chunkX, chunkZ, cuboid, options); 28 | } 29 | 30 | @Override 31 | public void getChunkSections(Consumer<@Nullable ChunkSection> sectionConsumer) { 32 | nms.getUnloadedChunkSections(world, chunkX, chunkZ, sectionConsumer); 33 | } 34 | 35 | @Override 36 | public void getChunkEntities(Consumer<@NotNull ChunkEntity> entityConsumer) throws IOException { 37 | nms.getUnloadedChunkEntities(world, chunkX, chunkZ, entityConsumer); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/count/IntegerCount.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.count; 2 | 3 | public class IntegerCount { 4 | 5 | private final int[] counts; 6 | private int total = 0; 7 | 8 | public IntegerCount(int size) { 9 | this.counts = new int[size]; 10 | } 11 | 12 | public int getTotal() { 13 | return total; 14 | } 15 | 16 | /** 17 | * Increments the count at given position. 18 | */ 19 | public int increment(int pos) { 20 | counts[pos] += 1; 21 | total += 1; 22 | 23 | return total; 24 | } 25 | 26 | public void reset(int pos) { 27 | total -= counts[pos]; 28 | counts[pos] = 0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/count/RedstoneUpdateCount.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.count; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | 5 | public class RedstoneUpdateCount { 6 | 7 | private final TickResetCount chunkCounts; 8 | private final TickResetCount addonCounts; 9 | 10 | /** 11 | * Constructs a new RedstoneUpdateCount. 12 | */ 13 | public RedstoneUpdateCount(InsightsPlugin plugin) { 14 | this.chunkCounts = new TickResetCount<>( 15 | plugin, 16 | plugin.getSettings().REDSTONE_UPDATE_AGGREGATE_TICKS, 17 | plugin.getSettings().REDSTONE_UPDATE_AGGREGATE_SIZE 18 | ); 19 | this.addonCounts = new TickResetCount<>( 20 | plugin, 21 | plugin.getSettings().REDSTONE_UPDATE_AGGREGATE_TICKS, 22 | plugin.getSettings().REDSTONE_UPDATE_AGGREGATE_SIZE 23 | ); 24 | } 25 | 26 | public void start() { 27 | this.chunkCounts.start(); 28 | this.addonCounts.start(); 29 | } 30 | 31 | public void stop() { 32 | this.chunkCounts.stop(); 33 | this.addonCounts.stop(); 34 | } 35 | 36 | public int increment(long chunkKey) { 37 | return chunkCounts.increment(chunkKey); 38 | } 39 | 40 | public int increment(String regionKey) { 41 | return addonCounts.increment(regionKey); 42 | } 43 | 44 | public void remove(long chunkKey) { 45 | chunkCounts.remove(chunkKey); 46 | } 47 | 48 | public void remove(String regionKey) { 49 | addonCounts.remove(regionKey); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/count/TickResetCount.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.count; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import org.bukkit.scheduler.BukkitTask; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | public class TickResetCount { 9 | 10 | private final InsightsPlugin plugin; 11 | private final int intervalTicks; 12 | private final int size; 13 | private final Map counts; 14 | private final ResetTask resetTask; 15 | private BukkitTask bukkitTask = null; 16 | 17 | /** 18 | * Constructs a new TickResetCount. 19 | */ 20 | public TickResetCount(InsightsPlugin plugin, int intervalTicks, int size) { 21 | this.plugin = plugin; 22 | this.intervalTicks = intervalTicks; 23 | this.size = size; 24 | this.counts = new ConcurrentHashMap<>(); 25 | this.resetTask = new ResetTask(); 26 | } 27 | 28 | /** 29 | * Starts the reset task. 30 | * @throws IllegalStateException If the task is already running. 31 | */ 32 | public void start() { 33 | if (bukkitTask != null) { 34 | throw new IllegalStateException("ResetTask is already running!"); 35 | } 36 | 37 | this.bukkitTask = plugin.getServer().getScheduler().runTaskTimerAsynchronously( 38 | plugin, 39 | resetTask, 40 | 0, 41 | intervalTicks 42 | ); 43 | } 44 | 45 | /** 46 | * Stops the reset task. 47 | * @throws IllegalStateException If the task is not running. 48 | */ 49 | public void stop() { 50 | if (bukkitTask == null) { 51 | throw new IllegalStateException("ResetTask is not running!"); 52 | } 53 | 54 | this.bukkitTask.cancel(); 55 | this.bukkitTask = null; 56 | } 57 | 58 | public int getCurrentTick() { 59 | return resetTask.tick; 60 | } 61 | 62 | public int increment(T key) { 63 | return counts.computeIfAbsent(key, k -> new IntegerCount(size)).increment(resetTask.tick); 64 | } 65 | 66 | public void remove(T key) { 67 | counts.remove(key); 68 | } 69 | 70 | public class ResetTask implements Runnable { 71 | 72 | private int tick; 73 | 74 | public ResetTask() { 75 | this.tick = 0; 76 | } 77 | 78 | @Override 79 | public void run() { 80 | int nextTick = tick + 1; 81 | if (nextTick >= size) { 82 | nextTick = 0; 83 | } 84 | 85 | for (IntegerCount value : counts.values()) { 86 | value.reset(nextTick); 87 | } 88 | 89 | this.tick = nextTick; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/storage/AddonStorage.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.storage; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class AddonStorage { 8 | 9 | private final Map distributionMap; 10 | 11 | public AddonStorage() { 12 | this.distributionMap = new ConcurrentHashMap<>(); 13 | } 14 | 15 | public Optional get(String key) { 16 | return Optional.ofNullable(distributionMap.get(key)); 17 | } 18 | 19 | public void put(String key, Storage storage) { 20 | this.distributionMap.put(key, storage); 21 | } 22 | 23 | public void remove(String key) { 24 | this.distributionMap.remove(key); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/storage/ChunkStorage.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.storage; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | import java.util.Set; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | public class ChunkStorage { 9 | 10 | private final Map distributionMap; 11 | 12 | public ChunkStorage() { 13 | this.distributionMap = new ConcurrentHashMap<>(); 14 | } 15 | 16 | public Set getChunks() { 17 | return distributionMap.keySet(); 18 | } 19 | 20 | public Optional get(long chunkKey) { 21 | return Optional.ofNullable(distributionMap.get(chunkKey)); 22 | } 23 | 24 | public void put(long chunkKey, Storage storage) { 25 | distributionMap.put(chunkKey, storage); 26 | } 27 | 28 | public void remove(long chunkKey) { 29 | distributionMap.remove(chunkKey); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/storage/Distribution.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.storage; 2 | 3 | import dev.frankheijden.insights.api.utils.MapUtils; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import java.util.function.Predicate; 7 | 8 | public class Distribution { 9 | 10 | protected final Map distributionMap; 11 | 12 | public Distribution(Map distributionMap) { 13 | this.distributionMap = distributionMap; 14 | } 15 | 16 | /** 17 | * Retrieves the distribution of all items. 18 | */ 19 | public long count() { 20 | long count = 0; 21 | for (long distribution : distributionMap.values()) { 22 | count += distribution; 23 | } 24 | return count; 25 | } 26 | 27 | /** 28 | * Retrieves the distribution of a single item. 29 | */ 30 | public long count(E item) { 31 | return item == null ? 0 : distributionMap.getOrDefault(item, 0L); 32 | } 33 | 34 | /** 35 | * Retrieves the distribution of items which match the predicate. 36 | */ 37 | public long count(Predicate predicate) { 38 | long count = 0; 39 | for (Map.Entry entry : distributionMap.entrySet()) { 40 | if (predicate.test(entry.getKey())) { 41 | count += entry.getValue(); 42 | } 43 | } 44 | return count; 45 | } 46 | 47 | /** 48 | * Modifies the distribution of given item by an amount. 49 | */ 50 | public void modify(E item, long amount) { 51 | if (item == null) return; 52 | distributionMap.compute(item, (e, count) -> { 53 | if (count == null) count = 0L; 54 | return Math.max(0, count + amount); 55 | }); 56 | } 57 | 58 | /** 59 | * Returns the keys in this distribution. 60 | */ 61 | public Set keys() { 62 | return distributionMap.keySet(); 63 | } 64 | 65 | /** 66 | * Merges the current instance into another distribution, summing their values. 67 | */ 68 | public void mergeRight(Distribution target) { 69 | MapUtils.mergeRight(this.distributionMap, target.distributionMap, Long::sum); 70 | } 71 | 72 | /** 73 | * Copies this instance over to a new Distribution using given map. 74 | */ 75 | public Distribution copy(Map map) { 76 | map.putAll(distributionMap); 77 | return new Distribution<>(map); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/storage/DistributionStorage.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.storage; 2 | 3 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 4 | import org.bukkit.Material; 5 | import org.bukkit.entity.EntityType; 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | public class DistributionStorage extends Distribution> implements Storage { 10 | 11 | public DistributionStorage() { 12 | this(new ConcurrentHashMap<>()); 13 | } 14 | 15 | public DistributionStorage(Map, Long> map) { 16 | super(map); 17 | } 18 | 19 | /** 20 | * Constructs a new DistributionStorage from given material and entity distributions. 21 | */ 22 | public static DistributionStorage of(Distribution materials, Distribution entities) { 23 | return of(materials.distributionMap, entities.distributionMap); 24 | } 25 | 26 | /** 27 | * Constructs a new DistributionStorage from given material and entity distribution maps. 28 | */ 29 | public static DistributionStorage of(Map materials, Map entities) { 30 | Map, Long> map = new ConcurrentHashMap<>(); 31 | for (Map.Entry entry : materials.entrySet()) { 32 | map.put(ScanObject.of(entry.getKey()), entry.getValue()); 33 | } 34 | for (Map.Entry entry : entities.entrySet()) { 35 | map.put(ScanObject.of(entry.getKey()), entry.getValue()); 36 | } 37 | return new DistributionStorage(map); 38 | } 39 | 40 | @Override 41 | public DistributionStorage copy(Map, Long> map) { 42 | map.putAll(distributionMap); 43 | return new DistributionStorage(map); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/storage/ScanHistory.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.storage; 2 | 3 | import dev.frankheijden.insights.api.config.Messages; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | import java.util.UUID; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | public class ScanHistory { 10 | 11 | private final Map> historyMap; 12 | 13 | public ScanHistory() { 14 | this.historyMap = new ConcurrentHashMap<>(); 15 | } 16 | 17 | public void setHistory(UUID player, Messages.PaginatedMessage message) { 18 | historyMap.put(player, message); 19 | } 20 | 21 | public Optional> getHistory(UUID player) { 22 | return Optional.ofNullable(historyMap.get(player)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/storage/Storage.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.storage; 2 | 3 | import dev.frankheijden.insights.api.config.limits.Limit; 4 | import dev.frankheijden.insights.api.config.limits.LimitType; 5 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 6 | import java.util.Collection; 7 | import java.util.Set; 8 | import java.util.function.Predicate; 9 | 10 | public interface Storage { 11 | 12 | Set> keys(); 13 | 14 | default long count() { 15 | return count(keys()); 16 | } 17 | 18 | long count(ScanObject item); 19 | 20 | /** 21 | * Counts the summed distribution for given ScanObjects. 22 | */ 23 | default long count(Collection> items) { 24 | long count = 0; 25 | for (ScanObject item : items) { 26 | count += count(item); 27 | } 28 | return count; 29 | } 30 | 31 | /** 32 | * Counts the distribution for all ScanObjects of a limit. 33 | */ 34 | default long count(Limit limit) { 35 | return count(limit.getScanObjects()); 36 | } 37 | 38 | /** 39 | * Counts the distribution for given limit and item. 40 | * Item must be of type Material or EntityType. 41 | */ 42 | default long count(Limit limit, ScanObject item) { 43 | return limit.getType() == LimitType.PERMISSION ? count(item) : count(limit); 44 | } 45 | 46 | /** 47 | * Counts the distribution of items which match the predicate. 48 | */ 49 | default long count(Predicate> predicate) { 50 | long count = 0; 51 | for (ScanObject key : keys()) { 52 | if (predicate.test(key)) { 53 | count += count(key); 54 | } 55 | } 56 | return count; 57 | } 58 | 59 | void modify(ScanObject item, long amount); 60 | 61 | void mergeRight(Distribution> target); 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/storage/WorldStorage.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.storage; 2 | 3 | import java.util.Map; 4 | import java.util.UUID; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class WorldStorage { 8 | 9 | private final Map chunkMap; 10 | 11 | public WorldStorage() { 12 | this.chunkMap = new ConcurrentHashMap<>(); 13 | } 14 | 15 | public ChunkStorage getWorld(UUID worldUid) { 16 | return chunkMap.computeIfAbsent(worldUid, k -> new ChunkStorage()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/tracker/AddonScanTracker.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.tracker; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class AddonScanTracker { 8 | 9 | private final Set tracker; 10 | 11 | public AddonScanTracker() { 12 | this.tracker = Collections.newSetFromMap(new ConcurrentHashMap<>()); 13 | } 14 | 15 | public void add(String key) { 16 | this.tracker.add(key); 17 | } 18 | 19 | public boolean isQueued(String key) { 20 | return this.tracker.contains(key); 21 | } 22 | 23 | public void remove(String key) { 24 | this.tracker.remove(key); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/tracker/ChunkScanTracker.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.tracker; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class ChunkScanTracker implements ScanTracker { 8 | 9 | private final Set queuedChunks; 10 | 11 | public ChunkScanTracker() { 12 | this.queuedChunks = Collections.newSetFromMap(new ConcurrentHashMap<>()); 13 | } 14 | 15 | @Override 16 | public boolean set(Long obj, boolean queued) { 17 | return queued ? queuedChunks.add(obj) : queuedChunks.remove(obj); 18 | } 19 | 20 | @Override 21 | public boolean isQueued(Long obj) { 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/tracker/ScanTracker.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.tracker; 2 | 3 | public interface ScanTracker { 4 | 5 | boolean set(T obj, boolean queued); 6 | 7 | boolean isQueued(T obj); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/concurrent/tracker/WorldChunkScanTracker.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.concurrent.tracker; 2 | 3 | import java.util.Map; 4 | import java.util.UUID; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class WorldChunkScanTracker { 8 | 9 | private final Map scanTrackerMap; 10 | 11 | public WorldChunkScanTracker() { 12 | this.scanTrackerMap = new ConcurrentHashMap<>(); 13 | } 14 | 15 | public ChunkScanTracker getChunkScanTracker(UUID worldUid) { 16 | return scanTrackerMap.computeIfAbsent(worldUid, k -> new ChunkScanTracker()); 17 | } 18 | 19 | public void set(UUID worldUid, long chunkKey, boolean queued) { 20 | getChunkScanTracker(worldUid).set(chunkKey, queued); 21 | } 22 | 23 | public boolean isQueued(UUID worldUid, long chunkKey) { 24 | return getChunkScanTracker(worldUid).isQueued(chunkKey); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/ConfigError.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ConfigError { 7 | 8 | private final String fileName; 9 | private final String path; 10 | private final String error; 11 | 12 | /** 13 | * Constructs a new ConfigError using given parameters. 14 | */ 15 | public ConfigError(String fileName, String path, String error) { 16 | this.fileName = fileName; 17 | this.path = path; 18 | this.error = error; 19 | } 20 | 21 | public static Builder newBuilder() { 22 | return new Builder(); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "File '" + fileName + "' at path '" + path + "': " + error; 28 | } 29 | 30 | public static final class Builder { 31 | 32 | private final List errors; 33 | 34 | public Builder() { 35 | this.errors = new ArrayList<>(); 36 | } 37 | 38 | public Builder append(String fileName, String path, String error) { 39 | return append(new ConfigError(fileName, path, error)); 40 | } 41 | 42 | public Builder append(ConfigError error) { 43 | this.errors.add(error); 44 | return this; 45 | } 46 | 47 | public List getErrors() { 48 | return errors; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/LimitEnvironment.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config; 2 | 3 | import dev.frankheijden.insights.api.config.limits.Limit; 4 | import org.bukkit.entity.Player; 5 | import java.util.function.Predicate; 6 | 7 | public class LimitEnvironment implements Predicate { 8 | 9 | private final Player player; 10 | private final String worldName; 11 | private final String addon; 12 | 13 | public LimitEnvironment(Player player, String worldName) { 14 | this(player, worldName, null); 15 | } 16 | 17 | /** 18 | * Constructs a new LimitEnvironment for a given player, in a given world, and given addon. 19 | */ 20 | public LimitEnvironment(Player player, String worldName, String addon) { 21 | this.player = player; 22 | this.worldName = worldName; 23 | this.addon = addon; 24 | } 25 | 26 | @Override 27 | public boolean test(Limit limit) { 28 | return limit.getSettings().appliesToWorld(worldName) 29 | && (addon == null || limit.getSettings().appliesToAddon(addon)) 30 | && !player.hasPermission(limit.getBypassPermission()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/Limits.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config; 2 | 3 | import static java.util.Comparator.comparingInt; 4 | 5 | import dev.frankheijden.insights.api.InsightsPlugin; 6 | import dev.frankheijden.insights.api.config.limits.Limit; 7 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 8 | import dev.frankheijden.insights.api.utils.SetUtils; 9 | import org.bukkit.Material; 10 | import org.bukkit.entity.EntityType; 11 | import java.util.ArrayList; 12 | import java.util.EnumMap; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Optional; 17 | import java.util.Set; 18 | import java.util.TreeSet; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.function.Predicate; 21 | 22 | public class Limits { 23 | 24 | private final List limits; 25 | private final Map limitsByFileName; 26 | private final Map> materialLimits; 27 | private final Map> entityLimits; 28 | 29 | /** 30 | * Constructs a new Limits object, holding a data structure for fast lookup of limits. 31 | */ 32 | public Limits() { 33 | limits = new ArrayList<>(); 34 | limitsByFileName = new ConcurrentHashMap<>(); 35 | materialLimits = new EnumMap<>(Material.class); 36 | entityLimits = new EnumMap<>(EntityType.class); 37 | } 38 | 39 | /** 40 | * Adds the given limit to the data structure. 41 | */ 42 | public void addLimit(Limit limit) { 43 | this.limits.add(limit); 44 | this.limitsByFileName.put(limit.getFile().getName(), limit); 45 | for (Material m : limit.getMaterials()) { 46 | materialLimits.computeIfAbsent(m, k -> new TreeSet<>( 47 | comparingInt(l -> l.getLimit(k).getLimit()) 48 | )).add(limit); 49 | } 50 | for (EntityType e : limit.getEntities()) { 51 | entityLimits.computeIfAbsent(e, k -> new TreeSet<>( 52 | comparingInt(l -> l.getLimit(k).getLimit()) 53 | )).add(limit); 54 | } 55 | } 56 | 57 | public List getLimits() { 58 | return new ArrayList<>(limits); 59 | } 60 | 61 | /** 62 | * Retrieves the first limit (sorted ascending on limit, such that the smallest limit is applied). 63 | * Item must be of type Material or EntityType. 64 | */ 65 | public Optional getFirstLimit(ScanObject item, Predicate limitPredicate) { 66 | switch (item.getType()) { 67 | case MATERIAL: return getFirstLimit((Material) item.getObject(), limitPredicate); 68 | case ENTITY: return getFirstLimit((EntityType) item.getObject(), limitPredicate); 69 | default: throw new IllegalArgumentException("Item is of unsupported limit type '" + item.getClass() + "'"); 70 | } 71 | } 72 | 73 | /** 74 | * Retrieves the first limit (sorted ascending on limit, such that the smallest limit is applied). 75 | */ 76 | public Optional getFirstLimit(Material material, Predicate limitPredicate) { 77 | InsightsPlugin.getInstance().getMetricsManager().getLimitMetric().increment(); 78 | Set set = materialLimits.get(material); 79 | return set == null ? Optional.empty() : Optional.ofNullable(SetUtils.findFirst(set, limitPredicate)); 80 | } 81 | 82 | /** 83 | * Retrieves the first limit (sorted ascending on limit, such that the smallest limit is applied). 84 | */ 85 | public Optional getFirstLimit(EntityType entity, Predicate limitPredicate) { 86 | InsightsPlugin.getInstance().getMetricsManager().getLimitMetric().increment(); 87 | Set set = entityLimits.get(entity); 88 | return set == null ? Optional.empty() : Optional.ofNullable(SetUtils.findFirst(set, limitPredicate)); 89 | } 90 | 91 | /** 92 | * Retrieves the limit by their filename. 93 | */ 94 | public Optional getLimitByFileName(String fileName) { 95 | return Optional.ofNullable(limitsByFileName.get(fileName)); 96 | } 97 | 98 | /** 99 | * Lists the filenames of all limits. 100 | */ 101 | public Set getLimitFileNames() { 102 | return new HashSet<>(limitsByFileName.keySet()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/Monad.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config; 2 | 3 | import java.util.List; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | public class Monad { 8 | 9 | private final T object; 10 | private final List errors; 11 | 12 | public Monad(T object, List errors) { 13 | this.object = object; 14 | this.errors = errors; 15 | } 16 | 17 | public T getObject() { 18 | return object; 19 | } 20 | 21 | public List getErrors() { 22 | return errors; 23 | } 24 | 25 | /** 26 | * Completes the monad, logging any errors, if any. 27 | */ 28 | public T exceptionally(Logger logger) { 29 | for (ConfigError error : errors) { 30 | logger.log(Level.SEVERE, error.toString()); 31 | } 32 | return object; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/Notifications.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.notifications.AbstractNotificationFactory; 5 | import dev.frankheijden.insights.api.config.notifications.Notification; 6 | import dev.frankheijden.insights.api.config.notifications.NotificationFactory; 7 | import dev.frankheijden.insights.api.config.notifications.ProgressNotification; 8 | import dev.frankheijden.insights.api.config.notifications.ProgressNotificationFactory; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.UUID; 12 | 13 | public class Notifications { 14 | 15 | private final NotificationFactory notificationFactory; 16 | private final ProgressNotificationFactory progressNotificationFactory; 17 | private final Messages messages; 18 | private final Settings.NotificationType type; 19 | private final Map> notificationMap = new HashMap<>(); 20 | private final Map> progressNotificationMap = new HashMap<>(); 21 | 22 | /** 23 | * Constructs a new Notifications Facade. 24 | */ 25 | public Notifications(InsightsPlugin plugin) { 26 | this.notificationFactory = new NotificationFactory(plugin); 27 | this.progressNotificationFactory = new ProgressNotificationFactory(plugin); 28 | this.messages = plugin.getMessages(); 29 | this.type = plugin.getSettings().NOTIFICATION_TYPE; 30 | } 31 | 32 | private T createNotification( 33 | AbstractNotificationFactory factory, 34 | Messages.Message content 35 | ) { 36 | return switch (type) { 37 | case ACTIONBAR -> factory.actionBar(content); 38 | case BOSSBAR -> factory.bossBar(content); 39 | default -> throw new IllegalArgumentException("Notification Type '" + type + "' is not implemented!"); 40 | }; 41 | } 42 | 43 | /** 44 | * Creates a new notification from given factory and message locale at given path. 45 | */ 46 | private T get(AbstractNotificationFactory factory, Messages.Key key) { 47 | Messages.Message message = messages.getMessage(key); 48 | return message == null ? factory.empty() : createNotification(factory, message); 49 | } 50 | 51 | public Notification get(Messages.Key messageKey) { 52 | return get(notificationFactory, messageKey); 53 | } 54 | 55 | /** 56 | * Retrieves the cached Notification, if present, or create a new one if it isn't present. 57 | */ 58 | public Notification getCached(UUID uuid, Messages.Key messageKey) { 59 | return notificationMap.compute(uuid, (uuid1, cache) -> { 60 | if (cache == null || cache.getKey() != messageKey) { 61 | return new Cache<>(get(messageKey), messageKey); 62 | } 63 | return cache; 64 | }).getNotification(); 65 | } 66 | 67 | /** 68 | * Retrieves the cached ProgressNotification, if present, or create a new one if it isn't present. 69 | */ 70 | public ProgressNotification getCachedProgress(UUID uuid, Messages.Key messageKey) { 71 | return progressNotificationMap.compute(uuid, (uuid1, cache) -> { 72 | if (cache == null || cache.getKey() != messageKey) { 73 | return new Cache<>(getProgress(messageKey), messageKey); 74 | } 75 | return cache; 76 | }).getNotification(); 77 | } 78 | 79 | public ProgressNotification getProgress(Messages.Key messageKey) { 80 | return get(progressNotificationFactory, messageKey); 81 | } 82 | 83 | /** 84 | * Clears all notifications. 85 | */ 86 | public void clearNotifications() { 87 | for (Cache cache : notificationMap.values()) { 88 | cache.getNotification().clear(); 89 | } 90 | notificationMap.clear(); 91 | for (Cache cache : progressNotificationMap.values()) { 92 | cache.getNotification().clear(); 93 | } 94 | progressNotificationMap.clear(); 95 | } 96 | 97 | private static final class Cache { 98 | 99 | private final T notification; 100 | private final Messages.Key key; 101 | 102 | private Cache(T notification, Messages.Key key) { 103 | this.notification = notification; 104 | this.key = key; 105 | } 106 | 107 | public T getNotification() { 108 | return notification; 109 | } 110 | 111 | public Messages.Key getKey() { 112 | return key; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/limits/GroupLimit.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.limits; 2 | 3 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 4 | import dev.frankheijden.insights.api.config.parser.YamlParseException; 5 | import dev.frankheijden.insights.api.config.parser.YamlParser; 6 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 7 | import org.bukkit.Material; 8 | import org.bukkit.entity.EntityType; 9 | import java.util.Collections; 10 | import java.util.EnumSet; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | public class GroupLimit extends Limit { 15 | 16 | private final String name; 17 | private final int limit; 18 | private final Set materials; 19 | private final Set entities; 20 | private final Set> scanObjects; 21 | private final ScanOptions scanOptions; 22 | 23 | protected GroupLimit(Info info, String name, int limit, Set materials, Set entities) { 24 | super(LimitType.GROUP, info); 25 | this.name = name; 26 | this.limit = limit; 27 | this.materials = Collections.unmodifiableSet(materials); 28 | this.entities = Collections.unmodifiableSet(entities); 29 | this.scanObjects = Collections.unmodifiableSet(ScanObject.of(materials, entities)); 30 | this.scanOptions = determineScanOptions(); 31 | } 32 | 33 | /** 34 | * Parses a GroupLimit. 35 | */ 36 | public static GroupLimit parse(YamlParser parser, Info info) throws YamlParseException { 37 | String name = parser.getString("limit.name", null, true); 38 | int limit = parser.getInt("limit.limit", -1, 0, Integer.MAX_VALUE); 39 | boolean regex = parser.getBoolean("limit.regex", false, false); 40 | List materials = regex 41 | ? parser.getRegexEnums("limit.materials", Material.class) 42 | : parser.getEnums("limit.materials", Material.class, "material"); 43 | List entities = regex 44 | ? parser.getRegexEnums("limit.entities", EntityType.class) 45 | : parser.getEnums("limit.entities", EntityType.class, "entity"); 46 | return new GroupLimit( 47 | info, 48 | name, 49 | limit, 50 | materials.isEmpty() ? Collections.emptySet() : EnumSet.copyOf(materials), 51 | entities.isEmpty() ? Collections.emptySet() : EnumSet.copyOf(entities) 52 | ); 53 | } 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | 59 | public int getLimit() { 60 | return limit; 61 | } 62 | 63 | @Override 64 | public LimitInfo getLimit(Material m) { 65 | return new LimitInfo(name, limit); 66 | } 67 | 68 | @Override 69 | public LimitInfo getLimit(EntityType e) { 70 | return new LimitInfo(name, limit); 71 | } 72 | 73 | @Override 74 | public Set getMaterials() { 75 | return materials; 76 | } 77 | 78 | @Override 79 | public Set getEntities() { 80 | return entities; 81 | } 82 | 83 | @Override 84 | public Set> getScanObjects() { 85 | return scanObjects; 86 | } 87 | 88 | @Override 89 | public ScanOptions getScanOptions() { 90 | return scanOptions; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/limits/LimitInfo.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.limits; 2 | 3 | public class LimitInfo { 4 | 5 | private final String name; 6 | private final int limit; 7 | 8 | /** 9 | * Constructs a new LimitInfo object containing the name of the limit and the actual limit. 10 | */ 11 | public LimitInfo(String name, int limit) { 12 | this.name = name; 13 | this.limit = limit; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public int getLimit() { 21 | return limit; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/limits/LimitParseException.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.limits; 2 | 3 | import dev.frankheijden.insights.api.config.parser.YamlParseException; 4 | 5 | public class LimitParseException extends YamlParseException { 6 | 7 | public LimitParseException(String s) { 8 | super(s); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/limits/LimitSettings.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.limits; 2 | 3 | import java.util.Set; 4 | 5 | public class LimitSettings { 6 | 7 | private final Set worlds; 8 | private final boolean worldWhitelist; 9 | private final Set addons; 10 | private final boolean addonWhitelist; 11 | private final boolean disallowPlacementOutsideRegion; 12 | 13 | /** 14 | * Constructs a new LimitSettings object. 15 | */ 16 | public LimitSettings( 17 | Set worlds, 18 | boolean worldWhitelist, 19 | Set addons, 20 | boolean addonWhitelist, 21 | boolean disallowPlacementOutsideRegion 22 | ) { 23 | this.worlds = worlds; 24 | this.worldWhitelist = worldWhitelist; 25 | this.addons = addons; 26 | this.addonWhitelist = addonWhitelist; 27 | this.disallowPlacementOutsideRegion = disallowPlacementOutsideRegion; 28 | } 29 | 30 | public Set getWorlds() { 31 | return worlds; 32 | } 33 | 34 | public boolean isWorldWhitelist() { 35 | return worldWhitelist; 36 | } 37 | 38 | /** 39 | * Checks whether this limit can be applied on given world. 40 | */ 41 | public boolean appliesToWorld(String worldName) { 42 | if (worldWhitelist) { 43 | return worlds.contains(worldName); 44 | } else { 45 | return !worlds.contains(worldName); 46 | } 47 | } 48 | 49 | public Set getAddons() { 50 | return addons; 51 | } 52 | 53 | public boolean isAddonWhitelist() { 54 | return addonWhitelist; 55 | } 56 | 57 | public boolean isDisallowedPlacementOutsideRegion() { 58 | return disallowPlacementOutsideRegion; 59 | } 60 | 61 | /** 62 | * Checks whether this limit can be applied on given addon. 63 | */ 64 | public boolean appliesToAddon(String addonName) { 65 | if (addonWhitelist) { 66 | return addons.contains(addonName); 67 | } else { 68 | return !addons.contains(addonName); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/limits/LimitType.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.limits; 2 | 3 | public enum LimitType { 4 | TILE, 5 | PERMISSION, 6 | GROUP 7 | } 8 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/limits/PermissionLimit.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.limits; 2 | 3 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 4 | import dev.frankheijden.insights.api.config.parser.YamlParseException; 5 | import dev.frankheijden.insights.api.config.parser.YamlParser; 6 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 7 | import dev.frankheijden.insights.api.utils.EnumUtils; 8 | import org.bukkit.Material; 9 | import org.bukkit.entity.EntityType; 10 | import java.util.Collections; 11 | import java.util.EnumMap; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | public class PermissionLimit extends Limit { 16 | 17 | private final Map materials; 18 | private final Map entities; 19 | private final Set> scanObjects; 20 | private final ScanOptions scanOptions; 21 | 22 | protected PermissionLimit(Info info, Map materials, Map entities) { 23 | super(LimitType.PERMISSION, info); 24 | this.materials = Collections.unmodifiableMap(materials); 25 | this.entities = Collections.unmodifiableMap(entities); 26 | this.scanObjects = Collections.unmodifiableSet(ScanObject.of(materials.keySet(), entities.keySet())); 27 | this.scanOptions = determineScanOptions(); 28 | } 29 | 30 | /** 31 | * Parses a PermissionLimit. 32 | */ 33 | public static PermissionLimit parse(YamlParser parser, Info info) throws YamlParseException { 34 | Map materials = new EnumMap<>(Material.class); 35 | for (String key : parser.getKeys("limit.materials")) { 36 | String fullKey = "limit.materials." + key; 37 | Material material = parser.checkEnum(fullKey, key, Material.class, null, "material"); 38 | int limit = parser.getInt(fullKey, -1, 0, Integer.MAX_VALUE); 39 | materials.put(material, limit); 40 | } 41 | 42 | Map entities = new EnumMap<>(EntityType.class); 43 | for (String key : parser.getKeys("limit.entities")) { 44 | String fullKey = "limit.entities." + key; 45 | EntityType entity = parser.checkEnum(fullKey, key, EntityType.class, null, "entity"); 46 | int limit = parser.getInt(fullKey, -1, 0, Integer.MAX_VALUE); 47 | entities.put(entity, limit); 48 | } 49 | 50 | return new PermissionLimit(info, materials, entities); 51 | } 52 | 53 | @Override 54 | public LimitInfo getLimit(Material m) { 55 | return new LimitInfo(EnumUtils.pretty(m), materials.getOrDefault(m, -1)); 56 | } 57 | 58 | @Override 59 | public LimitInfo getLimit(EntityType e) { 60 | return new LimitInfo(EnumUtils.pretty(e), entities.getOrDefault(e, -1)); 61 | } 62 | 63 | @Override 64 | public Set getMaterials() { 65 | return materials.keySet(); 66 | } 67 | 68 | public Set getEntities() { 69 | return entities.keySet(); 70 | } 71 | 72 | @Override 73 | public Set> getScanObjects() { 74 | return scanObjects; 75 | } 76 | 77 | @Override 78 | public ScanOptions getScanOptions() { 79 | return scanOptions; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/limits/TileLimit.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.limits; 2 | 3 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 4 | import dev.frankheijden.insights.api.config.parser.YamlParseException; 5 | import dev.frankheijden.insights.api.config.parser.YamlParser; 6 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 7 | import dev.frankheijden.insights.api.reflection.RTileEntityTypes; 8 | import dev.frankheijden.insights.api.utils.EnumUtils; 9 | import org.bukkit.Material; 10 | import org.bukkit.entity.EntityType; 11 | import java.util.Collections; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | public class TileLimit extends Limit { 16 | 17 | private final String name; 18 | private final int limit; 19 | private final Set effectiveMaterials; 20 | private final Set> effectiveScanObjects; 21 | private final ScanOptions scanOptions; 22 | 23 | protected TileLimit(Info info, String name, int limit, Set excludedMaterials) { 24 | super(LimitType.TILE, info); 25 | this.name = name; 26 | this.limit = limit; 27 | this.effectiveMaterials = EnumUtils.difference(RTileEntityTypes.getTileEntityMaterials(), excludedMaterials); 28 | 29 | Set> tileEntityScanObjects = new HashSet<>(RTileEntityTypes.getTileEntities()); 30 | tileEntityScanObjects.removeAll(ScanObject.of(excludedMaterials)); 31 | this.effectiveScanObjects = Collections.unmodifiableSet(tileEntityScanObjects); 32 | this.scanOptions = determineScanOptions(); 33 | } 34 | 35 | /** 36 | * Parses a TileLimit. 37 | */ 38 | public static TileLimit parse(YamlParser parser, Info info) throws YamlParseException { 39 | String name = parser.getString("limit.name", null, true); 40 | int limit = parser.getInt("limit.limit", -1, 0, Integer.MAX_VALUE); 41 | Set excludedMaterials = new HashSet<>(parser.getEnums("limit.excluded-materials", Material.class)); 42 | return new TileLimit(info, name, limit, excludedMaterials); 43 | } 44 | 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | public int getLimit() { 50 | return limit; 51 | } 52 | 53 | @Override 54 | public LimitInfo getLimit(Material m) { 55 | return new LimitInfo(name, limit); 56 | } 57 | 58 | @Override 59 | public LimitInfo getLimit(EntityType e) { 60 | return new LimitInfo(name, -1); 61 | } 62 | 63 | @Override 64 | public Set getMaterials() { 65 | return effectiveMaterials; 66 | } 67 | 68 | @Override 69 | public Set getEntities() { 70 | return Collections.emptySet(); 71 | } 72 | 73 | @Override 74 | public Set> getScanObjects() { 75 | return effectiveScanObjects; 76 | } 77 | 78 | @Override 79 | public ScanOptions getScanOptions() { 80 | return scanOptions; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/AbstractNotificationFactory.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | import dev.frankheijden.insights.api.config.Settings; 6 | import net.kyori.adventure.bossbar.BossBar; 7 | import net.kyori.adventure.text.Component; 8 | 9 | public abstract class AbstractNotificationFactory { 10 | 11 | protected final InsightsPlugin plugin; 12 | protected final Settings settings; 13 | 14 | protected AbstractNotificationFactory(InsightsPlugin plugin) { 15 | this.plugin = plugin; 16 | this.settings = plugin.getSettings(); 17 | } 18 | 19 | protected BossBar createBossBar(Messages.Message title) { 20 | return BossBar.bossBar( 21 | title.toComponent().orElse(Component.empty()), 22 | 0, 23 | settings.NOTIFICATION_BOSSBAR_COLOR, 24 | settings.NOTIFICATION_BOSSBAR_OVERLAY, 25 | settings.NOTIFICATION_BOSSBAR_FLAGS 26 | ); 27 | } 28 | 29 | public abstract T bossBar(Messages.Message title); 30 | 31 | public abstract T actionBar(Messages.Message content); 32 | 33 | public abstract T empty(); 34 | } 35 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/ActionBarNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | import org.bukkit.entity.Player; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | 10 | public class ActionBarNotification implements Notification { 11 | 12 | protected InsightsPlugin plugin; 13 | protected Messages.Message content; 14 | protected final Map receivers; 15 | 16 | protected ActionBarNotification(InsightsPlugin plugin, Messages.Message content) { 17 | this.plugin = plugin; 18 | this.content = content; 19 | this.receivers = new HashMap<>(); 20 | } 21 | 22 | @Override 23 | public ActionBarNotification add(Player player) { 24 | receivers.put(player.getUniqueId(), player); 25 | return this; 26 | } 27 | 28 | @Override 29 | public SendableNotification create() { 30 | return new SendableNotification(content.resetTemplates()) { 31 | @Override 32 | public void send() { 33 | var audiences = plugin.getMessages().getAudiences(); 34 | content.toComponent().ifPresent(component -> receivers.values() 35 | .forEach(player -> audiences.player(player).sendActionBar(component))); 36 | } 37 | }; 38 | } 39 | 40 | @Override 41 | public void clear() { 42 | // Actionbar clears itself 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/ActionBarProgressNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | 6 | public class ActionBarProgressNotification extends ActionBarNotification implements ProgressNotification { 7 | 8 | protected final String rawContent; 9 | protected final int segments; 10 | protected final String doneColor; 11 | protected final String totalColor; 12 | protected final String progressSequence; 13 | protected final String separator; 14 | 15 | protected ActionBarProgressNotification( 16 | InsightsPlugin plugin, 17 | Messages.Message content, 18 | int segments, 19 | String doneColor, 20 | String totalColor, 21 | String progressSequence, 22 | String separator 23 | ) { 24 | super(plugin, content); 25 | this.rawContent = content.getRawContent(); 26 | this.segments = segments; 27 | this.doneColor = doneColor; 28 | this.totalColor = totalColor; 29 | this.progressSequence = progressSequence; 30 | this.separator = separator; 31 | } 32 | 33 | @Override 34 | public ActionBarProgressNotification progress(float progress) { 35 | progress = Math.max(0, Math.min(1, progress)); 36 | int cut = (int) (progress * segments); 37 | StringBuilder sb = new StringBuilder(segments * progressSequence.length() 38 | + doneColor.length() 39 | + totalColor.length() 40 | + separator.length() 41 | + rawContent.length() 42 | ); 43 | 44 | sb.append(doneColor); 45 | for (int i = 0; i < cut; i++) { 46 | sb.append(progressSequence); 47 | } 48 | sb.append(totalColor); 49 | for (int i = cut; i < segments; i++) { 50 | sb.append(progressSequence); 51 | } 52 | this.content.setRawContent(sb.append(separator).append(rawContent).toString()); 53 | return this; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/BossBarNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | import net.kyori.adventure.audience.Audience; 6 | import net.kyori.adventure.bossbar.BossBar; 7 | import net.kyori.adventure.text.Component; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.scheduler.BukkitTask; 11 | import java.util.LinkedList; 12 | import java.util.Queue; 13 | 14 | public class BossBarNotification implements Notification { 15 | 16 | protected final InsightsPlugin plugin; 17 | protected final BossBar bossBar; 18 | protected final Messages.Message content; 19 | protected final Queue receivers = new LinkedList<>(); 20 | protected final Queue viewers = new LinkedList<>(); 21 | protected final int ticks; 22 | protected final Runnable bossBarClearer; 23 | protected BukkitTask task; 24 | 25 | protected BossBarNotification(InsightsPlugin plugin, BossBar bossBar, Messages.Message content, int ticks) { 26 | this.plugin = plugin; 27 | this.bossBar = bossBar; 28 | this.content = content; 29 | this.ticks = ticks; 30 | this.bossBarClearer = () -> { 31 | while (!viewers.isEmpty()) { 32 | viewers.poll().hideBossBar(bossBar); 33 | } 34 | }; 35 | } 36 | 37 | @Override 38 | public BossBarNotification add(Player player) { 39 | receivers.add(plugin.getMessages().getAudiences().player(player)); 40 | return this; 41 | } 42 | 43 | @Override 44 | public SendableNotification create() { 45 | return new SendableNotification(content.resetTemplates()) { 46 | @Override 47 | public void send() { 48 | if (task != null) { 49 | task.cancel(); 50 | } 51 | bossBar.name(content.toComponent().orElse(Component.empty())); 52 | 53 | while (!receivers.isEmpty()) { 54 | var audience = receivers.poll(); 55 | audience.showBossBar(bossBar); 56 | viewers.add(audience); 57 | } 58 | task = Bukkit.getScheduler().runTaskLater(plugin, bossBarClearer, ticks); 59 | } 60 | }; 61 | } 62 | 63 | @Override 64 | public void clear() { 65 | bossBarClearer.run(); 66 | if (task != null) { 67 | task.cancel(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/BossBarProgressNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | import net.kyori.adventure.bossbar.BossBar; 6 | 7 | public class BossBarProgressNotification extends BossBarNotification implements ProgressNotification { 8 | 9 | protected BossBarProgressNotification(InsightsPlugin plugin, BossBar bossBar, Messages.Message content, int ticks) { 10 | super(plugin, bossBar, content, ticks); 11 | } 12 | 13 | @Override 14 | public BossBarProgressNotification progress(float progress) { 15 | bossBar.progress(Math.max(0, Math.min(1, progress))); 16 | return this; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/EmptyNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | public class EmptyNotification implements Notification { 6 | 7 | private static EmptyNotification instance = null; 8 | 9 | /** 10 | * Static getter for an empty Notification. 11 | */ 12 | public static EmptyNotification get() { 13 | if (instance == null) { 14 | instance = new EmptyNotification(); 15 | } 16 | return instance; 17 | } 18 | 19 | protected EmptyNotification() {} 20 | 21 | @Override 22 | public EmptyNotification add(Player player) { 23 | return this; 24 | } 25 | 26 | @Override 27 | public SendableNotification create() { 28 | return new SendableNotification(null) { 29 | @Override 30 | public void send() { 31 | // Nothing to send. 32 | } 33 | }; 34 | } 35 | 36 | @Override 37 | public void clear() { 38 | // Nothing to clear 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/EmptyProgressNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | public class EmptyProgressNotification extends EmptyNotification implements ProgressNotification { 4 | 5 | private static EmptyProgressNotification instance = null; 6 | 7 | /** 8 | * Static getter for an empty ProgressNotification. 9 | */ 10 | public static EmptyProgressNotification get() { 11 | if (instance == null) { 12 | instance = new EmptyProgressNotification(); 13 | } 14 | return instance; 15 | } 16 | 17 | protected EmptyProgressNotification() {} 18 | 19 | @Override 20 | public EmptyProgressNotification progress(float progress) { 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/Notification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | public interface Notification { 6 | 7 | Notification add(Player player); 8 | 9 | SendableNotification create(); 10 | 11 | void clear(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/NotificationFactory.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | 6 | public class NotificationFactory extends AbstractNotificationFactory { 7 | 8 | public NotificationFactory(InsightsPlugin plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public Notification bossBar(Messages.Message title) { 14 | return new BossBarNotification( 15 | plugin, 16 | createBossBar(title), 17 | title, 18 | settings.NOTIFICATION_BOSSBAR_DURATION_TICKS 19 | ); 20 | } 21 | 22 | @Override 23 | public Notification actionBar(Messages.Message content) { 24 | return new ActionBarNotification(plugin, content); 25 | } 26 | 27 | @Override 28 | public Notification empty() { 29 | return EmptyNotification.get(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/ProgressNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | public interface ProgressNotification extends Notification { 4 | 5 | ProgressNotification progress(float progress); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/ProgressNotificationFactory.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | 6 | public class ProgressNotificationFactory extends AbstractNotificationFactory { 7 | 8 | public ProgressNotificationFactory(InsightsPlugin plugin) { 9 | super(plugin); 10 | } 11 | 12 | @Override 13 | public ProgressNotification bossBar(Messages.Message title) { 14 | return new BossBarProgressNotification( 15 | plugin, 16 | createBossBar(title), 17 | title, 18 | settings.NOTIFICATION_BOSSBAR_DURATION_TICKS 19 | ); 20 | } 21 | 22 | @Override 23 | public ProgressNotification actionBar(Messages.Message content) { 24 | return new ActionBarProgressNotification( 25 | plugin, 26 | content, 27 | settings.NOTIFICATION_ACTIONBAR_SEGMENTS, 28 | settings.NOTIFICATION_ACTIONBAR_DONE_COLOR, 29 | settings.NOTIFICATION_ACTIONBAR_TOTAL_COLOR, 30 | settings.NOTIFICATION_ACTIONBAR_SEQUENCE, 31 | settings.NOTIFICATION_ACTIONBAR_SEPARATOR 32 | ); 33 | } 34 | 35 | @Override 36 | public ProgressNotification empty() { 37 | return EmptyProgressNotification.get(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/notifications/SendableNotification.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.notifications; 2 | 3 | import dev.frankheijden.insights.api.config.Messages; 4 | import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 5 | 6 | public abstract class SendableNotification { 7 | 8 | protected Messages.Message content; 9 | 10 | protected SendableNotification(Messages.Message content) { 11 | this.content = content; 12 | } 13 | 14 | public SendableNotification addTemplates(TagResolver resolver) { 15 | this.content.addTemplates(resolver); 16 | return this; 17 | } 18 | 19 | public SendableNotification addTemplates(TagResolver... resolvers) { 20 | this.content.addTemplates(resolvers); 21 | return this; 22 | } 23 | 24 | public abstract void send(); 25 | } 26 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/parser/PassiveYamlParser.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.parser; 2 | 3 | import dev.frankheijden.insights.api.config.ConfigError; 4 | import dev.frankheijden.insights.api.config.Monad; 5 | import org.bukkit.configuration.file.YamlConfiguration; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | 10 | public class PassiveYamlParser extends YamlParser { 11 | 12 | private final ConfigError.Builder errors; 13 | 14 | protected PassiveYamlParser(YamlConfiguration yaml, String name, ConfigError.Builder errors) { 15 | super(yaml, name, errors::append); 16 | this.errors = errors; 17 | } 18 | 19 | public static PassiveYamlParser load(File file) throws IOException { 20 | return load(file, null); 21 | } 22 | 23 | public static PassiveYamlParser load(File file, InputStream defaultSettings) throws IOException { 24 | return new PassiveYamlParser(loadYaml(file, defaultSettings), file.getName(), ConfigError.newBuilder()); 25 | } 26 | 27 | public Monad toMonad(T obj) { 28 | return new Monad<>(obj, errors.getErrors()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/parser/SensitiveYamlParser.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.parser; 2 | 3 | import org.bukkit.configuration.file.YamlConfiguration; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | public class SensitiveYamlParser extends YamlParser { 9 | 10 | protected SensitiveYamlParser(YamlConfiguration yaml, String name) { 11 | super(yaml, name, error -> { 12 | throw new YamlParseException(error.toString()); 13 | }); 14 | } 15 | 16 | public static SensitiveYamlParser load(File file) throws IOException { 17 | return load(file, null); 18 | } 19 | 20 | public static SensitiveYamlParser load(File file, InputStream defaultSettings) throws IOException { 21 | return new SensitiveYamlParser(loadYaml(file, defaultSettings), file.getName()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/config/parser/YamlParseException.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.config.parser; 2 | 3 | public class YamlParseException extends RuntimeException { 4 | 5 | public YamlParseException(String s) { 6 | super(s); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/events/EntityRemoveFromWorldEvent.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.events; 2 | 3 | import org.bukkit.entity.Entity; 4 | import org.bukkit.event.HandlerList; 5 | import org.bukkit.event.entity.EntityEvent; 6 | 7 | public class EntityRemoveFromWorldEvent extends EntityEvent { 8 | 9 | private static final HandlerList handlers = new HandlerList(); 10 | 11 | public EntityRemoveFromWorldEvent(Entity entity) { 12 | super(entity); 13 | } 14 | 15 | public HandlerList getHandlers() { 16 | return handlers; 17 | } 18 | 19 | public static HandlerList getHandlerList() { 20 | return handlers; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/exceptions/ChunkCuboidOutOfBoundsException.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.exceptions; 2 | 3 | import dev.frankheijden.insights.api.objects.chunk.ChunkCuboid; 4 | 5 | public class ChunkCuboidOutOfBoundsException extends RuntimeException { 6 | 7 | public ChunkCuboidOutOfBoundsException(ChunkCuboid cuboid, ChunkCuboid max) { 8 | super("Cuboid " + cuboid + " is out of bounds for cuboid " + max); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/exceptions/ChunkIOException.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.exceptions; 2 | 3 | import java.io.IOException; 4 | 5 | public class ChunkIOException extends RuntimeException { 6 | 7 | public ChunkIOException(IOException cause) { 8 | super(cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/listeners/manager/InsightsListenerManager.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.listeners.manager; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Map; 5 | 6 | public interface InsightsListenerManager { 7 | 8 | void register(); 9 | 10 | void unregister(); 11 | 12 | Map getAllowedDisableMethods(); 13 | 14 | Map getAllowedPriorityOverrideMethods(); 15 | } 16 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/metrics/IntegerMetric.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.metrics; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.atomic.LongAdder; 5 | 6 | public class IntegerMetric implements Callable { 7 | 8 | private final LongAdder adder = new LongAdder(); 9 | 10 | @Override 11 | public Integer call() { 12 | return Math.toIntExact(Math.min(adder.sumThenReset(), Integer.MAX_VALUE)); 13 | } 14 | 15 | public void increment() { 16 | adder.increment(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/metrics/MetricsManager.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.metrics; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import java.util.concurrent.atomic.LongAdder; 5 | import org.bstats.bukkit.Metrics; 6 | import org.bstats.charts.SingleLineChart; 7 | 8 | public class MetricsManager { 9 | 10 | private static final int BSTATS_METRICS_ID = 7272; 11 | 12 | private final IntegerMetric chunkScanMetric = new IntegerMetric(); 13 | private final IntegerMetric limitMetric = new IntegerMetric(); 14 | private final LongAdder totalBlocksScanned = new LongAdder(); 15 | 16 | /** 17 | * Constructs a new MetricsManager with some extra charts. 18 | */ 19 | public MetricsManager(InsightsPlugin plugin) { 20 | var metrics = new Metrics(plugin, BSTATS_METRICS_ID); 21 | metrics.addCustomChart(new SingleLineChart("chunks-scanned", chunkScanMetric)); 22 | metrics.addCustomChart(new SingleLineChart("limits", limitMetric)); 23 | } 24 | 25 | public IntegerMetric getChunkScanMetric() { 26 | return chunkScanMetric; 27 | } 28 | 29 | public IntegerMetric getLimitMetric() { 30 | return limitMetric; 31 | } 32 | 33 | public LongAdder getTotalBlocksScanned() { 34 | return totalBlocksScanned; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/objects/InsightsBase.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.objects; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | 5 | public abstract class InsightsBase { 6 | 7 | protected final InsightsPlugin plugin; 8 | 9 | protected InsightsBase(InsightsPlugin plugin) { 10 | this.plugin = plugin; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/objects/chunk/ChunkCuboid.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.objects.chunk; 2 | 3 | import org.bukkit.World; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | import java.util.UUID; 8 | 9 | public class ChunkCuboid { 10 | 11 | private static final Map maxCuboidCache = new HashMap<>(); 12 | 13 | private final ChunkVector min; 14 | private final ChunkVector max; 15 | 16 | public ChunkCuboid(ChunkVector min, ChunkVector max) { 17 | this.min = min; 18 | this.max = max; 19 | } 20 | 21 | public ChunkVector getMin() { 22 | return min; 23 | } 24 | 25 | public ChunkVector getMax() { 26 | return max; 27 | } 28 | 29 | public long getVolume() { 30 | return (max.getX() - min.getX() + 1L) * (max.getY() - min.getY() + 1L) * (max.getX() - min.getZ() + 1L); 31 | } 32 | 33 | /** 34 | * Determines whether the specified cuboid "fits" in this cuboid instance. 35 | */ 36 | public boolean contains(ChunkCuboid other) { 37 | return this.min.getX() <= other.min.getX() 38 | && this.min.getY() <= other.min.getY() 39 | && this.min.getZ() <= other.min.getZ() 40 | && this.max.getX() >= other.max.getX() 41 | && this.max.getY() >= other.max.getY() 42 | && this.max.getZ() >= other.max.getZ(); 43 | } 44 | 45 | /** 46 | * Determines the maximum ChunkCuboid of a given world, and caches the result. 47 | */ 48 | public static ChunkCuboid maxCuboid(World world) { 49 | return maxCuboidCache.computeIfAbsent(world.getUID(), k -> new ChunkCuboid( 50 | ChunkVector.minVector(world), 51 | ChunkVector.maxVector(world) 52 | )); 53 | } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (this == o) return true; 58 | if (o == null || getClass() != o.getClass()) return false; 59 | ChunkCuboid that = (ChunkCuboid) o; 60 | return min.equals(that.min) && max.equals(that.max); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return Objects.hash(min, max); 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return min + " -> " + max; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/objects/chunk/ChunkLocation.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.objects.chunk; 2 | 3 | import dev.frankheijden.insights.api.utils.ChunkUtils; 4 | import org.bukkit.Chunk; 5 | import org.bukkit.World; 6 | import java.util.Objects; 7 | 8 | public class ChunkLocation { 9 | 10 | private final World world; 11 | private final int x; 12 | private final int z; 13 | 14 | /** 15 | * Constructs a new ChunkLocation at given world, x and z coordinates of the chunk. 16 | */ 17 | public ChunkLocation(World world, int x, int z) { 18 | this.world = world; 19 | this.x = x; 20 | this.z = z; 21 | } 22 | 23 | public static ChunkLocation of(Chunk chunk) { 24 | return new ChunkLocation(chunk.getWorld(), chunk.getX(), chunk.getZ()); 25 | } 26 | 27 | public World getWorld() { 28 | return world; 29 | } 30 | 31 | public int getX() { 32 | return x; 33 | } 34 | 35 | public int getZ() { 36 | return z; 37 | } 38 | 39 | public long getKey() { 40 | return ChunkUtils.getKey(x, z); 41 | } 42 | 43 | public ChunkPart toPart() { 44 | return new ChunkPart(this); 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | if (this == o) return true; 50 | if (o == null || getClass() != o.getClass()) return false; 51 | ChunkLocation that = (ChunkLocation) o; 52 | return x == that.x && z == that.z && world.equals(that.world); 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(world, x, z); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return world.getName() + " @ " + x + ", " + z; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/objects/chunk/ChunkPart.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.objects.chunk; 2 | 3 | import java.util.Objects; 4 | 5 | public class ChunkPart { 6 | 7 | private final ChunkLocation chunkLocation; 8 | private final ChunkCuboid chunkCuboid; 9 | 10 | public ChunkPart(ChunkLocation chunkLocation) { 11 | this(chunkLocation, ChunkCuboid.maxCuboid(chunkLocation.getWorld())); 12 | } 13 | 14 | public ChunkPart(ChunkLocation chunkLocation, ChunkCuboid chunkCuboid) { 15 | this.chunkLocation = chunkLocation; 16 | this.chunkCuboid = chunkCuboid; 17 | } 18 | 19 | public ChunkLocation getChunkLocation() { 20 | return chunkLocation; 21 | } 22 | 23 | public ChunkCuboid getChunkCuboid() { 24 | return chunkCuboid; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | ChunkPart chunkPart = (ChunkPart) o; 32 | return chunkLocation.equals(chunkPart.chunkLocation) && chunkCuboid.equals(chunkPart.chunkCuboid); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(chunkLocation, chunkCuboid); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/objects/chunk/ChunkVector.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.objects.chunk; 2 | 3 | import dev.frankheijden.insights.api.objects.math.Vector3; 4 | import org.bukkit.World; 5 | 6 | public class ChunkVector extends Vector3 { 7 | 8 | /** 9 | * Constructs a new ChunkVector with given x, y, z coordinates in the chunk. 10 | */ 11 | public ChunkVector(int x, int y, int z) { 12 | super(x, y, z); 13 | } 14 | 15 | /** 16 | * Constructs a new ChunkVector from the given vector. 17 | */ 18 | public static ChunkVector from(Vector3 vector) { 19 | return new ChunkVector(vector.getX() & 15, vector.getY(), vector.getZ() & 15); 20 | } 21 | 22 | public static ChunkVector minVector(World world) { 23 | return new ChunkVector(0, world.getMinHeight(), 0); 24 | } 25 | 26 | public static ChunkVector maxVector(World world) { 27 | return new ChunkVector(15, world.getMaxHeight() - 1, 15); 28 | } 29 | 30 | /** 31 | * Creates a value-based minimum of the current vector with the one given. 32 | */ 33 | public ChunkVector min(ChunkVector other) { 34 | return new ChunkVector( 35 | Math.min(x, other.x), 36 | Math.min(y, other.y), 37 | Math.min(z, other.z) 38 | ); 39 | } 40 | 41 | /** 42 | * Creates a value-based maximum of the current vector with the one given. 43 | */ 44 | public ChunkVector max(ChunkVector other) { 45 | return new ChunkVector( 46 | Math.max(x, other.x), 47 | Math.max(y, other.y), 48 | Math.max(z, other.z) 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/objects/math/Cuboid.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.objects.math; 2 | 3 | import dev.frankheijden.insights.api.objects.chunk.ChunkCuboid; 4 | import dev.frankheijden.insights.api.objects.chunk.ChunkLocation; 5 | import dev.frankheijden.insights.api.objects.chunk.ChunkPart; 6 | import dev.frankheijden.insights.api.objects.chunk.ChunkVector; 7 | import org.bukkit.World; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Objects; 11 | 12 | public class Cuboid { 13 | 14 | private final World world; 15 | private final Vector3 min; 16 | private final Vector3 max; 17 | 18 | /** 19 | * Constructs a new cuboid in given world, with given minimum and maximum vectors. 20 | */ 21 | public Cuboid(World world, Vector3 min, Vector3 max) { 22 | this.world = world; 23 | this.min = min; 24 | this.max = max; 25 | } 26 | 27 | public World getWorld() { 28 | return world; 29 | } 30 | 31 | public Vector3 getMin() { 32 | return min; 33 | } 34 | 35 | public Vector3 getMax() { 36 | return max; 37 | } 38 | 39 | /** 40 | * Converts this cuboid into a List of ChunkParts. 41 | */ 42 | public List toChunkParts() { 43 | ChunkVector minV = ChunkVector.from(this.min); 44 | int minX = this.min.x >> 4; 45 | int minZ = this.min.z >> 4; 46 | 47 | ChunkVector maxV = ChunkVector.from(this.max); 48 | int maxX = this.max.x >> 4; 49 | int maxZ = this.max.z >> 4; 50 | 51 | List parts = new ArrayList<>(maxX - minX + 1 + maxZ - minZ + 1); 52 | for (int x = minX; x <= maxX; x++) { 53 | int xmin = (x == minX) ? minV.getX() : 0; 54 | int ymin = minV.getY(); 55 | int xmax = (x == maxX) ? maxV.getX() : 15; 56 | 57 | for (int z = minZ; z <= maxZ; z++) { 58 | int zmin = (z == minZ) ? minV.getZ() : 0; 59 | int ymax = maxV.getY(); 60 | int zmax = (z == maxZ) ? maxV.getZ() : 15; 61 | 62 | ChunkLocation loc = new ChunkLocation(world, x, z); 63 | ChunkVector vmin = new ChunkVector(xmin, ymin, zmin); 64 | ChunkVector vmax = new ChunkVector(xmax, ymax, zmax); 65 | parts.add(new ChunkPart(loc, new ChunkCuboid(vmin, vmax))); 66 | } 67 | } 68 | return parts; 69 | } 70 | 71 | @Override 72 | public boolean equals(Object o) { 73 | if (this == o) return true; 74 | if (o == null || getClass() != o.getClass()) return false; 75 | Cuboid cuboid = (Cuboid) o; 76 | return world.equals(cuboid.world) && min.equals(cuboid.min) && max.equals(cuboid.max); 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | return Objects.hash(world, min, max); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/objects/math/Vector3.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.objects.math; 2 | 3 | import java.util.Objects; 4 | 5 | public class Vector3 { 6 | 7 | protected final int x; 8 | protected final int y; 9 | protected final int z; 10 | 11 | /** 12 | * Constructs a new vector with given 3D coordinates. 13 | */ 14 | public Vector3(int x, int y, int z) { 15 | this.x = x; 16 | this.y = y; 17 | this.z = z; 18 | } 19 | 20 | public int getX() { 21 | return x; 22 | } 23 | 24 | public int getY() { 25 | return y; 26 | } 27 | 28 | public int getZ() { 29 | return z; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (o == null || getClass() != o.getClass()) return false; 36 | Vector3 vector3 = (Vector3) o; 37 | return x == vector3.x && y == vector3.y && z == vector3.z; 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return Objects.hash(x, y, z); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "(" + x + ", " + y + ", " + z + ")"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/reflection/RTileEntityTypes.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.reflection; 2 | 3 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 4 | import dev.frankheijden.insights.api.util.SetCollector; 5 | import dev.frankheijden.insights.nms.core.ReflectionUtils; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.Material; 8 | import java.lang.invoke.MethodHandle; 9 | import java.lang.invoke.MethodHandles; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.lang.reflect.Modifier; 13 | import java.util.Collections; 14 | import java.util.EnumMap; 15 | import java.util.EnumSet; 16 | import java.util.HashSet; 17 | import java.util.Map; 18 | import java.util.Set; 19 | 20 | public class RTileEntityTypes { 21 | private static final Set TILE_ENTITY_MATERIALS; 22 | private static final Set TILE_ENTITIES; 23 | 24 | static { 25 | try { 26 | var tileEntityTypesClazz = Class.forName( 27 | "net.minecraft.world.level.block.entity.TileEntityTypes" 28 | ); 29 | var craftBlockDataClazz = Class.forName( 30 | "org.bukkit.craftbukkit.block.data.CraftBlockData" 31 | ); 32 | var blockDataClazz = Class.forName( 33 | "net.minecraft.world.level.block.state.IBlockData" 34 | ); 35 | MethodHandle isValidMethodHandle; 36 | try { 37 | isValidMethodHandle = MethodHandles.lookup().unreflect(ReflectionUtils.findDeclaredMethod( 38 | tileEntityTypesClazz, 39 | new Class[]{ blockDataClazz }, 40 | boolean.class, 41 | "isValid" 42 | )); 43 | } catch (IllegalAccessException ex) { 44 | throw new RuntimeException(ex); 45 | } 46 | 47 | Map blockStateMap = new EnumMap<>(Material.class); 48 | for (Material m : Material.values()) { 49 | if (m.isBlock()) { 50 | blockStateMap.put(m, craftBlockDataClazz.getMethod("getState").invoke(Bukkit.createBlockData(m))); 51 | } 52 | } 53 | 54 | Set materials = new HashSet<>(); 55 | try { 56 | for (Field field : tileEntityTypesClazz.getFields()) { 57 | if (!Modifier.isStatic(field.getModifiers())) continue; 58 | if (!field.getType().equals(tileEntityTypesClazz)) continue; 59 | 60 | Object tileEntityTypes = field.get(null); 61 | for (Map.Entry entry : blockStateMap.entrySet()) { 62 | if ((boolean) isValidMethodHandle.invoke(tileEntityTypes, entry.getValue())) { 63 | materials.add(entry.getKey()); 64 | } 65 | } 66 | } 67 | } catch (Throwable th) { 68 | th.printStackTrace(); 69 | } 70 | 71 | materials.removeIf(Material::isAir); 72 | TILE_ENTITY_MATERIALS = Collections.unmodifiableSet(EnumSet.copyOf(materials)); 73 | TILE_ENTITIES = TILE_ENTITY_MATERIALS.stream() 74 | .map(ScanObject::of) 75 | .collect(SetCollector.unmodifiableSet()); 76 | } catch (ClassNotFoundException 77 | | NoSuchMethodException 78 | | InvocationTargetException 79 | | IllegalAccessException ex 80 | ) { 81 | throw new RuntimeException(ex); 82 | } 83 | } 84 | 85 | private RTileEntityTypes() {} 86 | 87 | public static boolean isTileEntity(Material m) { 88 | return TILE_ENTITY_MATERIALS.contains(m); 89 | } 90 | 91 | public static Set getTileEntityMaterials() { 92 | return TILE_ENTITY_MATERIALS; 93 | } 94 | 95 | public static Set getTileEntities() { 96 | return TILE_ENTITIES; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/tasks/InsightsAsyncTask.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.tasks; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | 5 | public abstract class InsightsAsyncTask extends InsightsTask { 6 | 7 | protected InsightsAsyncTask(InsightsPlugin plugin) { 8 | super(plugin); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/tasks/InsightsTask.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.tasks; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.objects.InsightsBase; 5 | 6 | public abstract class InsightsTask extends InsightsBase implements Runnable { 7 | 8 | protected InsightsTask(InsightsPlugin plugin) { 9 | super(plugin); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/util/LazyChunkPartRadiusIterator.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.util; 2 | 3 | import dev.frankheijden.insights.api.objects.chunk.ChunkLocation; 4 | import dev.frankheijden.insights.api.objects.chunk.ChunkPart; 5 | import java.util.EnumMap; 6 | import java.util.Iterator; 7 | import java.util.Map; 8 | import org.bukkit.World; 9 | 10 | public class LazyChunkPartRadiusIterator implements Iterator, Iterable { 11 | 12 | private static final EnumMap nextDirections = new EnumMap<>(Map.of( 13 | Direction.NORTH, Direction.EAST, 14 | Direction.EAST, Direction.SOUTH, 15 | Direction.SOUTH, Direction.WEST, 16 | Direction.WEST, Direction.NORTH 17 | )); 18 | 19 | private final World world; 20 | private final int chunkCount; 21 | private int currentChunkCount; 22 | private int currentChunkX; 23 | private int currentChunkZ; 24 | private Direction currentDirection; 25 | private int currentEdge; 26 | private int currentEdgeStep; 27 | private int currentEdgeStepMax; 28 | 29 | /** 30 | * Constructs a new LazyChunkPartRadiusIterator. 31 | */ 32 | public LazyChunkPartRadiusIterator(World world, int chunkX, int chunkZ, int radius) { 33 | this.world = world; 34 | 35 | int edge = (2 * radius) + 1; 36 | this.chunkCount = edge * edge; 37 | 38 | this.currentChunkCount = 0; 39 | this.currentChunkX = chunkX; 40 | this.currentChunkZ = chunkZ; 41 | this.currentDirection = Direction.NORTH; 42 | this.currentEdge = 0; 43 | this.currentEdgeStep = 0; 44 | this.currentEdgeStepMax = 1; 45 | } 46 | 47 | @Override 48 | public boolean hasNext() { 49 | return currentChunkCount < chunkCount; 50 | } 51 | 52 | @Override 53 | public ChunkPart next() { 54 | @SuppressWarnings("VariableDeclarationUsageDistance") 55 | ChunkPart part = new ChunkLocation(world, currentChunkX, currentChunkZ).toPart(); 56 | 57 | currentChunkCount++; 58 | currentChunkX += switch (currentDirection) { 59 | case EAST -> 1; 60 | case WEST -> -1; 61 | default -> 0; 62 | }; 63 | currentChunkZ += switch (currentDirection) { 64 | case NORTH -> 1; 65 | case SOUTH -> -1; 66 | default -> 0; 67 | }; 68 | 69 | if (++currentEdgeStep >= currentEdgeStepMax) { 70 | currentEdgeStep = 0; 71 | currentDirection = nextDirections.get(currentDirection); 72 | if (++currentEdge == 2) { 73 | currentEdge = 0; 74 | currentEdgeStepMax++; 75 | } 76 | } 77 | 78 | return part; 79 | } 80 | 81 | @Override 82 | public Iterator iterator() { 83 | return this; 84 | } 85 | 86 | public int getChunkCount() { 87 | return chunkCount; 88 | } 89 | 90 | enum Direction { 91 | NORTH, 92 | EAST, 93 | SOUTH, 94 | WEST, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/util/MaterialTags.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.util; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import org.bukkit.Material; 5 | import org.bukkit.NamespacedKey; 6 | import org.bukkit.Tag; 7 | import java.util.Arrays; 8 | import java.util.EnumSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | import java.util.function.Predicate; 12 | import java.util.stream.Collectors; 13 | 14 | public class MaterialTags implements Tag { 15 | 16 | private static final List MATERIALS = Arrays.asList(Material.values()); 17 | public static final MaterialTags BUCKETS = new MaterialTags(m -> m.name().endsWith("_BUCKET"), "buckets"); 18 | public static final MaterialTags NEEDS_GROUND = new MaterialTags( 19 | EnumSet.of( 20 | Material.ACTIVATOR_RAIL, 21 | Material.COMPARATOR, 22 | Material.DETECTOR_RAIL, 23 | Material.POWERED_RAIL, 24 | Material.RAIL, 25 | Material.REDSTONE_WIRE, 26 | Material.REPEATER 27 | ), 28 | "needs_ground" 29 | ); 30 | 31 | private final Set materials; 32 | private final String key; 33 | 34 | private MaterialTags(Predicate materialPredicate, String key) { 35 | this(MATERIALS.stream().filter(materialPredicate).collect(Collectors.toSet()), key); 36 | } 37 | 38 | private MaterialTags(Set materials, String key) { 39 | this.materials = materials; 40 | this.key = key; 41 | } 42 | 43 | @Override 44 | public boolean isTagged(Material material) { 45 | return materials.contains(material); 46 | } 47 | 48 | @Override 49 | public Set getValues() { 50 | return materials; 51 | } 52 | 53 | @Override 54 | public NamespacedKey getKey() { 55 | return new NamespacedKey(InsightsPlugin.getInstance(), key); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/util/SetCollector.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | import java.util.function.BiConsumer; 8 | import java.util.function.BinaryOperator; 9 | import java.util.function.Function; 10 | import java.util.function.Supplier; 11 | import java.util.stream.Collector; 12 | 13 | public interface SetCollector extends Collector, Set> { 14 | 15 | Set defaultCharacteristics = new HashSet<>(Arrays.asList( 16 | Characteristics.UNORDERED, 17 | Characteristics.IDENTITY_FINISH 18 | )); 19 | 20 | /** 21 | * Creates a new SetCollector which collects items to an unmodifiable HashSet. 22 | */ 23 | static SetCollector unmodifiableSet() { 24 | return new SetCollector() { 25 | @Override 26 | public Set supplySet() { 27 | return new HashSet<>(); 28 | } 29 | 30 | @Override 31 | public Function, Set> finisher() { 32 | return Collections::unmodifiableSet; 33 | } 34 | 35 | @Override 36 | public Set characteristics() { 37 | return Collections.singleton(Characteristics.UNORDERED); 38 | } 39 | }; 40 | } 41 | 42 | Set supplySet(); 43 | 44 | @Override 45 | default Supplier> supplier() { 46 | return this::supplySet; 47 | } 48 | 49 | @Override 50 | default BiConsumer, T> accumulator() { 51 | return Set::add; 52 | } 53 | 54 | @Override 55 | default BinaryOperator> combiner() { 56 | return (s1, s2) -> { 57 | s1.addAll(s2); 58 | return s1; 59 | }; 60 | } 61 | 62 | @Override 63 | default Function, Set> finisher() { 64 | return Function.identity(); 65 | } 66 | 67 | @Override 68 | default Set characteristics() { 69 | return defaultCharacteristics; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/util/TriConsumer.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.util; 2 | 3 | @FunctionalInterface 4 | public interface TriConsumer { 5 | 6 | void accept(T t, U u, V v); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/BlockUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.block.BlockFace; 5 | import org.bukkit.block.data.type.Bed; 6 | import java.util.Optional; 7 | 8 | public class BlockUtils { 9 | 10 | private BlockUtils() {} 11 | 12 | /** 13 | * Checks whether two blocks are within the same chunk. 14 | */ 15 | public static boolean isSameChunk(Block x, Block y) { 16 | return ((x.getX() >> 4) == (y.getX() >> 4)) && (x.getZ() >> 4) == (y.getZ() >> 4); 17 | } 18 | 19 | /** 20 | * Checks whether two x,z block location pairs are within the same chunk. 21 | */ 22 | public static boolean isSameChunk(int x1, int z1, int x2, int z2) { 23 | return ((x1 >> 4) == (x2 >> 4)) && (z1 >> 4) == (z2 >> 4); 24 | } 25 | 26 | /** 27 | * Attempts to retrieve the other half of a block. 28 | */ 29 | public static Optional getOtherHalf(Block block) { 30 | var data = block.getBlockData(); 31 | if (data instanceof Bed) { 32 | var bed = (Bed) data; 33 | BlockFace facing = bed.getFacing(); 34 | return Optional.of(block.getRelative(bed.getPart() == Bed.Part.HEAD ? facing.getOppositeFace() : facing)); 35 | } 36 | return Optional.empty(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/ChunkUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import org.bukkit.Chunk; 4 | import org.bukkit.ChunkSnapshot; 5 | import org.bukkit.Location; 6 | 7 | public class ChunkUtils { 8 | 9 | private ChunkUtils() {} 10 | 11 | public static int getX(long key) { 12 | return (int) (key & 4294967295L); 13 | } 14 | 15 | public static int getZ(long key) { 16 | return (int) (key >>> 32 & 4294967295L); 17 | } 18 | 19 | public static long getKey(Chunk chunk) { 20 | return getKey(chunk.getX(), chunk.getZ()); 21 | } 22 | 23 | public static long getKey(Location location) { 24 | return getKey(location.getBlockX() >> 4, location.getBlockZ() >> 4); 25 | } 26 | 27 | public static long getKey(ChunkSnapshot snapshot) { 28 | return getKey(snapshot.getX(), snapshot.getZ()); 29 | } 30 | 31 | public static long getKey(int x, int z) { 32 | return ((long) x & 4294967295L) | ((long) z & 4294967295L) << 32; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/ColorUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import org.bukkit.ChatColor; 4 | 5 | public class ColorUtils { 6 | 7 | private ColorUtils() {} 8 | 9 | /** 10 | * Colorizes given strings. 11 | */ 12 | public static String[] colorize(String... strings) { 13 | String[] colored = new String[strings.length]; 14 | for (int i = 0; i < strings.length; i++) { 15 | colored[i] = colorize(strings[i]); 16 | } 17 | return colored; 18 | } 19 | 20 | public static String colorize(String color) { 21 | return ChatColor.translateAlternateColorCodes('&', color); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 4 | import dev.frankheijden.insights.api.util.SetCollector; 5 | import org.bukkit.Material; 6 | import org.bukkit.entity.EntityType; 7 | import java.util.Arrays; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class Constants { 12 | 13 | private Constants() {} 14 | 15 | public static final Set BLOCKS = Arrays.stream(Material.values()) 16 | .filter(Material::isBlock) 17 | .collect((SetCollector) HashSet::new); 18 | public static final Set SCAN_BLOCKS = BLOCKS.stream() 19 | .map(ScanObject::of) 20 | .collect(SetCollector.unmodifiableSet()); 21 | public static final Set ENTITIES = Arrays.stream(EntityType.values()) 22 | .collect((SetCollector) HashSet::new); 23 | public static final Set SCAN_ENTITIES = ENTITIES.stream() 24 | .map(ScanObject::of) 25 | .collect(SetCollector.unmodifiableSet()); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/EnumUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import java.util.Collections; 4 | import java.util.EnumSet; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Locale; 8 | import java.util.Set; 9 | import java.util.regex.Pattern; 10 | import java.util.stream.Collectors; 11 | 12 | public class EnumUtils { 13 | 14 | private EnumUtils() {} 15 | 16 | /** 17 | * Retrieves the values of an enumerator. 18 | * @return An unmodifiable set of values of given enum values. 19 | */ 20 | public static > Set getValues(Class enumClass) { 21 | return getValues(enumClass.getEnumConstants()); 22 | } 23 | 24 | /** 25 | * Retrieves the values of an enumerator. 26 | * @return An unmodifiable set of values of given enum values. 27 | */ 28 | public static > Set getValues(Enum[] enums) { 29 | Set values = new HashSet<>(); 30 | for (Enum e : enums) { 31 | values.add(e.name()); 32 | } 33 | return Collections.unmodifiableSet(values); 34 | } 35 | 36 | /** 37 | * Retrieves a list of enums by regex for the given enum class. 38 | */ 39 | public static > List getEnumsByRegex(String regex, Class enumClass) { 40 | Pattern pattern = Pattern.compile(regex); 41 | return EnumUtils.getValues(enumClass).stream() 42 | .filter(str -> pattern.matcher(str).matches()) 43 | .map(str -> Enum.valueOf(enumClass, str)) 44 | .collect(Collectors.toList()); 45 | } 46 | 47 | public static > String pretty(Enum e) { 48 | return StringUtils.capitalizeSentence(e.name().replace('_', ' ').toLowerCase(Locale.ENGLISH)); 49 | } 50 | 51 | /** 52 | * Returns the difference of two sets. This method does not assume anything about mutability of the two sets. 53 | */ 54 | public static > Set difference(Set left, Set right) { 55 | return EnumSet.copyOf(SetUtils.difference(left, right)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/IOUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.URL; 6 | import java.net.URLConnection; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.util.Collection; 10 | 11 | public class IOUtils { 12 | 13 | private IOUtils() {} 14 | 15 | /** 16 | * Copies the contents of a folder in the jar to a target folder. 17 | */ 18 | public static void copyResources( 19 | Path target, 20 | ClassLoader loader, 21 | Collection collection 22 | ) { 23 | try { 24 | for (String fileName : collection) { 25 | try (InputStream in = getResource(fileName, loader)) { 26 | if (in == null) continue; 27 | save(in, target.resolve(fileName)); 28 | } 29 | } 30 | } catch (IOException ex) { 31 | ex.printStackTrace(); 32 | } 33 | } 34 | 35 | /** 36 | * Retrieves a resource from the ClassLoader object. 37 | */ 38 | public static InputStream getResource(String path, ClassLoader loader) { 39 | try { 40 | URL url = loader.getResource(path); 41 | if (url == null) return null; 42 | 43 | URLConnection connection = url.openConnection(); 44 | connection.setUseCaches(false); 45 | return connection.getInputStream(); 46 | } catch (IOException ex) { 47 | return null; 48 | } 49 | } 50 | 51 | /** 52 | * Saves an InputStream to a file. Closes the stream after use. 53 | */ 54 | public static void save(InputStream in, Path target) throws IOException { 55 | byte[] buffer = new byte[in.available()]; 56 | in.read(buffer); 57 | in.close(); 58 | 59 | Files.write(target, buffer); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/LocationUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Location; 5 | 6 | public class LocationUtils { 7 | 8 | private LocationUtils() {} 9 | 10 | /** 11 | * Serializes a location into a String. 12 | */ 13 | public static String getKey(Location loc) { 14 | return loc.getWorld().getName() + '|' + loc.getBlockX() + '|' + loc.getBlockY() + '|' + loc.getBlockZ(); 15 | } 16 | 17 | /** 18 | * Parses a location key to a Location object. 19 | */ 20 | public static Location getKey(String key) { 21 | String[] split = key.split("\\|"); 22 | return new Location( 23 | Bukkit.getWorld(split[0]), 24 | Integer.parseInt(split[1]), 25 | Integer.parseInt(split[2]), 26 | Integer.parseInt(split[3]) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/MapUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.function.BinaryOperator; 6 | 7 | public class MapUtils { 8 | 9 | private MapUtils() {} 10 | 11 | /** 12 | * Converts a sequence of key-value pairs into a map. 13 | */ 14 | @SafeVarargs 15 | public static Map toMap(T... objects) { 16 | if (objects.length % 2 != 0) throw new IllegalArgumentException("Must be a multiple of two"); 17 | Map map = new HashMap<>(objects.length >> 1); 18 | for (int i = 0; i < objects.length; i += 2) { 19 | map.put(objects[i], objects[i + 1]); 20 | } 21 | return map; 22 | } 23 | 24 | /** 25 | * Merges the left map into the right map (a into b). 26 | */ 27 | public static void mergeRight(Map a, Map b, BinaryOperator combiner) { 28 | for (Map.Entry entry : a.entrySet()) { 29 | b.merge(entry.getKey(), entry.getValue(), combiner); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/SetUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.function.Predicate; 6 | 7 | public class SetUtils { 8 | 9 | public SetUtils() {} 10 | 11 | /** 12 | * Returns the intersection of two sets. 13 | */ 14 | public static Set intersect(Set a, Set b) { 15 | Set set = new HashSet<>(a); 16 | set.retainAll(b); 17 | return set; 18 | } 19 | 20 | /** 21 | * Returns the difference of two sets. This method does not assume anything about mutability of the two sets. 22 | */ 23 | public static Set difference(Set a, Set b) { 24 | Set set = new HashSet<>(a); 25 | set.removeAll(b); 26 | return set; 27 | } 28 | 29 | /** 30 | * Finds the first item in a given set that tests positive to the predicate. 31 | */ 32 | public static T findFirst(Set set, Predicate predicate) { 33 | for (T obj : set) { 34 | if (predicate.test(obj)) { 35 | return obj; 36 | } 37 | } 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import java.text.NumberFormat; 4 | import java.time.Duration; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.List; 8 | import java.util.Locale; 9 | import java.util.Map; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | public class StringUtils { 14 | 15 | private static final Pattern placeholderPattern = Pattern.compile("%[0-9a-zA-Z]+%"); 16 | 17 | private StringUtils() {} 18 | 19 | /** 20 | * Efficient method for small placeholder replacements. 21 | */ 22 | public static String replaceSmall(String str, String... replacements) { 23 | if (replacements.length % 2 != 0) throw new IllegalArgumentException("Must be a multiple of two"); 24 | for (int i = 0; i < replacements.length; i += 2) { 25 | str = str.replace('%' + replacements[i] + '%', replacements[i + 1]); 26 | } 27 | return str; 28 | } 29 | 30 | public static String replace(String str, String... replacements) { 31 | return replacements.length < 10 ? replaceSmall(str, replacements) : replace(str, MapUtils.toMap(replacements)); 32 | } 33 | 34 | /** 35 | * Efficient method for large placeholder replacements. 36 | */ 37 | public static String replace(String str, Map replacements) { 38 | StringBuffer sb = new StringBuffer(str.length()); 39 | Matcher matcher = placeholderPattern.matcher(str); 40 | while (matcher.find()) { 41 | String group = matcher.group(); 42 | matcher.appendReplacement(sb, replacements.getOrDefault(group.substring(1, group.length() - 1), group)); 43 | } 44 | return sb.toString(); 45 | } 46 | 47 | public static String pretty(long n) { 48 | return NumberFormat.getIntegerInstance().format(n); 49 | } 50 | 51 | /** 52 | * Pretty Duration string. 53 | * Adapted from https://stackoverflow.com/a/40487511 54 | */ 55 | public static String pretty(Duration duration) { 56 | return duration.toString() 57 | .substring(2) 58 | .replaceAll("(\\d[HMS])(?!$)", "$1 ") 59 | .toLowerCase(); 60 | } 61 | 62 | public static String prettyOneDecimal(double d) { 63 | return String.format("%,.1f", d); 64 | } 65 | 66 | /** 67 | * Finds strings in the collection that starts with the given input. 68 | * Note: collection must only contain LOWERCASE values. 69 | */ 70 | public static List findThatStartsWith(Collection collection, String input) { 71 | List strings = new ArrayList<>(); 72 | input = input.toLowerCase(Locale.ENGLISH); 73 | for (String str : collection) { 74 | if (str.startsWith(input)) { 75 | strings.add(str); 76 | } 77 | } 78 | return strings; 79 | } 80 | 81 | /** 82 | * Capitalizes a sentence (each word first letter uppercase). 83 | */ 84 | public static String capitalizeSentence(String str) { 85 | StringBuilder sb = new StringBuilder(str.length()); 86 | for (String s : str.split(" ")) { 87 | sb.append(' ').append(capitalize(s)); 88 | } 89 | return sb.substring(1); 90 | } 91 | 92 | /** 93 | * Capitalizes given string, which must be in LOWERCASE. 94 | */ 95 | public static String capitalize(String str) { 96 | return str.substring(0, 1).toUpperCase(Locale.ENGLISH) + str.substring(1); 97 | } 98 | 99 | public static String join(String[] strings, String delimiter, int fromIndex) { 100 | return join(strings, delimiter, fromIndex, strings.length); 101 | } 102 | 103 | /** 104 | * Joins the given string with a delimiter, starting at fromIndex (including) until toIndex (excluding). 105 | */ 106 | public static String join(String[] strings, String delimiter, int fromIndex, int toIndex) { 107 | StringBuilder sb = new StringBuilder(); 108 | for (int i = fromIndex; i < toIndex; i++) { 109 | sb.append(delimiter).append(strings[i]); 110 | } 111 | return sb.length() == 0 ? "" : sb.substring(delimiter.length()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/VersionUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class VersionUtils { 6 | 7 | private static final Pattern integerPattern = Pattern.compile("[^0-9]"); 8 | 9 | private VersionUtils() {} 10 | 11 | /** 12 | * Compares two versions in X.X.X format. 13 | * Returns true if version is newer than the old one. 14 | * @param oldVersion The old version. 15 | * @param newVersion The new version. 16 | * @return true iff new version is newer than old version. 17 | */ 18 | public static boolean isNewVersion(String oldVersion, String newVersion) { 19 | if (oldVersion == null || newVersion == null) return false; 20 | String[] oldVersionSplit = oldVersion.split("-")[0].split("\\."); 21 | String[] newVersionSplit = newVersion.split("-")[0].split("\\."); 22 | 23 | int i = 0; 24 | while (i < oldVersionSplit.length && i < newVersionSplit.length) { 25 | int o = extractInteger(oldVersionSplit[i]); 26 | int n = extractInteger(newVersionSplit[i]); 27 | if (i != oldVersionSplit.length - 1 && i != newVersionSplit.length - 1) { 28 | if (n < o) return false; 29 | } 30 | if (n > o) return true; 31 | i++; 32 | } 33 | return false; 34 | } 35 | 36 | private static int extractInteger(String str) { 37 | return Integer.parseInt(integerPattern.matcher(str).replaceAll("")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Insights-API/src/main/java/dev/frankheijden/insights/api/utils/YamlUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.utils; 2 | 3 | import org.bukkit.configuration.ConfigurationSection; 4 | 5 | public class YamlUtils { 6 | 7 | private YamlUtils() {} 8 | 9 | /** 10 | * Appends any keys not present in the current configuration. 11 | */ 12 | public static void update(ConfigurationSection conf, ConfigurationSection def) { 13 | for (String key : def.getKeys(false)) { 14 | Object confValue = conf.get(key); 15 | Object defValue = def.get(key); 16 | if (confValue == null) { 17 | conf.set(key, defValue); 18 | } else if (defValue instanceof ConfigurationSection) { 19 | if (confValue instanceof ConfigurationSection) { 20 | update((ConfigurationSection) confValue, (ConfigurationSection) defValue); 21 | } else { 22 | conf.set(key, defValue); 23 | } 24 | } 25 | } 26 | } 27 | 28 | /** 29 | * Removes any unused keys (ie keys not present in the default configuration). 30 | */ 31 | public static void removeUnusedKeys(ConfigurationSection conf, ConfigurationSection def) { 32 | for (String key : conf.getKeys(false)) { 33 | Object confValue = conf.get(key); 34 | Object defValue = def.get(key); 35 | if (defValue == null) { 36 | conf.set(key, null); 37 | } else if (confValue instanceof ConfigurationSection) { 38 | if (defValue instanceof ConfigurationSection) { 39 | removeUnusedKeys((ConfigurationSection) confValue, (ConfigurationSection) defValue); 40 | } else { 41 | conf.set(key, null); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Insights-API/src/test/java/dev/frankheijden/insights/api/util/LazyChunkPartRadiusIteratorTest.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.api.util; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.fail; 5 | 6 | import dev.frankheijden.insights.api.objects.chunk.ChunkLocation; 7 | import dev.frankheijden.insights.api.objects.chunk.ChunkPart; 8 | import org.bukkit.World; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.Arguments; 11 | import org.junit.jupiter.params.provider.MethodSource; 12 | import org.mockito.Mockito; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Random; 16 | import java.util.stream.IntStream; 17 | import java.util.stream.Stream; 18 | 19 | class LazyChunkPartRadiusIteratorTest { 20 | 21 | private static final Random random = new Random(); 22 | 23 | @ParameterizedTest(name = "Chunk[{0}, {1}], Radius = {2}") 24 | @MethodSource("radiusGenerator") 25 | void determineChunkParts(int chunkX, int chunkZ, int radius) { 26 | World world = Mockito.mock(World.class); 27 | 28 | int edge = (2 * radius) + 1; 29 | int chunkCount = edge * edge; 30 | List expectedChunkParts = new ArrayList<>(chunkCount); 31 | for (int x = chunkX - radius; x <= chunkX + radius; x++) { 32 | for (int z = chunkZ - radius; z <= chunkZ + radius; z++) { 33 | expectedChunkParts.add(new ChunkLocation(world, x, z).toPart()); 34 | } 35 | } 36 | 37 | List actualChunkParts = new ArrayList<>(chunkCount); 38 | LazyChunkPartRadiusIterator it = new LazyChunkPartRadiusIterator(world, chunkX, chunkZ, radius); 39 | while (it.hasNext()) { 40 | actualChunkParts.add(it.next()); 41 | 42 | if (actualChunkParts.size() > expectedChunkParts.size()) { 43 | fail("Expected ChunkPart count exceeded of " + expectedChunkParts.size()); 44 | } 45 | } 46 | 47 | assertThat(actualChunkParts).containsExactlyInAnyOrderElementsOf(expectedChunkParts); 48 | } 49 | 50 | private static Stream radiusGenerator() { 51 | return Stream.concat( 52 | Stream.of( 53 | Arguments.of(0, 0, 10) 54 | ), 55 | IntStream.rangeClosed(0, 20) 56 | .mapToObj(r -> Arguments.of(random.nextInt(10000), random.nextInt(10000), r)) 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Insights-NMS/Core/build.gradle.kts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InsightsPlugin/Insights/3dfa42079b7c3fe76a88b066a6a01d951845f725/Insights-NMS/Core/build.gradle.kts -------------------------------------------------------------------------------- /Insights-NMS/Core/src/main/java/dev/frankheijden/insights/nms/core/ChunkEntity.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.nms.core; 2 | 3 | import org.bukkit.entity.EntityType; 4 | 5 | public record ChunkEntity(EntityType type, int x, int y, int z) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Insights-NMS/Core/src/main/java/dev/frankheijden/insights/nms/core/ChunkReflectionException.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.nms.core; 2 | 3 | public class ChunkReflectionException extends RuntimeException { 4 | 5 | public ChunkReflectionException(Throwable cause) { 6 | super(cause); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Insights-NMS/Core/src/main/java/dev/frankheijden/insights/nms/core/ChunkSection.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.nms.core; 2 | 3 | import org.bukkit.Material; 4 | import java.util.function.BiConsumer; 5 | 6 | public interface ChunkSection { 7 | 8 | int index(); 9 | 10 | boolean isNull(); 11 | 12 | Material blockAt(int x, int y, int z); 13 | 14 | void countBlocks(BiConsumer consumer); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Insights-NMS/Core/src/main/java/dev/frankheijden/insights/nms/core/InsightsNMS.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.nms.core; 2 | 3 | import org.bukkit.Chunk; 4 | import org.bukkit.World; 5 | import java.io.IOException; 6 | import java.util.function.Consumer; 7 | import java.util.logging.Level; 8 | import java.util.logging.Logger; 9 | 10 | public abstract class InsightsNMS { 11 | 12 | protected final String CHUNK_ERROR = "Recoverable errors when loading section [%d, %d, %d]: %s"; 13 | protected static Logger logger = Logger.getLogger("Insights"); 14 | 15 | /** 16 | * Gets an InsightsNMS instance for given version. 17 | * TODO: can be yeeted later or reused if we decide to do backwards compatibility again 18 | */ 19 | @SuppressWarnings("unchecked") 20 | public static T get() { 21 | try { 22 | Class clazz = Class.forName("dev.frankheijden.insights.nms.current.InsightsNMSImpl"); 23 | return (T) clazz.getDeclaredConstructor().newInstance(); 24 | } catch (ReflectiveOperationException e) { 25 | logger.log(Level.SEVERE, "Unable to get InsightsNMSImpl", e); 26 | throw new RuntimeException(e); 27 | } 28 | } 29 | 30 | public abstract void getLoadedChunkSections(Chunk chunk, Consumer sectionConsumer); 31 | 32 | public abstract void getUnloadedChunkSections( 33 | World world, 34 | int chunkX, 35 | int chunkZ, 36 | Consumer sectionConsumer 37 | ); 38 | 39 | public abstract void getLoadedChunkEntities(Chunk chunk, Consumer entityConsumer); 40 | 41 | public abstract void getUnloadedChunkEntities( 42 | World world, 43 | int chunkX, 44 | int chunkZ, 45 | Consumer entityConsumer 46 | ) throws IOException; 47 | } 48 | -------------------------------------------------------------------------------- /Insights-NMS/Core/src/main/java/dev/frankheijden/insights/nms/core/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.nms.core; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public class ReflectionUtils { 11 | 12 | private ReflectionUtils() {} 13 | 14 | /** 15 | * Finds a declared field in given class. 16 | */ 17 | public static Field findDeclaredField(Class clazz, Class type, String name) { 18 | for (Field field : clazz.getDeclaredFields()) { 19 | if (field.getType().equals(type)) { 20 | field.setAccessible(true); 21 | return field; 22 | } 23 | } 24 | 25 | throw new IllegalStateException("Can't find field " + clazz.getName() + "#" + name); 26 | } 27 | 28 | /** 29 | * Finds a declared method in given class. 30 | */ 31 | public static Method findDeclaredMethod( 32 | Class clazz, 33 | Class[] paramTypes, 34 | Class returnType, 35 | String name 36 | ) { 37 | for (Method method : clazz.getDeclaredMethods()) { 38 | if (!method.getReturnType().equals(returnType)) continue; 39 | if (!Arrays.equals(paramTypes, method.getParameterTypes())) continue; 40 | 41 | method.setAccessible(true); 42 | return method; 43 | } 44 | 45 | throw new IllegalStateException("Can't find method " + clazz.getName() + "." + name + ""); 46 | } 47 | 48 | /** 49 | * Retrieves all methods with given annotation. 50 | */ 51 | public static List getAnnotatedMethods(Class clazz, Class annotationClass) { 52 | Method[] declaredMethods = clazz.getDeclaredMethods(); 53 | List methods = new ArrayList<>(declaredMethods.length); 54 | for (Method method : declaredMethods) { 55 | if (method.isAnnotationPresent(annotationClass)) { 56 | methods.add(method); 57 | } 58 | } 59 | return methods; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Insights-NMS/Current/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | paperweight.paperDevBundle("1.21.3-R0.1-SNAPSHOT") 3 | } 4 | -------------------------------------------------------------------------------- /Insights/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | import net.minecrell.pluginyml.bukkit.BukkitPluginDescription 3 | 4 | plugins { 5 | alias(libs.plugins.pluginYml) 6 | } 7 | 8 | val dependencyDir = "$group.dependencies" 9 | 10 | repositories { 11 | maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") 12 | } 13 | 14 | dependencies { 15 | compileOnly(libs.brigadier) 16 | compileOnly(libs.placeholderapi) 17 | implementation(libs.commodore) 18 | implementation(libs.cloudPaper) 19 | implementation(libs.cloudAnnotations) 20 | implementation(libs.semver) 21 | compileOnly(project(":Insights-API")) 22 | } 23 | 24 | tasks.withType { 25 | exclude("com/mojang/**") 26 | relocate("com.github.zafarkhaja.semver", "$dependencyDir.semver") 27 | relocate("org.incendo.cloud", "$dependencyDir.cloud") 28 | relocate("io.leangen.geantyref", "$dependencyDir.typetoken") 29 | relocate("me.lucko.commodore", "$dependencyDir.commodore") 30 | } 31 | 32 | bukkit { 33 | main = "dev.frankheijden.insights.Insights" 34 | description = "Insights about your server and regional block limits" 35 | apiVersion = "1.21" 36 | website = "https://github.com/InsightsPlugin/Insights" 37 | softDepend = listOf("PlaceholderAPI") 38 | authors = listOf("FrankHeijden") 39 | permissions { 40 | register("insights.info") { 41 | description = "Allows you to see information about insights" 42 | default = BukkitPluginDescription.Permission.Default.TRUE 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/CommandCancelScan.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.commands.InsightsCommand; 5 | import dev.frankheijden.insights.api.config.Messages; 6 | import dev.frankheijden.insights.api.tasks.ScanTask; 7 | import org.bukkit.entity.Player; 8 | import org.incendo.cloud.annotations.Command; 9 | import org.incendo.cloud.annotations.Permission; 10 | 11 | public class CommandCancelScan extends InsightsCommand { 12 | 13 | public CommandCancelScan(InsightsPlugin plugin) { 14 | super(plugin); 15 | } 16 | 17 | @Command("cancelscan") 18 | @Permission("insights.cancelscan") 19 | private void handleCancelScan(Player player) { 20 | if (ScanTask.cancelScan(player.getUniqueId())) { 21 | // Player will be notified of the results, no need to send verification. 22 | } else { 23 | plugin.getMessages().getMessage(Messages.Key.CANCELSCAN_NO_SCAN).sendTo(player); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/CommandInsights.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands; 2 | 3 | import dev.frankheijden.insights.Insights; 4 | import dev.frankheijden.insights.api.InsightsPlugin; 5 | import dev.frankheijden.insights.api.commands.InsightsCommand; 6 | import dev.frankheijden.insights.api.config.Messages; 7 | import dev.frankheijden.insights.api.utils.ColorUtils; 8 | import dev.frankheijden.insights.api.utils.StringUtils; 9 | import dev.frankheijden.insights.concurrent.ContainerExecutorService; 10 | import org.bukkit.command.CommandSender; 11 | import org.incendo.cloud.annotations.Command; 12 | import org.incendo.cloud.annotations.Permission; 13 | 14 | @Command("insights|in") 15 | public class CommandInsights extends InsightsCommand { 16 | 17 | public CommandInsights(InsightsPlugin plugin) { 18 | super(plugin); 19 | } 20 | 21 | @Command("") 22 | @Permission("insights.info") 23 | private void showBase(CommandSender sender) { 24 | sender.sendMessage(ColorUtils.colorize( 25 | "&8&l&m---------------=&r&8[ &b&lInsights&8 ]&l&m=----------------", 26 | "&b Plugin version: &a" + plugin.getDescription().getVersion(), 27 | "&b Plugin author: &7FrankHeijden#0099", 28 | "&b Plugin link: &7https://www.spigotmc.org/resources/56489/", 29 | "&b Wiki: &7https://github.com/InsightsPlugin/Insights/wiki", 30 | "&8&m-------------------------------------------------" 31 | )); 32 | } 33 | 34 | @Command("reload") 35 | @Permission("insights.reload") 36 | private void reloadConfigurations(CommandSender sender) { 37 | plugin.reloadConfigs(); 38 | plugin.reload(); 39 | plugin.getMessages().getMessage(Messages.Key.CONFIGS_RELOADED).sendTo(sender); 40 | } 41 | 42 | @Command("stats") 43 | @Permission("insights.stats") 44 | private void displayStatistics(CommandSender sender) { 45 | ContainerExecutorService executor = ((Insights) plugin).getExecutor(); 46 | plugin.getMessages().getMessage(Messages.Key.STATS).addTemplates( 47 | Messages.tagOf("chunks_scanned", StringUtils.pretty(executor.getCompletedTaskCount())), 48 | Messages.tagOf( 49 | "blocks_scanned", 50 | StringUtils.pretty(plugin.getMetricsManager().getTotalBlocksScanned().sum()) 51 | ), 52 | Messages.tagOf("queue_size", StringUtils.pretty(executor.getQueueSize())) 53 | ).sendTo(sender); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/CommandScanHistory.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.commands.InsightsCommand; 5 | import dev.frankheijden.insights.api.config.Messages; 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | import org.bukkit.entity.Player; 9 | import org.incendo.cloud.annotations.Argument; 10 | import org.incendo.cloud.annotations.Command; 11 | 12 | public class CommandScanHistory extends InsightsCommand { 13 | 14 | public CommandScanHistory(InsightsPlugin plugin) { 15 | super(plugin); 16 | } 17 | 18 | @Command("scanhistory ") 19 | private void handleScanHistory( 20 | Player player, 21 | @Argument("page") Page page 22 | ) { 23 | UUID uuid = player.getUniqueId(); 24 | Optional> historyOptional = plugin.getScanHistory().getHistory(uuid); 25 | if (historyOptional.isPresent()) { 26 | historyOptional.get().sendTo(player, page.page); 27 | } else { 28 | plugin.getMessages().getMessage(Messages.Key.SCANHISTORY_NO_HISTORY).sendTo(player); 29 | } 30 | } 31 | 32 | public record Page(int page) {} 33 | } 34 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/CommandScanRegion.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.addons.Region; 5 | import dev.frankheijden.insights.api.commands.InsightsCommand; 6 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 7 | import dev.frankheijden.insights.api.config.Messages; 8 | import dev.frankheijden.insights.api.config.limits.Limit; 9 | import dev.frankheijden.insights.api.objects.chunk.ChunkPart; 10 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 11 | import dev.frankheijden.insights.api.reflection.RTileEntityTypes; 12 | import dev.frankheijden.insights.api.tasks.ScanTask; 13 | import dev.frankheijden.insights.api.utils.Constants; 14 | import org.bukkit.entity.Player; 15 | import org.incendo.cloud.annotations.Argument; 16 | import org.incendo.cloud.annotations.Command; 17 | import org.incendo.cloud.annotations.Flag; 18 | import org.incendo.cloud.annotations.Permission; 19 | import java.util.Arrays; 20 | import java.util.HashSet; 21 | import java.util.List; 22 | import java.util.Optional; 23 | import java.util.Set; 24 | 25 | @Command("scanregion") 26 | public class CommandScanRegion extends InsightsCommand { 27 | 28 | public CommandScanRegion(InsightsPlugin plugin) { 29 | super(plugin); 30 | } 31 | 32 | @Command("tile") 33 | @Permission("insights.scanregion.tile") 34 | private void handleTileScan( 35 | Player player, 36 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk 37 | ) { 38 | handleScan(player, RTileEntityTypes.getTileEntities(), ScanOptions.materialsOnly(), false, groupByChunk); 39 | } 40 | 41 | @Command("entity") 42 | @Permission("insights.scanregion.entity") 43 | private void handleEntityScan( 44 | Player player, 45 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk 46 | ) { 47 | handleScan(player, Constants.SCAN_ENTITIES, ScanOptions.entitiesOnly(), false, groupByChunk); 48 | } 49 | 50 | @Command("all") 51 | @Permission("insights.scanregion.all") 52 | private void handleAllScan( 53 | Player player, 54 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk 55 | ) { 56 | handleScan(player, null, ScanOptions.scanOnly(), false, groupByChunk); 57 | } 58 | 59 | @Command("custom ") 60 | @Permission("insights.scanregion.custom") 61 | private void handleCustomScan( 62 | Player player, 63 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk, 64 | @Argument("items") ScanObject[] items 65 | ) { 66 | handleScan(player, new HashSet<>(Arrays.asList(items)), ScanOptions.scanOnly(), true, groupByChunk); 67 | } 68 | 69 | @Command("limit ") 70 | @Permission("insights.scanregion.limit") 71 | private void handleLimitScan( 72 | Player player, 73 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk, 74 | @Argument("limit") Limit limit 75 | ) { 76 | handleScan(player, limit.getScanObjects(), limit.getScanOptions(), false, groupByChunk); 77 | } 78 | 79 | /** 80 | * Checks the player's location for a region and scans it for materials. 81 | */ 82 | public void handleScan( 83 | Player player, 84 | Set> items, 85 | ScanOptions options, 86 | boolean displayZeros, 87 | boolean groupByChunk 88 | ) { 89 | Optional optionalRegion = plugin.getAddonManager().getRegion(player.getLocation()); 90 | if (optionalRegion.isEmpty()) { 91 | plugin.getMessages().getMessage(Messages.Key.SCANREGION_NO_REGION).sendTo(player); 92 | return; 93 | } 94 | 95 | List parts = optionalRegion.get().toChunkParts(); 96 | if (groupByChunk) { 97 | ScanTask.scanAndDisplayGroupedByChunk(plugin, player, parts, parts.size(), options, items, false); 98 | } else { 99 | ScanTask.scanAndDisplay(plugin, player, parts, parts.size(), options, items, displayZeros); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/CommandScanWorld.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.commands.InsightsCommand; 5 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 6 | import dev.frankheijden.insights.api.config.limits.Limit; 7 | import dev.frankheijden.insights.api.objects.chunk.ChunkLocation; 8 | import dev.frankheijden.insights.api.objects.chunk.ChunkPart; 9 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 10 | import dev.frankheijden.insights.api.reflection.RTileEntityTypes; 11 | import dev.frankheijden.insights.api.tasks.ScanTask; 12 | import dev.frankheijden.insights.api.utils.Constants; 13 | import org.bukkit.Chunk; 14 | import org.bukkit.World; 15 | import org.bukkit.entity.Player; 16 | import org.incendo.cloud.annotations.Argument; 17 | import org.incendo.cloud.annotations.Command; 18 | import org.incendo.cloud.annotations.Flag; 19 | import org.incendo.cloud.annotations.Permission; 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | 26 | @Command("scanworld") 27 | public class CommandScanWorld extends InsightsCommand { 28 | 29 | public CommandScanWorld(InsightsPlugin plugin) { 30 | super(plugin); 31 | } 32 | 33 | @Command("tile") 34 | @Permission("insights.scanworld.tile") 35 | private void handleTileScan( 36 | Player player, 37 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk 38 | ) { 39 | handleScan(player, RTileEntityTypes.getTileEntities(), ScanOptions.materialsOnly(), false, groupByChunk); 40 | } 41 | 42 | @Command("entity") 43 | @Permission("insights.scanworld.entity") 44 | private void handleEntityScan( 45 | Player player, 46 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk 47 | ) { 48 | handleScan(player, Constants.SCAN_ENTITIES, ScanOptions.entitiesOnly(), false, groupByChunk); 49 | } 50 | 51 | @Command("all") 52 | @Permission("insights.scanworld.all") 53 | private void handleAllScan( 54 | Player player, 55 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk 56 | ) { 57 | handleScan(player, null, ScanOptions.scanOnly(), false, groupByChunk); 58 | } 59 | 60 | @Command("custom ") 61 | @Permission("insights.scanworld.custom") 62 | private void handleCustomScan( 63 | Player player, 64 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk, 65 | @Argument("items") ScanObject[] items 66 | ) { 67 | handleScan(player, new HashSet<>(Arrays.asList(items)), ScanOptions.scanOnly(), true, groupByChunk); 68 | } 69 | 70 | @Command("limit ") 71 | @Permission("insights.scanworld.limit") 72 | private void handleLimitScan( 73 | Player player, 74 | @Flag(value = "group-by-chunk", aliases = { "c" }) boolean groupByChunk, 75 | @Argument("limit") Limit limit 76 | ) { 77 | handleScan(player, limit.getScanObjects(), limit.getScanOptions(), false, groupByChunk); 78 | } 79 | 80 | /** 81 | * Scans chunks in the world of a player. 82 | */ 83 | public void handleScan( 84 | Player player, 85 | Set> items, 86 | ScanOptions options, 87 | boolean displayZeros, 88 | boolean groupByChunk 89 | ) { 90 | World world = player.getWorld(); 91 | 92 | // Generate chunk parts 93 | Chunk[] chunks = world.getLoadedChunks(); 94 | List chunkParts = new ArrayList<>(chunks.length); 95 | for (Chunk chunk : chunks) { 96 | chunkParts.add(ChunkLocation.of(chunk).toPart()); 97 | } 98 | 99 | if (groupByChunk) { 100 | ScanTask.scanAndDisplayGroupedByChunk(plugin, player, chunkParts, chunkParts.size(), options, items, false); 101 | } else { 102 | ScanTask.scanAndDisplay(plugin, player, chunkParts, chunkParts.size(), options, items, displayZeros); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/CommandTeleportChunk.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.commands.InsightsCommand; 5 | import dev.frankheijden.insights.api.config.Messages; 6 | import org.bukkit.World; 7 | import org.bukkit.entity.Player; 8 | import org.incendo.cloud.annotations.Argument; 9 | import org.incendo.cloud.annotations.Command; 10 | import org.incendo.cloud.annotations.Flag; 11 | import org.incendo.cloud.annotations.Permission; 12 | 13 | public class CommandTeleportChunk extends InsightsCommand { 14 | 15 | public CommandTeleportChunk(InsightsPlugin plugin) { 16 | super(plugin); 17 | } 18 | 19 | @Command("teleportchunk|tpchunk ") 20 | @Permission("insights.teleportchunk") 21 | private void handleTeleportChunk( 22 | Player player, 23 | @Argument("world") World world, 24 | @Argument("x") int chunkX, 25 | @Argument("z") int chunkZ, 26 | @Flag(value = "generate", aliases = { "g" }) boolean generate 27 | ) { 28 | var chunkTp = plugin.getChunkTeleport(); 29 | var messages = plugin.getMessages(); 30 | chunkTp.teleportPlayerToChunk(player, world, chunkX, chunkZ, generate).whenComplete((res, err) -> { 31 | Messages.Message message; 32 | if (err != null) { 33 | message = messages.getMessage(Messages.Key.TELEPORTCHUNK_ERROR); 34 | } else { 35 | message = switch (res) { 36 | case NOT_GENERATED -> messages.getMessage(Messages.Key.TELEPORTCHUNK_NOT_GENERATED); 37 | case FAILED -> messages.getMessage(Messages.Key.TELEPORTCHUNK_FAILED); 38 | case SUCCESS -> messages.getMessage(Messages.Key.TELEPORTCHUNK_SUCCESS).addTemplates( 39 | Messages.tagOf("world", world.getName()), 40 | Messages.tagOf("chunk-x", chunkX), 41 | Messages.tagOf("chunk-z", chunkZ) 42 | ); 43 | default -> throw new IllegalArgumentException("Unhandled result case: " + res); 44 | }; 45 | } 46 | message.sendTo(player); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/parser/LimitParser.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands.parser; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.limits.Limit; 5 | import java.util.concurrent.CompletableFuture; 6 | import org.incendo.cloud.context.CommandContext; 7 | import org.incendo.cloud.context.CommandInput; 8 | import org.incendo.cloud.parser.ArgumentParseResult; 9 | import org.incendo.cloud.parser.ArgumentParser; 10 | import org.incendo.cloud.parser.ParserParameters; 11 | import org.incendo.cloud.suggestion.Suggestion; 12 | import org.incendo.cloud.suggestion.SuggestionProvider; 13 | 14 | public class LimitParser implements ArgumentParser, SuggestionProvider { 15 | 16 | public LimitParser(ParserParameters options) { 17 | // 18 | } 19 | 20 | @Override 21 | public ArgumentParseResult parse(CommandContext ctx, CommandInput input) { 22 | var fileName = input.peekString(); 23 | var limit = InsightsPlugin.getInstance().getLimits().getLimitByFileName(fileName); 24 | if (limit.isEmpty()) { 25 | return ArgumentParseResult.failure(new IllegalArgumentException( 26 | "Invalid limit file name '" + fileName + "'" 27 | )); 28 | } 29 | 30 | input.readString(); 31 | return ArgumentParseResult.success(limit.get()); 32 | } 33 | 34 | 35 | @Override 36 | public CompletableFuture> suggestionsFuture( 37 | CommandContext ctx, 38 | CommandInput input 39 | ) { 40 | return CompletableFuture.completedFuture( 41 | InsightsPlugin.getInstance() 42 | .getLimits() 43 | .getLimitFileNames() 44 | .stream() 45 | .map(Suggestion::suggestion) 46 | .toList() 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/parser/ScanHistoryPageParser.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands.parser; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.config.Messages; 5 | import dev.frankheijden.insights.api.utils.StringUtils; 6 | import dev.frankheijden.insights.commands.CommandScanHistory; 7 | import io.papermc.paper.command.brigadier.CommandSourceStack; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.concurrent.CompletableFuture; 12 | import org.bukkit.entity.Player; 13 | import org.incendo.cloud.context.CommandContext; 14 | import org.incendo.cloud.context.CommandInput; 15 | import org.incendo.cloud.parser.ArgumentParseResult; 16 | import org.incendo.cloud.parser.ArgumentParser; 17 | import org.incendo.cloud.parser.ParserParameters; 18 | import org.incendo.cloud.suggestion.Suggestion; 19 | import org.incendo.cloud.suggestion.SuggestionProvider; 20 | 21 | public class ScanHistoryPageParser implements ArgumentParser, SuggestionProvider { 22 | 23 | public ScanHistoryPageParser(ParserParameters options) { 24 | // 25 | } 26 | 27 | @Override 28 | public ArgumentParseResult parse(CommandContext ctx, CommandInput input) { 29 | try { 30 | var pageNumber = Integer.parseInt(input.peekString()); 31 | if (pageNumber <= 0) throw new NumberFormatException(); 32 | 33 | input.readString(); 34 | return ArgumentParseResult.success(new CommandScanHistory.Page(pageNumber - 1)); 35 | } catch (NumberFormatException ex) { 36 | return ArgumentParseResult.failure(new IllegalArgumentException( 37 | "Invalid Page '" + input.peekString() + "'" 38 | )); 39 | } 40 | } 41 | 42 | @Override 43 | public CompletableFuture> suggestionsFuture( 44 | CommandContext ctx, 45 | CommandInput input 46 | ) { 47 | return CompletableFuture.supplyAsync(() -> { 48 | if (ctx.sender() instanceof CommandSourceStack sourceStack 49 | && sourceStack.getSender() instanceof Player player) { 50 | var scanHistory = InsightsPlugin.getInstance().getScanHistory(); 51 | int pages = scanHistory.getHistory(player.getUniqueId()) 52 | .map(Messages.PaginatedMessage::getPageAmount) 53 | .orElse(0); 54 | 55 | List suggestions = new ArrayList<>(pages); 56 | for (var i = 1; i <= pages; i++) { 57 | suggestions.add(String.valueOf(i)); 58 | } 59 | 60 | return StringUtils.findThatStartsWith(suggestions, input.peekString()).stream() 61 | .map(Suggestion::suggestion) 62 | .toList(); 63 | } 64 | 65 | return Collections.emptyList(); 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/parser/ScanObjectArrayParser.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands.parser; 2 | 3 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 4 | import dev.frankheijden.insights.api.utils.Constants; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Set; 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | import dev.frankheijden.insights.api.utils.StringUtils; 12 | import org.incendo.cloud.context.CommandContext; 13 | import org.incendo.cloud.context.CommandInput; 14 | import org.incendo.cloud.parser.ArgumentParseResult; 15 | import org.incendo.cloud.parser.ArgumentParser; 16 | import org.incendo.cloud.parser.ParserParameters; 17 | import org.incendo.cloud.suggestion.Suggestion; 18 | import org.incendo.cloud.suggestion.SuggestionProvider; 19 | 20 | public class ScanObjectArrayParser implements ArgumentParser[]>, SuggestionProvider { 21 | protected static final Set SUGGESTIONS = Stream.>concat( 22 | Constants.BLOCKS.stream(), 23 | Constants.ENTITIES.stream() 24 | ).map(Enum::name).map(String::toLowerCase).collect(Collectors.toSet()); 25 | 26 | public ScanObjectArrayParser(ParserParameters options) { 27 | // 28 | } 29 | 30 | @Override 31 | public ArgumentParseResult[]> parse(CommandContext ctx, CommandInput input) { 32 | try { 33 | // Find the index of the first item in the *actual* input 34 | // input.input() is the entire command 35 | int indexOf = input.input().indexOf(input.peekString()); 36 | 37 | // Find the number of elements that need to be in the array 38 | int queueSize = input.input().substring(indexOf).split(" ").length; 39 | 40 | List> items = new ArrayList<>(queueSize); 41 | for (var i = 0; i < (queueSize - 1); i++) { 42 | items.add(ScanObject.parse(input.peekString())); 43 | input.readString(); 44 | } 45 | 46 | String last = input.peekString(); 47 | if (!last.equalsIgnoreCase("-c") && !last.equalsIgnoreCase("--group-by-chunk")) { 48 | items.add(ScanObject.parse(last)); 49 | input.readString(); 50 | } 51 | 52 | return ArgumentParseResult.success(items.toArray(ScanObject[]::new)); 53 | } catch (IllegalArgumentException ex) { 54 | return ArgumentParseResult.failure(new IllegalArgumentException( 55 | "Invalid Material '" + input.peekString() + "'" 56 | )); 57 | } 58 | } 59 | 60 | @Override 61 | public CompletableFuture> suggestionsFuture( 62 | CommandContext ctx, 63 | CommandInput input 64 | ) { 65 | return CompletableFuture.completedFuture( 66 | StringUtils.findThatStartsWith(SUGGESTIONS, input.peekString()).stream() 67 | .map(Suggestion::suggestion) 68 | .toList() 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/parser/WorldParser.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands.parser; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.World; 6 | import org.incendo.cloud.context.CommandContext; 7 | import org.incendo.cloud.context.CommandInput; 8 | import org.incendo.cloud.parser.ArgumentParseResult; 9 | import org.incendo.cloud.parser.ArgumentParser; 10 | import org.incendo.cloud.parser.ParserParameters; 11 | import org.incendo.cloud.suggestion.Suggestion; 12 | import org.incendo.cloud.suggestion.SuggestionProvider; 13 | 14 | public class WorldParser implements ArgumentParser, SuggestionProvider { 15 | public WorldParser(ParserParameters options) { 16 | // 17 | } 18 | 19 | @Override 20 | public ArgumentParseResult parse(CommandContext ctx, CommandInput input) { 21 | var world = Bukkit.getWorld(input.peekString()); 22 | if (world == null) { 23 | return ArgumentParseResult.failure(new IllegalArgumentException( 24 | "Invalid World '" + input.peekString() + "'" 25 | )); 26 | } 27 | 28 | input.readString(); 29 | return ArgumentParseResult.success(world); 30 | } 31 | 32 | @Override 33 | public CompletableFuture> suggestionsFuture( 34 | CommandContext ctx, 35 | CommandInput input 36 | ) { 37 | return CompletableFuture.completedFuture( 38 | Bukkit.getWorlds() 39 | .stream() 40 | .map(world -> Suggestion.suggestion(world.getName())) 41 | .toList() 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/commands/util/CommandSenderMapper.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.commands.util; 2 | 3 | import io.papermc.paper.command.brigadier.CommandSourceStack; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.Location; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.entity.Entity; 8 | import org.incendo.cloud.SenderMapper; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | public class CommandSenderMapper implements SenderMapper { 13 | @Override 14 | public CommandSender map(CommandSourceStack source) { 15 | return source.getSender(); 16 | } 17 | 18 | @Override 19 | public CommandSourceStack reverse(CommandSender sender) { 20 | return new CommandSourceStack() { 21 | @Override 22 | public @NotNull Location getLocation() { 23 | if (sender instanceof Entity entity) { 24 | return entity.getLocation(); 25 | } 26 | 27 | var worlds = Bukkit.getWorlds(); 28 | return new Location(worlds.isEmpty() ? null : worlds.getFirst(), 0, 0, 0); // Best effort lol 29 | } 30 | 31 | @Override 32 | public @NotNull CommandSender getSender() { 33 | return sender; 34 | } 35 | 36 | @Override 37 | public @Nullable Entity getExecutor() { 38 | return sender instanceof Entity entity ? entity : null; 39 | } 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/concurrent/ContainerExecutorService.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.concurrent; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import dev.frankheijden.insights.api.InsightsPlugin; 5 | import dev.frankheijden.insights.api.concurrent.ContainerExecutor; 6 | import dev.frankheijden.insights.api.concurrent.containers.RunnableContainer; 7 | import dev.frankheijden.insights.api.concurrent.containers.SupplierContainer; 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.LinkedBlockingQueue; 10 | import java.util.concurrent.ThreadPoolExecutor; 11 | import java.util.concurrent.TimeUnit; 12 | import java.util.logging.Level; 13 | 14 | public class ContainerExecutorService implements ContainerExecutor { 15 | 16 | private final ThreadPoolExecutor executor; 17 | private final int timeoutMs; 18 | 19 | private ContainerExecutorService(ThreadPoolExecutor executor, int timeoutMs) { 20 | this.executor = executor; 21 | this.timeoutMs = timeoutMs; 22 | } 23 | 24 | /** 25 | * Constructs a new containerexecutor with given amount of worker threads. 26 | */ 27 | public static ContainerExecutorService newExecutor(int nThreads, int timeoutMs) { 28 | return new ContainerExecutorService( 29 | new ThreadPoolExecutor( 30 | nThreads, 31 | nThreads, 32 | 0L, TimeUnit.MILLISECONDS, 33 | new LinkedBlockingQueue<>(), 34 | new ThreadFactoryBuilder() 35 | .setNameFormat("Insights-worker-%d") 36 | .setUncaughtExceptionHandler((t, e) -> InsightsPlugin.getInstance().getLogger().log( 37 | Level.SEVERE, 38 | String.format("[%s] Error occurred on worker thread:", t.getName()), 39 | e 40 | )) 41 | .build() 42 | ), 43 | timeoutMs 44 | ); 45 | } 46 | 47 | @Override 48 | public CompletableFuture submit(SupplierContainer container) { 49 | return CompletableFuture.supplyAsync(container, executor).orTimeout(timeoutMs, TimeUnit.MILLISECONDS); 50 | } 51 | 52 | @Override 53 | public CompletableFuture submit(RunnableContainer container) { 54 | return CompletableFuture.runAsync(container, executor).orTimeout(timeoutMs, TimeUnit.MILLISECONDS); 55 | } 56 | 57 | public int getQueueSize() { 58 | return executor.getQueue().size(); 59 | } 60 | 61 | public long getCompletedTaskCount() { 62 | return executor.getCompletedTaskCount(); 63 | } 64 | 65 | @Override 66 | public void shutdown() { 67 | executor.shutdownNow(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/listeners/ChunkListener.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.listeners; 2 | 3 | import dev.frankheijden.insights.Insights; 4 | import dev.frankheijden.insights.api.listeners.InsightsListener; 5 | import dev.frankheijden.insights.api.utils.ChunkUtils; 6 | import org.bukkit.Chunk; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.world.ChunkUnloadEvent; 9 | 10 | public class ChunkListener extends InsightsListener { 11 | 12 | protected Insights insights; 13 | 14 | public ChunkListener(Insights plugin) { 15 | super(plugin); 16 | this.insights = plugin; 17 | } 18 | 19 | /** 20 | * Cleans up any chunk data from insights when a chunk unloads. 21 | */ 22 | @EventHandler 23 | public void onChunkUnload(ChunkUnloadEvent event) { 24 | Chunk chunk = event.getChunk(); 25 | long chunkKey = ChunkUtils.getKey(chunk); 26 | plugin.getWorldStorage().getWorld(chunk.getWorld().getUID()).remove(chunkKey); 27 | insights.getRedstoneUpdateCount().remove(chunkKey); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/listeners/PaperBlockListener.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.listeners; 2 | 3 | import com.destroystokyo.paper.event.block.BlockDestroyEvent; 4 | import dev.frankheijden.insights.api.InsightsPlugin; 5 | import dev.frankheijden.insights.api.annotations.AllowDisabling; 6 | import dev.frankheijden.insights.api.listeners.InsightsListener; 7 | import org.bukkit.block.Block; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.EventPriority; 10 | 11 | public class PaperBlockListener extends InsightsListener { 12 | 13 | public PaperBlockListener(InsightsPlugin plugin) { 14 | super(plugin); 15 | } 16 | 17 | @AllowDisabling 18 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 19 | public void onBlockDestroy(BlockDestroyEvent event) { 20 | Block block = event.getBlock(); 21 | handleModification(block.getLocation(), block.getType(), event.getNewState().getMaterial(), 1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/listeners/PaperEntityListener.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.listeners; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.EventPriority; 8 | import org.bukkit.event.block.TNTPrimeEvent; 9 | 10 | public class PaperEntityListener extends EntityListener { 11 | 12 | public PaperEntityListener(InsightsPlugin plugin) { 13 | super(plugin); 14 | } 15 | 16 | /** 17 | * Handles the EntityRemoveFromWorldEvent as "catch-all" for entity removals. 18 | */ 19 | @EventHandler(priority = EventPriority.MONITOR) 20 | public void onEntityRemove(com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent event) { 21 | handleEntityRemoveFromWorld(event.getEntity()); 22 | } 23 | 24 | /** 25 | * Handles the TNTPrimeEvent for ignited TNT blocks. 26 | */ 27 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 28 | public void onTNTPrime(TNTPrimeEvent event) { 29 | var primingEntity = event.getPrimingEntity(); 30 | var block = event.getBlock(); 31 | 32 | if (primingEntity instanceof Player) { 33 | var player = (Player) primingEntity; 34 | 35 | handleRemoval(player, block.getLocation(), ScanObject.of(block.getType()), 1, false); 36 | } else { 37 | handleModification(block, -1); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/listeners/PlayerListener.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.listeners; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import dev.frankheijden.insights.api.InsightsPlugin; 6 | import dev.frankheijden.insights.api.listeners.InsightsListener; 7 | import dev.frankheijden.insights.api.tasks.UpdateCheckerTask; 8 | import dev.frankheijden.insights.api.utils.BlockUtils; 9 | import dev.frankheijden.insights.api.utils.LocationUtils; 10 | import org.bukkit.Location; 11 | import org.bukkit.Material; 12 | import org.bukkit.Tag; 13 | import org.bukkit.World; 14 | import org.bukkit.block.Block; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.event.EventHandler; 17 | import org.bukkit.event.EventPriority; 18 | import org.bukkit.event.block.Action; 19 | import org.bukkit.event.player.PlayerInteractEvent; 20 | import org.bukkit.event.player.PlayerJoinEvent; 21 | import org.bukkit.event.player.PlayerQuitEvent; 22 | import java.util.Optional; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | public class PlayerListener extends InsightsListener { 26 | 27 | private final Cache intentionalDesignBugs = CacheBuilder.newBuilder() 28 | .expireAfterWrite(500, TimeUnit.MILLISECONDS) 29 | .build(); 30 | 31 | public PlayerListener(InsightsPlugin plugin) { 32 | super(plugin); 33 | } 34 | 35 | public Optional getIntentionalDesignBugAt(Location loc) { 36 | return Optional.ofNullable(this.intentionalDesignBugs.getIfPresent(LocationUtils.getKey(loc))); 37 | } 38 | 39 | /** 40 | * Handles the PlayerJoinEvent, updating the concurrent PlayerList and checking for updates. 41 | */ 42 | @EventHandler(priority = EventPriority.MONITOR) 43 | public void onPlayerJoin(PlayerJoinEvent event) { 44 | Player player = event.getPlayer(); 45 | plugin.getPlayerList().addPlayer(player); 46 | 47 | if (player.hasPermission("insights.update")) { 48 | UpdateCheckerTask.check(player); 49 | } 50 | } 51 | 52 | @EventHandler(priority = EventPriority.MONITOR) 53 | public void onPlayerQuit(PlayerQuitEvent event) { 54 | plugin.getPlayerList().removePlayer(event.getPlayer()); 55 | } 56 | 57 | /** 58 | * Handles the PlayerInteractEvent to track bed explosions in the nether/end. 59 | */ 60 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 61 | public void onPlayerInteract(PlayerInteractEvent event) { 62 | if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; 63 | 64 | Block block = event.getClickedBlock(); 65 | if (block == null) return; 66 | 67 | // Only need to check beds 68 | Material material = block.getType(); 69 | if (!Tag.BEDS.isTagged(material)) return; 70 | 71 | // Only need to check this in the nether/end 72 | if (block.getWorld().getEnvironment() == World.Environment.NORMAL) return; 73 | 74 | BlockUtils.getOtherHalf(block).ifPresent(otherHalf -> { 75 | Location location = block.getLocation(); 76 | Location otherHalfLocation = otherHalf.getLocation(); 77 | 78 | ExplodedBed explodedBed = new ExplodedBed(material, location, otherHalfLocation); 79 | intentionalDesignBugs.put(LocationUtils.getKey(location), explodedBed); 80 | intentionalDesignBugs.put(LocationUtils.getKey(otherHalfLocation), explodedBed); 81 | }); 82 | } 83 | 84 | public static final class ExplodedBed { 85 | 86 | private final Material material; 87 | private final Location head; 88 | private final Location foot; 89 | 90 | private ExplodedBed(Material material, Location head, Location foot) { 91 | this.material = material; 92 | this.head = head; 93 | this.foot = foot; 94 | } 95 | 96 | public Material getMaterial() { 97 | return material; 98 | } 99 | 100 | public Location getHead() { 101 | return head; 102 | } 103 | 104 | public Location getFoot() { 105 | return foot; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/listeners/WorldListener.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.listeners; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.annotations.AllowDisabling; 5 | import dev.frankheijden.insights.api.listeners.InsightsListener; 6 | import org.bukkit.block.Block; 7 | import org.bukkit.block.BlockState; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.EventPriority; 10 | import org.bukkit.event.world.StructureGrowEvent; 11 | 12 | public class WorldListener extends InsightsListener { 13 | 14 | public WorldListener(InsightsPlugin plugin) { 15 | super(plugin); 16 | } 17 | 18 | /** 19 | * Handles structure grows (trees/giant mushrooms). 20 | */ 21 | @AllowDisabling 22 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 23 | public void onStructureGrow(StructureGrowEvent event) { 24 | Block block = event.getLocation().getBlock(); 25 | handleModification(block, -1); 26 | 27 | for (BlockState state : event.getBlocks()) { 28 | handleModification(state, 1); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/placeholders/InsightsPlaceholderExpansion.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.placeholders; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.addons.Region; 5 | import dev.frankheijden.insights.api.concurrent.storage.Storage; 6 | import dev.frankheijden.insights.api.config.LimitEnvironment; 7 | import dev.frankheijden.insights.api.config.limits.Limit; 8 | import dev.frankheijden.insights.api.objects.wrappers.ScanObject; 9 | import dev.frankheijden.insights.api.utils.ChunkUtils; 10 | import dev.frankheijden.insights.api.utils.StringUtils; 11 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 12 | import org.bukkit.Location; 13 | import org.bukkit.World; 14 | import org.bukkit.entity.Player; 15 | import java.util.Locale; 16 | import java.util.Optional; 17 | import java.util.UUID; 18 | 19 | public class InsightsPlaceholderExpansion extends PlaceholderExpansion { 20 | 21 | private final InsightsPlugin plugin; 22 | 23 | public InsightsPlaceholderExpansion(InsightsPlugin plugin) { 24 | this.plugin = plugin; 25 | } 26 | 27 | @Override 28 | public String getIdentifier() { 29 | return "insights"; 30 | } 31 | 32 | @Override 33 | public String getAuthor() { 34 | return String.join(", ", plugin.getDescription().getAuthors()); 35 | } 36 | 37 | @Override 38 | public String getVersion() { 39 | return plugin.getDescription().getVersion(); 40 | } 41 | 42 | @Override 43 | public String onPlaceholderRequest(Player player, String identifier) { 44 | if (identifier == null) return ""; 45 | String[] args = identifier.split("_"); 46 | switch (args[0].toLowerCase(Locale.ENGLISH)) { 47 | case "limits": 48 | if (args.length < 3) break; 49 | 50 | String itemString = StringUtils.join(args, "_", 2).toUpperCase(Locale.ENGLISH); 51 | final ScanObject item; 52 | try { 53 | item = ScanObject.parse(itemString); 54 | } catch (IllegalArgumentException ex) { 55 | return ""; 56 | } 57 | 58 | Location location = player.getLocation(); 59 | World world = location.getWorld(); 60 | UUID worldUid = world.getUID(); 61 | LimitEnvironment env = new LimitEnvironment(player, world.getName()); 62 | Optional limitOptional = plugin.getLimits().getFirstLimit(item, env); 63 | if (!limitOptional.isPresent()) break; 64 | 65 | Limit limit = limitOptional.get(); 66 | switch (args[1].toLowerCase(Locale.ENGLISH)) { 67 | case "name": return limit.getLimit(item).getName(); 68 | case "max": return String.valueOf(limit.getLimit(item).getLimit()); 69 | case "count": 70 | Optional regionOptional = plugin.getAddonManager().getRegion(location); 71 | Optional storageOptional; 72 | if (regionOptional.isPresent()) { 73 | Region region = regionOptional.get(); 74 | storageOptional = plugin.getAddonStorage().get(region.getKey()); 75 | } else { 76 | long chunkKey = ChunkUtils.getKey(location); 77 | storageOptional = plugin.getWorldStorage().getWorld(worldUid).get(chunkKey); 78 | } 79 | return storageOptional.map(storage -> String.valueOf(storage.count(limit, item))) 80 | .orElse(""); 81 | default: break; 82 | } 83 | break; 84 | default: break; 85 | } 86 | return ""; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Insights/src/main/java/dev/frankheijden/insights/tasks/PlayerTrackerTask.java: -------------------------------------------------------------------------------- 1 | package dev.frankheijden.insights.tasks; 2 | 3 | import dev.frankheijden.insights.api.InsightsPlugin; 4 | import dev.frankheijden.insights.api.concurrent.ScanOptions; 5 | import dev.frankheijden.insights.api.objects.chunk.ChunkLocation; 6 | import dev.frankheijden.insights.api.tasks.InsightsAsyncTask; 7 | import org.bukkit.entity.Player; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.UUID; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | import java.util.logging.Level; 14 | 15 | public class PlayerTrackerTask extends InsightsAsyncTask { 16 | 17 | private final Map scanLocations = new ConcurrentHashMap<>(); 18 | private static final Set knownErrorStackTraceHashes = ConcurrentHashMap.newKeySet(); 19 | 20 | public PlayerTrackerTask(InsightsPlugin plugin) { 21 | super(plugin); 22 | } 23 | 24 | @Override 25 | public void run() { 26 | var worldStorage = plugin.getWorldStorage(); 27 | Set locations = new HashSet<>(); 28 | for (Map.Entry entry : plugin.getPlayerList()) { 29 | var location = entry.getValue().getLocation(); 30 | var world = location.getWorld(); 31 | Set loadedChunks = worldStorage.getWorld(world.getUID()).getChunks(); 32 | 33 | int chunkX = location.getBlockX() >> 4; 34 | int chunkZ = location.getBlockZ() >> 4; 35 | for (int x = -1; x <= 1; x++) { 36 | for (int z = -1; z <= 1; z++) { 37 | var loc = new ChunkLocation(world, chunkX + x, chunkZ + z); 38 | if (!loadedChunks.contains(loc.getKey()) && !this.scanLocations.containsKey(loc)) { 39 | locations.add(loc); 40 | } 41 | } 42 | } 43 | } 44 | 45 | if (locations.isEmpty()) { 46 | return; 47 | } 48 | 49 | plugin.getServer().getScheduler().runTask(plugin, () -> { 50 | long now = System.nanoTime(); 51 | for (ChunkLocation loc : locations) { 52 | var world = loc.getWorld(); 53 | if (world.isChunkLoaded(loc.getX(), loc.getZ())) { 54 | this.scanLocations.put(loc, now); 55 | 56 | var chunk = world.getChunkAt(loc.getX(), loc.getZ()); 57 | plugin.getChunkContainerExecutor().submit(chunk, ScanOptions.all()).whenComplete((s, e) -> { 58 | if (s == null) { 59 | int hash = e.getStackTrace()[0].hashCode(); 60 | if (!knownErrorStackTraceHashes.contains(hash)) { 61 | knownErrorStackTraceHashes.add(hash); 62 | plugin.getLogger().log( 63 | Level.SEVERE, 64 | "Error occurred while scanning " 65 | + loc 66 | + " (future errors with the same stacktrace are suppressed)", 67 | e 68 | ); 69 | } 70 | } 71 | this.scanLocations.remove(loc); 72 | }); 73 | } 74 | } 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Insights/src/main/resources/bed-limit.yml: -------------------------------------------------------------------------------- 1 | # This is an example group limit for beds. This regex limit will limit all beds to 64 per chunk / addon area. 2 | # Please note that you must escape \ as "\\" 3 | limit: 4 | name: "Beds" 5 | type: "GROUP" 6 | bypass-permission: "insights.bypass.limit.beds" 7 | limit: 64 8 | regex: true 9 | materials: 10 | - "^.*_BED\\b" -------------------------------------------------------------------------------- /Insights/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | update-checker: 3 | enabled: true 4 | interval-seconds: 10800 5 | scans: 6 | concurrent-threads: -1 7 | timeout-millis: 10000 8 | iteration-interval-ticks: 1 9 | chunks-per-iteration: 256 10 | info-interval-millis: 100 11 | chunk-scans: 12 | mode: "ALWAYS" 13 | player-tracker-interval-ticks: 5 14 | notification: 15 | type: "BOSSBAR" 16 | bossbar: 17 | color: "BLUE" 18 | overlay: "NOTCHED_10" 19 | flags: [] 20 | duration-ticks: 60 21 | actionbar: 22 | segments: 50 23 | progress-sequence: "|" 24 | done-color: "" 25 | total-color: "" 26 | separator: " " 27 | area-scan-notifications: 28 | enabled: true 29 | permission: "" 30 | spigot: 31 | entity-tracker-interval-ticks: 10 32 | apply-piston-limits: true 33 | pagination-results-per-page: 6 34 | disabled-listeners: 35 | - "BlockFromToEvent" 36 | - "FluidLevelChangeEvent" 37 | - "SpongeAbsorbEvent" 38 | - "LeavesDecayEvent" 39 | listener-priorities: 40 | BlockPlaceEvent: LOWEST 41 | HangingPlaceEvent: LOWEST 42 | EntityPlaceEvent: LOWEST 43 | BlockPistonExtendEvent: LOWEST 44 | BlockPistonRetractEvent: LOWEST 45 | PlayerBucketEmptyEvent: LOWEST 46 | redstone-update-limiter: 47 | enabled: false 48 | limit: 50000 49 | aggregate-ticks: 10 50 | aggregate-size: 30 51 | block-outside-region: false 52 | -------------------------------------------------------------------------------- /Insights/src/main/resources/messages.yml: -------------------------------------------------------------------------------- 1 | messages: 2 | prefix: "Insights » " 3 | update-available: "Version of Insights is now available! Info: " 4 | configs-reloaded: "All configurations have been reloaded!" 5 | area-scan-started: "Please wait while we scan this ..." 6 | area-scan-queued: "This is currently queued for scanning..." 7 | area-scan-completed: "Scan completed!" 8 | limit-reached: "You have reached the limit of x in this . Please remove some." 9 | limit-disallowed-placement: "You can't place in this !" 10 | limit-notification: ": /" 11 | scan: 12 | start: "Scanning chunks..." 13 | already-scanning: "Please wait for your other scan to finish before requesting another one." 14 | finish: 15 | header: "----------------=[ Insights Scan ]=---------------" 16 | chunk-format: "Chunk @ , " 17 | chunk-hover: "Click to teleport!" 18 | format: " : " 19 | footer: |- 20 | Scanned through a total of chunks with a total of blocks and entities. Took