From 9fe87bed7b2cc44caf4ee72c64eb217e3e7351b5 Mon Sep 17 00:00:00 2001 From: phinner <62483793+phinner@users.noreply.github.com> Date: Sat, 19 Oct 2024 21:02:01 +0200 Subject: [PATCH] fix: Fix compilation issues --- HEADER.md | 23 -- HEADER.txt | 25 +++ build.gradle.kts | 2 +- .../kotlin/com/xpdustry/nohorny/NoHornyAPI.kt | 4 +- .../com/xpdustry/nohorny/NoHornyAutoBan.kt | 10 +- .../com/xpdustry/nohorny/NoHornyCache.kt | 8 +- .../com/xpdustry/nohorny/NoHornyConfig.kt | 6 +- .../com/xpdustry/nohorny/NoHornyImage.kt | 6 +- .../com/xpdustry/nohorny/NoHornyListener.kt | 1 - .../com/xpdustry/nohorny/NoHornyLogger.kt | 96 +++++--- .../com/xpdustry/nohorny/NoHornyPlugin.kt | 19 +- .../com/xpdustry/nohorny/NoHornyTracker.kt | 212 +++++++++++------- .../nohorny/analyzer/ImageAnalyzer.kt | 8 +- .../nohorny/analyzer/ImageAnalyzerEvent.kt | 2 +- .../analyzer/ModerateContentAnalyzer.kt | 99 -------- .../nohorny/analyzer/SightEngineAnalyzer.kt | 23 +- .../extension/BufferedImageExtensions.kt | 16 +- .../nohorny/extension/MindustryExtensions.kt | 11 +- .../nohorny/extension/OkHttpExtensions.kt | 24 +- .../nohorny/extension/SchedulerExtensions.kt | 23 +- .../com/xpdustry/nohorny/geometry/Cluster.kt | 3 +- .../nohorny/geometry/ClusterManager.kt | 40 +++- .../xpdustry/nohorny/geometry/Rectangle.kt | 6 +- .../nohorny/geometry/ClusterManagerTest.kt | 7 +- 24 files changed, 360 insertions(+), 314 deletions(-) delete mode 100644 HEADER.md create mode 100644 HEADER.txt delete mode 100644 src/main/kotlin/com/xpdustry/nohorny/analyzer/ModerateContentAnalyzer.kt diff --git a/HEADER.md b/HEADER.md deleted file mode 100644 index ab3a204..0000000 --- a/HEADER.md +++ /dev/null @@ -1,23 +0,0 @@ -This file is part of NoHorny. The plugin securing your server against nsfw builds. - -MIT License - -Copyright (c) 2024 Xpdustry - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/HEADER.txt b/HEADER.txt new file mode 100644 index 0000000..e1ff26e --- /dev/null +++ b/HEADER.txt @@ -0,0 +1,25 @@ +/* + * This file is part of NoHorny. The plugin securing your server against nsfw builds. + * + * MIT License + * + * Copyright (c) 2024 Xpdustry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index afac59e..b609129 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -93,7 +93,7 @@ indra { spotless { kotlin { ktlint() - licenseHeaderFile(rootProject.file("HEADER.md")) + licenseHeaderFile(rootProject.file("HEADER.txt")) } kotlinGradle { ktlint() diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyAPI.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyAPI.kt index b346945..44d475d 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyAPI.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyAPI.kt @@ -25,10 +25,12 @@ */ package com.xpdustry.nohorny +import mindustry.Vars + public interface NoHornyAPI { public fun setCache(cache: NoHornyCache) public companion object { - @JvmStatic public fun get(): NoHornyAPI = NoHornyPlugin.INSTANCE + @JvmStatic public fun get(): NoHornyAPI = Vars.mods.getMod(NoHornyPlugin::class.java).main as NoHornyAPI } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyAutoBan.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyAutoBan.kt index d505b48..5cff218 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyAutoBan.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyAutoBan.kt @@ -38,18 +38,19 @@ import mindustry.world.blocks.logic.LogicBlock import mindustry.world.blocks.logic.LogicDisplay internal class NoHornyAutoBan(private val plugin: NoHornyPlugin) : NoHornyListener { - override fun onInit() { onEvent { (result, cluster, _, author) -> if (result.rating == ImageAnalyzer.Rating.UNSAFE && plugin.config.autoBan && - author != null) { + author != null + ) { for (player in Groups.player) { if (player.uuid() == author.uuid || player.ip() == author.address.hostAddress) { Vars.netServer.admins.banPlayer(player.uuid()) player.kick("[scarlet]You have been banned for building a NSFW building.") Call.sendMessage( - "[pink]NoHorny: [white]${player.plainName()} has been banned for building a NSFW building.") + "[pink]NoHorny: [white]${player.plainName()} has been banned for building a NSFW building.", + ) } } cluster.blocks @@ -65,7 +66,8 @@ internal class NoHornyAutoBan(private val plugin: NoHornyPlugin) : NoHornyListen val building = Vars.world.build(point.x, point.y) if (building is LogicDisplay.LogicDisplayBuild || building is LogicBlock.LogicBuild || - building is CanvasBlock.CanvasBuild) { + building is CanvasBlock.CanvasBuild + ) { Vars.world.tile(point.x, point.y)?.setNet(Blocks.air) } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyCache.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyCache.kt index 306f73b..aad5305 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyCache.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyCache.kt @@ -33,25 +33,25 @@ import java.util.concurrent.CompletableFuture public interface NoHornyCache { public fun getResult( cluster: Cluster, - image: BufferedImage + image: BufferedImage, ): CompletableFuture public fun putResult( cluster: Cluster, image: BufferedImage, - result: ImageAnalyzer.Result + result: ImageAnalyzer.Result, ) public data object None : NoHornyCache { override fun getResult( cluster: Cluster, - image: BufferedImage + image: BufferedImage, ): CompletableFuture = CompletableFuture.completedFuture(null) override fun putResult( cluster: Cluster, image: BufferedImage, - result: ImageAnalyzer.Result + result: ImageAnalyzer.Result, ): Unit = Unit } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyConfig.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyConfig.kt index b13ac29..9b0c3d6 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyConfig.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyConfig.kt @@ -38,7 +38,7 @@ internal data class NoHornyConfig( val minimumCanvasClusterSize: Int = 9, val minimumProcessorCount: Int = 5, val processorSearchRadius: Int = 10, - val alwaysProcess: Boolean = false + val alwaysProcess: Boolean = false, ) { init { require(minimumInstructionCount >= 1) { "minimumInstructionCount cannot be lower than 1" } @@ -58,7 +58,7 @@ internal data class NoHornyConfig( val sightEngineSecret: Secret, val unsafeThreshold: Float = 0.55F, val warningThreshold: Float = 0.4F, - val kinds: List = listOf(ImageAnalyzer.Kind.NUDITY) + val kinds: List = listOf(ImageAnalyzer.Kind.NUDITY), ) : Analyzer { init { require(unsafeThreshold >= 0) { "unsafeThreshold cannot be lower than 0" } @@ -67,8 +67,6 @@ internal data class NoHornyConfig( } } - data class ModerateContent(val moderateContentToken: Secret) : Analyzer - data class Fallback(val primary: Analyzer, val secondary: Analyzer) : Analyzer } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyImage.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyImage.kt index add9626..7fa99f8 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyImage.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyImage.kt @@ -37,14 +37,14 @@ public sealed interface NoHornyImage { public data class Canvas( override val resolution: Int, public val pixels: IntIntMap, - public val author: Author? + public val author: Author?, ) : NoHornyImage { override fun copy(): Canvas = Canvas(resolution, IntIntMap(pixels), author) } public data class Display( override val resolution: Int, - public val processors: MutableMap + public val processors: MutableMap, ) : NoHornyImage { override fun copy(): Display = Display(resolution, processors.toMutableMap()) } @@ -62,7 +62,7 @@ public sealed interface NoHornyImage { val x2: Int, val y2: Int, val x3: Int, - val y3: Int + val y3: Int, ) : Instruction } diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyListener.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyListener.kt index af111e4..3236dab 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyListener.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyListener.kt @@ -28,7 +28,6 @@ package com.xpdustry.nohorny import arc.util.CommandHandler internal interface NoHornyListener { - fun onInit() = Unit fun onServerCommandsRegistration(handler: CommandHandler) = Unit diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyLogger.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyLogger.kt index 3cce64b..12368d4 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyLogger.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyLogger.kt @@ -30,28 +30,54 @@ import mindustry.Vars import org.slf4j.LoggerFactory internal interface NoHornyLogger { - - fun error(text: String, vararg args: Any = emptyArray()) - - fun info(text: String, vararg args: Any = emptyArray()) - - fun debug(text: String, vararg args: Any = emptyArray()) - - fun trace(text: String, vararg args: Any = emptyArray()) + fun error( + text: String, + vararg args: Any = emptyArray(), + ) + + fun info( + text: String, + vararg args: Any = emptyArray(), + ) + + fun debug( + text: String, + vararg args: Any = emptyArray(), + ) + + fun trace( + text: String, + vararg args: Any = emptyArray(), + ) companion object : NoHornyLogger by findImplementation() object ARC : NoHornyLogger { - - override fun error(text: String, vararg args: Any) = log(Log.LogLevel.err, text, args) - - override fun info(text: String, vararg args: Any) = log(Log.LogLevel.info, text, args) - - override fun debug(text: String, vararg args: Any) = log(Log.LogLevel.debug, text, args) - - override fun trace(text: String, vararg args: Any) = log(Log.LogLevel.debug, text, args) - - private fun log(level: Log.LogLevel, text: String, args: Array) { + override fun error( + text: String, + vararg args: Any, + ) = log(Log.LogLevel.err, text, args) + + override fun info( + text: String, + vararg args: Any, + ) = log(Log.LogLevel.info, text, args) + + override fun debug( + text: String, + vararg args: Any, + ) = log(Log.LogLevel.debug, text, args) + + override fun trace( + text: String, + vararg args: Any, + ) = log(Log.LogLevel.debug, text, args) + + private fun log( + level: Log.LogLevel, + text: String, + args: Array, + ) { val throwable: Throwable? val arguments: Array if (args.lastOrNull() is Throwable) { @@ -69,21 +95,35 @@ internal interface NoHornyLogger { } object SLF4J : NoHornyLogger { - private val logger = LoggerFactory.getLogger(NoHornyPlugin::class.java) - override fun error(text: String, vararg args: Any) = logger.error(text, *args) - - override fun info(text: String, vararg args: Any) = logger.info(text, *args) - - override fun debug(text: String, vararg args: Any) = logger.debug(text, *args) - - override fun trace(text: String, vararg args: Any) = logger.trace(text, *args) + override fun error( + text: String, + vararg args: Any, + ) = logger.error(text, *args) + + override fun info( + text: String, + vararg args: Any, + ) = logger.info(text, *args) + + override fun debug( + text: String, + vararg args: Any, + ) = logger.debug(text, *args) + + override fun trace( + text: String, + vararg args: Any, + ) = logger.trace(text, *args) } } private fun findImplementation() = if (Vars.mods.getMod("distributor-core") != null || - Vars.mods.getMod("distributor-logging") != null) + Vars.mods.getMod("distributor-logging") != null + ) { NoHornyLogger.SLF4J - else NoHornyLogger.ARC + } else { + NoHornyLogger.ARC + } diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyPlugin.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyPlugin.kt index 0ffea9b..f8a2570 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyPlugin.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyPlugin.kt @@ -34,8 +34,11 @@ import com.sksamuel.hoplite.addPathSource import com.xpdustry.nohorny.analyzer.DebugImageAnalyzer import com.xpdustry.nohorny.analyzer.FallbackAnalyzer import com.xpdustry.nohorny.analyzer.ImageAnalyzer -import com.xpdustry.nohorny.analyzer.ModerateContentAnalyzer import com.xpdustry.nohorny.analyzer.SightEngineAnalyzer +import mindustry.Vars +import mindustry.mod.Plugin +import okhttp3.Dispatcher +import okhttp3.OkHttpClient import java.nio.file.Files import java.nio.file.Path import java.util.concurrent.Executors @@ -45,13 +48,8 @@ import java.util.concurrent.atomic.AtomicInteger import kotlin.io.path.notExists import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration -import mindustry.Vars -import mindustry.mod.Plugin -import okhttp3.Dispatcher -import okhttp3.OkHttpClient public class NoHornyPlugin : Plugin(), NoHornyAPI { - private val directory: Path get() = Vars.modDirectory.child("nohorny").file().toPath() @@ -82,7 +80,6 @@ public class NoHornyPlugin : Plugin(), NoHornyAPI { internal var cache: NoHornyCache = NoHornyCache.None init { - INSTANCE = this Files.createDirectories(directory) listeners += NoHornyTracker(this) listeners += NoHornyAutoBan(this) @@ -101,7 +98,8 @@ public class NoHornyPlugin : Plugin(), NoHornyAPI { EXECUTOR.shutdownNow() } } - }) + }, + ) } override fun registerServerCommands(handler: CommandHandler) { @@ -144,7 +142,6 @@ public class NoHornyPlugin : Plugin(), NoHornyAPI { when (config) { is NoHornyConfig.Analyzer.None -> ImageAnalyzer.None is NoHornyConfig.Analyzer.Debug -> DebugImageAnalyzer(directory.resolve("debug")) - is NoHornyConfig.Analyzer.ModerateContent -> ModerateContentAnalyzer(config, http) is NoHornyConfig.Analyzer.SightEngine -> SightEngineAnalyzer(config, http) is NoHornyConfig.Analyzer.Fallback -> FallbackAnalyzer(createAnalyzer(config.primary), createAnalyzer(config.secondary)) @@ -153,12 +150,10 @@ public class NoHornyPlugin : Plugin(), NoHornyAPI { private object NoHornyThreadFactory : ThreadFactory { private val count = AtomicInteger(0) - override fun newThread(runnable: Runnable) = - Thread(runnable, "nohorny-worker-${count.incrementAndGet()}").apply { isDaemon = true } + override fun newThread(runnable: Runnable) = Thread(runnable, "nohorny-worker-${count.incrementAndGet()}").apply { isDaemon = true } } internal companion object { - @JvmStatic internal lateinit var INSTANCE: NoHornyPlugin @JvmStatic internal val EXECUTOR = Executors.newScheduledThreadPool(4, NoHornyThreadFactory) } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/NoHornyTracker.kt b/src/main/kotlin/com/xpdustry/nohorny/NoHornyTracker.kt index ed9e4be..1f93b42 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/NoHornyTracker.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/NoHornyTracker.kt @@ -32,18 +32,15 @@ import arc.struct.IntIntMap import arc.struct.IntSet import arc.util.CommandHandler import com.xpdustry.nohorny.analyzer.ImageAnalyzerEvent -import com.xpdustry.nohorny.extension.* +import com.xpdustry.nohorny.extension.onBuildingLifecycleEvent +import com.xpdustry.nohorny.extension.onEvent +import com.xpdustry.nohorny.extension.render import com.xpdustry.nohorny.extension.rx import com.xpdustry.nohorny.extension.ry +import com.xpdustry.nohorny.extension.schedule import com.xpdustry.nohorny.geometry.Cluster import com.xpdustry.nohorny.geometry.ClusterManager import com.xpdustry.nohorny.geometry.ImmutablePoint -import java.net.InetAddress -import java.time.Instant -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit -import kotlin.time.Duration.Companion.seconds import mindustry.Vars import mindustry.game.EventType import mindustry.gen.Call @@ -54,9 +51,14 @@ import mindustry.logic.LExecutor.DrawFlushI import mindustry.world.blocks.logic.CanvasBlock import mindustry.world.blocks.logic.LogicBlock import mindustry.world.blocks.logic.LogicDisplay +import java.net.InetAddress +import java.time.Instant +import java.util.PriorityQueue +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import kotlin.time.Duration.Companion.seconds internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListener { - private val processors = mutableMapOf() private val displays = ClusterManager.create() private val displayProcessingQueue = PriorityQueue() @@ -91,7 +93,11 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen } } - private fun renderCluster(player: Player, manager: ClusterManager<*>, color: Color) { + private fun renderCluster( + player: Player, + manager: ClusterManager<*>, + color: Color, + ) { for (cluster in manager.clusters) { for (block in cluster.blocks) { Call.label( @@ -119,64 +125,76 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen override fun onClientCommandsRegistration(handler: CommandHandler) { handler.register( - "nohorny-tracker-debug", "Enable debug mode of the nohorny tracker.") { _, player -> - if (debug.add(player.id)) { - player.sendMessage("Tracker debug mode is now enabled") - } else { - debug.remove(player.id) - player.sendMessage("Tracker debug mode is now disabled") - } + "nohorny-tracker-debug", + "Enable debug mode of the nohorny tracker.", + ) { _, player -> + if (debug.add(player.id)) { + player.sendMessage("Tracker debug mode is now enabled") + } else { + debug.remove(player.id) + player.sendMessage("Tracker debug mode is now disabled") } + } } @Suppress("UNCHECKED_CAST") private fun startProcessing( manager: ClusterManager, queue: PriorityQueue, - name: String - ) = - schedule(async = false, delay = 1.seconds, repeat = 1.seconds) { - val element = queue.peek() - if (element == null || element.instant > Instant.now()) return@schedule - queue.remove() - val cluster = manager.getClusterByIdentifier(element.identifier) ?: return@schedule - val copy = - cluster.copy( - blocks = cluster.blocks.map { it.copy(payload = it.payload.copy() as T) }) - - NoHornyLogger.trace("Begin processing of {} cluster {}", name, copy.identifier) - - schedule(async = true) { - val image = render(copy) - plugin.cache - .getResult(copy, image) - .thenCompose { cached -> - if (cached != null) { - CompletableFuture.completedFuture(cached to true) - } else { - plugin.analyzer.analyse(image).thenApply { it to false } - } + name: String, + ) = schedule(async = false, delay = 1.seconds, repeat = 1.seconds) { + val element = queue.peek() + if (element == null || element.instant > Instant.now()) return@schedule + queue.remove() + val cluster = manager.getClusterByIdentifier(element.identifier) ?: return@schedule + val copy = + cluster.copy( + blocks = cluster.blocks.map { it.copy(payload = it.payload.copy() as T) }, + ) + + NoHornyLogger.trace("Begin processing of {} cluster {}", name, copy.identifier) + + schedule(async = true) { + val image = render(copy) + plugin.cache + .getResult(copy, image) + .thenCompose { cached -> + if (cached != null) { + CompletableFuture.completedFuture(cached to true) + } else { + plugin.analyzer.analyse(image).thenApply { it to false } } - .orTimeout(10L, TimeUnit.SECONDS) - .whenComplete { (result, cached), error -> - if (error != null) { - NoHornyLogger.error( - "Failed to verify cluster {}", copy.identifier, error) - return@whenComplete - } - - if (!cached) { - plugin.cache.putResult(copy, image, result) - } - - NoHornyLogger.trace( - "Processed {} cluster {}, posting event", name, copy.identifier) - Core.app.post { Events.fire(ImageAnalyzerEvent(result, copy, image)) } + } + .orTimeout(10L, TimeUnit.SECONDS) + .whenComplete { (result, cached), error -> + if (error != null) { + NoHornyLogger.error( + "Failed to verify cluster {}", + copy.identifier, + error, + ) + return@whenComplete } - } + + if (!cached) { + plugin.cache.putResult(copy, image, result) + } + + NoHornyLogger.trace( + "Processed {} cluster {}, posting event", + name, + copy.identifier, + ) + Core.app.post { Events.fire(ImageAnalyzerEvent(result, copy, image)) } + } } + } - private fun addCanvas(canvas: CanvasBlock.CanvasBuild, player: Player?, new: Boolean) { + private fun addCanvas( + canvas: CanvasBlock.CanvasBuild, + player: Player?, + new: Boolean, + ) { handleCanvasesModifications( canvases.addBlock( Cluster.Block( @@ -186,17 +204,24 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen NoHornyImage.Canvas( (canvas.block as CanvasBlock).canvasSize, readCanvas(canvas), - player?.asAuthor()))), - shouldReschedule = new) + player?.asAuthor(), + ), + ), + ), + shouldReschedule = new, + ) } - private fun removeCanvas(x: Int, y: Int) { + private fun removeCanvas( + x: Int, + y: Int, + ) { handleCanvasesModifications(canvases.removeBlock(x, y), true) } private fun handleCanvasesModifications( modifications: Iterable, - shouldReschedule: Boolean + shouldReschedule: Boolean, ) { for (modified in modifications) { if (canvasProcessingQueue.removeIf { it.identifier == modified }) { @@ -204,30 +229,42 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen } val cluster = canvases.getClusterByIdentifier(modified) ?: return if (cluster.blocks.size >= plugin.config.minimumCanvasClusterSize && - (plugin.config.alwaysProcess || shouldReschedule)) { + (plugin.config.alwaysProcess || shouldReschedule) + ) { NoHornyLogger.trace("Scheduled processing of canvas cluster {}", modified) canvasProcessingQueue.add( ProcessingTask( modified, Instant.now() - .plusMillis(plugin.config.processingDelay.inWholeMilliseconds))) + .plusMillis(plugin.config.processingDelay.inWholeMilliseconds), + ), + ) } } } - private fun addDisplay(display: LogicDisplay.LogicDisplayBuild, player: Player?, new: Boolean) { + private fun addDisplay( + display: LogicDisplay.LogicDisplayBuild, + player: Player?, + new: Boolean, + ) { val resolution = (display.block as LogicDisplay).displaySize val map = mutableMapOf() val block = Cluster.Block( - display.rx, display.ry, display.block.size, NoHornyImage.Display(resolution, map)) + display.rx, + display.ry, + display.block.size, + NoHornyImage.Display(resolution, map), + ) for ((position, data) in processors) { if (display.within( - position.x.toFloat() * Vars.tilesize, - position.y.toFloat() * Vars.tilesize, - plugin.config.processorSearchRadius.toFloat() * Vars.tilesize)) { - + position.x.toFloat() * Vars.tilesize, + position.y.toFloat() * Vars.tilesize, + plugin.config.processorSearchRadius.toFloat() * Vars.tilesize, + ) + ) { val (processor, links) = data for (link in links) { if (block.contains(link.x, link.y)) { @@ -240,7 +277,10 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen handleDisplaysModifications(displays.addBlock(block)) } - private fun removeDisplay(x: Int, y: Int) { + private fun removeDisplay( + x: Int, + y: Int, + ) { handleDisplaysModifications(displays.removeBlock(x, y)) } @@ -251,18 +291,25 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen } val cluster = displays.getClusterByIdentifier(modified) ?: return if (cluster.blocks.sumOf { it.payload.processors.size } >= - plugin.config.minimumProcessorCount) { + plugin.config.minimumProcessorCount + ) { NoHornyLogger.trace("Scheduled processing of display cluster {}", modified) displayProcessingQueue.add( ProcessingTask( modified, Instant.now() - .plusMillis(plugin.config.processingDelay.inWholeMilliseconds))) + .plusMillis(plugin.config.processingDelay.inWholeMilliseconds), + ), + ) } } } - private fun addProcessor(processor: LogicBlock.LogicBuild, player: Player?, new: Boolean) { + private fun addProcessor( + processor: LogicBlock.LogicBuild, + player: Player?, + new: Boolean, + ) { if (processor.executor.instructions.any { it is DrawFlushI }) { val instructions = readInstructions(processor.executor) val links = processor.links.select { it.active }.map { ImmutablePoint(it.x, it.y) } @@ -270,7 +317,9 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen if (instructions.size >= plugin.config.minimumInstructionCount && !links.isEmpty) { val data = ProcessorWithLinks( - NoHornyImage.Processor(instructions, player?.asAuthor()), links.list()) + NoHornyImage.Processor(instructions, player?.asAuthor()), + links.list(), + ) val point = ImmutablePoint(processor.tileX(), processor.tileY()) processors[point] = data @@ -282,7 +331,10 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen } } - private fun removeProcessor(x: Int, y: Int) { + private fun removeProcessor( + x: Int, + y: Int, + ) { val point = ImmutablePoint(x, y) processors.remove(point) val modified = mutableSetOf() @@ -354,14 +406,20 @@ internal class NoHornyTracker(private val plugin: NoHornyPlugin) : NoHornyListen } // Anuke black magic - private fun getByte(block: CanvasBlock, data: ByteArray, bitOffset: Int): Int { + private fun getByte( + block: CanvasBlock, + data: ByteArray, + bitOffset: Int, + ): Int { var result = 0 for (i in 0 until block.bitsPerPixel) { val word = i + bitOffset ushr 3 result = result or - ((if (data[word].toInt() and (1 shl (i + bitOffset and 7)) == 0) 0 else 1) shl - i) + ( + (if (data[word].toInt() and (1 shl (i + bitOffset and 7)) == 0) 0 else 1) shl + i + ) } return result } diff --git a/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzer.kt b/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzer.kt index 359a8c6..3c71af4 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzer.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzer.kt @@ -29,12 +29,10 @@ import java.awt.image.BufferedImage import java.util.concurrent.CompletableFuture public interface ImageAnalyzer { - public fun analyse(image: BufferedImage): CompletableFuture public object None : ImageAnalyzer { - override fun analyse(image: BufferedImage): CompletableFuture = - CompletableFuture.completedFuture(Result.EMPTY) + override fun analyse(image: BufferedImage): CompletableFuture = CompletableFuture.completedFuture(Result.EMPTY) } public data class Result(val rating: Rating, val details: Map) { @@ -45,12 +43,12 @@ public interface ImageAnalyzer { public enum class Kind { NUDITY, - GORE + GORE, } public enum class Rating { SAFE, WARNING, - UNSAFE + UNSAFE, } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzerEvent.kt b/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzerEvent.kt index 1022d44..df481ce 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzerEvent.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/analyzer/ImageAnalyzerEvent.kt @@ -32,7 +32,7 @@ import java.awt.image.BufferedImage public data class ImageAnalyzerEvent( val result: ImageAnalyzer.Result, val cluster: Cluster, - val image: BufferedImage + val image: BufferedImage, ) { public operator fun component4(): NoHornyImage.Author? = author diff --git a/src/main/kotlin/com/xpdustry/nohorny/analyzer/ModerateContentAnalyzer.kt b/src/main/kotlin/com/xpdustry/nohorny/analyzer/ModerateContentAnalyzer.kt deleted file mode 100644 index eeb9850..0000000 --- a/src/main/kotlin/com/xpdustry/nohorny/analyzer/ModerateContentAnalyzer.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is part of NoHorny. The plugin securing your server against nsfw builds. - * - * MIT License - * - * Copyright (c) 2024 Xpdustry - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.xpdustry.nohorny.analyzer - -import com.xpdustry.nohorny.NoHornyConfig -import com.xpdustry.nohorny.NoHornyLogger -import com.xpdustry.nohorny.extension.toCompletableFuture -import com.xpdustry.nohorny.extension.toJpgByteArray -import com.xpdustry.nohorny.extension.toJsonObject -import java.awt.image.BufferedImage -import java.io.IOException -import java.util.concurrent.CompletableFuture -import kotlinx.serialization.json.* -import okhttp3.* -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.RequestBody.Companion.toRequestBody - -internal class ModerateContentAnalyzer( - private val config: NoHornyConfig.Analyzer.ModerateContent, - private val http: OkHttpClient -) : ImageAnalyzer { - override fun analyse(image: BufferedImage): CompletableFuture = - http - .newCall( - Request.Builder() - .url("https://api.moderatecontent.com/moderate/") - .post( - MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("key", config.moderateContentToken.value) - .addFormDataPart( - "file", - "image.jpg", - image - .toJpgByteArray() - .toRequestBody("image/jpeg".toMediaTypeOrNull())) - .build()) - .build()) - .toCompletableFuture() - .thenApply(Response::toJsonObject) - .thenCompose { json -> - NoHornyLogger.debug("ModerateContent response: {}", json) - - val code = json["error_code"]!!.jsonPrimitive.int - if (code != 0) { - return@thenCompose CompletableFuture.failedFuture( - IOException( - "ModerateContent API returned an error: ${json["error"]!!.jsonPrimitive.content} ($code)")) - } - - val predictions = - json["predictions"]!!.jsonObject.mapValues { it.value.jsonPrimitive.float } - - val rating = - when (val label = json["rating_label"]!!.jsonPrimitive.content) { - EVERYONE_LABEL -> ImageAnalyzer.Rating.SAFE - TEEN_LABEL -> ImageAnalyzer.Rating.WARNING - ADULT_LABEL -> ImageAnalyzer.Rating.UNSAFE - else -> { - return@thenCompose CompletableFuture.failedFuture( - IOException("Unknown label: $label")) - } - } - - return@thenCompose CompletableFuture.completedFuture( - ImageAnalyzer.Result( - rating, - mapOf(ImageAnalyzer.Kind.NUDITY to predictions[ADULT_LABEL]!! / 100F))) - } - - companion object { - private const val EVERYONE_LABEL = "everyone" - private const val TEEN_LABEL = "teen" - private const val ADULT_LABEL = "adult" - } -} diff --git a/src/main/kotlin/com/xpdustry/nohorny/analyzer/SightEngineAnalyzer.kt b/src/main/kotlin/com/xpdustry/nohorny/analyzer/SightEngineAnalyzer.kt index 785222b..3e00214 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/analyzer/SightEngineAnalyzer.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/analyzer/SightEngineAnalyzer.kt @@ -30,9 +30,6 @@ import com.xpdustry.nohorny.NoHornyLogger import com.xpdustry.nohorny.extension.toCompletableFuture import com.xpdustry.nohorny.extension.toJpgByteArray import com.xpdustry.nohorny.extension.toJsonObject -import java.awt.image.BufferedImage -import java.io.IOException -import java.util.concurrent.CompletableFuture import kotlinx.serialization.json.float import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive @@ -42,10 +39,13 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response +import java.awt.image.BufferedImage +import java.io.IOException +import java.util.concurrent.CompletableFuture internal class SightEngineAnalyzer( private val config: NoHornyConfig.Analyzer.SightEngine, - private val http: OkHttpClient + private val http: OkHttpClient, ) : ImageAnalyzer { override fun analyse(image: BufferedImage): CompletableFuture { val request = @@ -60,11 +60,13 @@ internal class SightEngineAnalyzer( ImageAnalyzer.Kind.NUDITY -> "nudity-2.0" ImageAnalyzer.Kind.GORE -> "gore" } - }) + }, + ) .addFormDataPart( "media", "image.jpg", - image.toJpgByteArray().toRequestBody("image/jpeg".toMediaTypeOrNull(), 0)) + image.toJpgByteArray().toRequestBody("image/jpeg".toMediaTypeOrNull(), 0), + ) .build() return http @@ -72,7 +74,8 @@ internal class SightEngineAnalyzer( Request.Builder() .url("https://api.sightengine.com/1.0/check.json") .post(request) - .build()) + .build(), + ) .toCompletableFuture() .thenApply(Response::toJsonObject) .thenCompose { json -> @@ -80,7 +83,8 @@ internal class SightEngineAnalyzer( if (json["status"]!!.jsonPrimitive.content != "success") { return@thenCompose CompletableFuture.failedFuture( - IOException("SightEngine API returned error: ${json["error"]}")) + IOException("SightEngine API returned error: ${json["error"]}"), + ) } val results = mutableMapOf() @@ -107,7 +111,8 @@ internal class SightEngineAnalyzer( } return@thenCompose CompletableFuture.completedFuture( - ImageAnalyzer.Result(rating, results)) + ImageAnalyzer.Result(rating, results), + ) } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/extension/BufferedImageExtensions.kt b/src/main/kotlin/com/xpdustry/nohorny/extension/BufferedImageExtensions.kt index b4f831d..fa4d49b 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/extension/BufferedImageExtensions.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/extension/BufferedImageExtensions.kt @@ -46,7 +46,10 @@ internal fun BufferedImage.toJpgByteArray(): ByteArray { internal fun render(cluster: Cluster): BufferedImage { val image = BufferedImage( - cluster.w * RES_PER_BLOCK, cluster.h * RES_PER_BLOCK, BufferedImage.TYPE_INT_ARGB) + cluster.w * RES_PER_BLOCK, + cluster.h * RES_PER_BLOCK, + BufferedImage.TYPE_INT_ARGB, + ) val graphics = image.graphics graphics.color = Color(0, 0, 0, 0) graphics.fillRect(0, 0, image.width, image.height) @@ -78,7 +81,10 @@ private fun createImage(image: NoHornyImage): BufferedImage { is NoHornyImage.Canvas -> { for (pixel in image.pixels) { output.setRGB( - pixel.key % image.resolution, pixel.key / image.resolution, pixel.value) + pixel.key % image.resolution, + pixel.key / image.resolution, + pixel.value, + ) } output = invertYAxis(output) } @@ -95,7 +101,11 @@ private fun createImage(image: NoHornyImage): BufferedImage { output.setRGB(instruction.x, instruction.y, graphics.color.rgb) } else { graphics.fillRect( - instruction.x, instruction.y, instruction.w, instruction.h) + instruction.x, + instruction.y, + instruction.w, + instruction.h, + ) } } is NoHornyImage.Instruction.Triangle -> { diff --git a/src/main/kotlin/com/xpdustry/nohorny/extension/MindustryExtensions.kt b/src/main/kotlin/com/xpdustry/nohorny/extension/MindustryExtensions.kt index f1daf97..ca70d27 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/extension/MindustryExtensions.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/extension/MindustryExtensions.kt @@ -27,12 +27,12 @@ package com.xpdustry.nohorny.extension import arc.Events import com.xpdustry.nohorny.NoHornyLogger -import java.util.function.Consumer -import kotlin.reflect.jvm.jvmName import mindustry.game.EventType import mindustry.gen.Building import mindustry.gen.Player import mindustry.world.blocks.ConstructBlock +import java.util.function.Consumer +import kotlin.reflect.jvm.jvmName internal val Building.rx: Int get() = tileX() + block.sizeOffset @@ -42,7 +42,7 @@ internal val Building.ry: Int internal inline fun onBuildingLifecycleEvent( crossinline insert: (T, Player?, new: Boolean) -> Unit, - crossinline remove: (Int, Int) -> Unit + crossinline remove: (Int, Int) -> Unit, ) { onEvent { event -> var buildings = listOf(event.tile.build) @@ -89,6 +89,9 @@ internal inline fun onEvent(consumer: Consumer) = consumer.accept(event) } catch (e: Throwable) { NoHornyLogger.error( - "An error occurred while handling event {}", event::class.jvmName, e) + "An error occurred while handling event {}", + event::class.jvmName, + e, + ) } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/extension/OkHttpExtensions.kt b/src/main/kotlin/com/xpdustry/nohorny/extension/OkHttpExtensions.kt index 5356ea0..e01ee04 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/extension/OkHttpExtensions.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/extension/OkHttpExtensions.kt @@ -25,30 +25,38 @@ */ package com.xpdustry.nohorny.extension -import java.io.IOException -import java.util.concurrent.CompletableFuture import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import okhttp3.Call import okhttp3.Callback import okhttp3.Response +import java.io.IOException +import java.util.concurrent.CompletableFuture internal fun Call.toCompletableFuture(): CompletableFuture { val future = CompletableFuture() enqueue( object : Callback { - override fun onResponse(call: Call, response: Response) { + override fun onResponse( + call: Call, + response: Response, + ) { future.complete(response) } - override fun onFailure(call: Call, e: IOException) { + override fun onFailure( + call: Call, + e: IOException, + ) { future.completeExceptionally(e) } - }) + }, + ) return future } -internal fun Response.toJsonObject(): JsonObject = use { - Json.parseToJsonElement(it.body!!.string()).jsonObject -} +internal fun Response.toJsonObject(): JsonObject = + use { + Json.parseToJsonElement(it.body!!.string()).jsonObject + } diff --git a/src/main/kotlin/com/xpdustry/nohorny/extension/SchedulerExtensions.kt b/src/main/kotlin/com/xpdustry/nohorny/extension/SchedulerExtensions.kt index ebecc86..3da6a2d 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/extension/SchedulerExtensions.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/extension/SchedulerExtensions.kt @@ -35,24 +35,29 @@ internal fun schedule( async: Boolean, delay: Duration? = null, repeat: Duration? = null, - task: Runnable + task: Runnable, ) { - val wrapper = Runnable { - try { - task.run() - } catch (throwable: Throwable) { - NoHornyLogger.error("An error occurred while executing a scheduled task", throwable) + val wrapper = + Runnable { + try { + task.run() + } catch (throwable: Throwable) { + NoHornyLogger.error("An error occurred while executing a scheduled task", throwable) + } } - } val runnable = Runnable { if (async) wrapper.run() else Core.app.post(wrapper) } if (repeat != null) { NoHornyPlugin.EXECUTOR.scheduleWithFixedDelay( runnable, delay?.inWholeMilliseconds ?: 0L, repeat.inWholeMilliseconds, - TimeUnit.MILLISECONDS) + TimeUnit.MILLISECONDS, + ) } else { NoHornyPlugin.EXECUTOR.schedule( - runnable, delay?.inWholeMilliseconds ?: 0L, TimeUnit.MILLISECONDS) + runnable, + delay?.inWholeMilliseconds ?: 0L, + TimeUnit.MILLISECONDS, + ) } } diff --git a/src/main/kotlin/com/xpdustry/nohorny/geometry/Cluster.kt b/src/main/kotlin/com/xpdustry/nohorny/geometry/Cluster.kt index 47e1031..cb47ce2 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/geometry/Cluster.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/geometry/Cluster.kt @@ -26,7 +26,6 @@ package com.xpdustry.nohorny.geometry public data class Cluster(val identifier: Int, val blocks: List>) : Rectangle { - override val x: Int = (blocks.minOfOrNull { it.x } ?: 0) override val y: Int = (blocks.minOfOrNull { it.y } ?: 0) override val w: Int = (blocks.maxOfOrNull { it.x + it.size } ?: 0) - x @@ -36,7 +35,7 @@ public data class Cluster(val identifier: Int, val blocks: List { - val clusters: List> fun getClusterByIdentifier(identifier: Int): Cluster? - fun getCluster(x: Int, y: Int): Cluster? + fun getCluster( + x: Int, + y: Int, + ): Cluster? - fun getBlock(x: Int, y: Int): Cluster.Block? + fun getBlock( + x: Int, + y: Int, + ): Cluster.Block? fun addBlock(block: Cluster.Block): List - fun removeBlock(x: Int, y: Int): List + fun removeBlock( + x: Int, + y: Int, + ): List fun clear() @@ -50,21 +58,25 @@ internal interface ClusterManager { } private class SimpleClusterManager : ClusterManager { - private val generator = AtomicInteger(0) private val _clusters = mutableListOf>() override val clusters: List> get() = _clusters - override fun getClusterByIdentifier(identifier: Int): Cluster? = - _clusters.firstOrNull { it.identifier == identifier } + override fun getClusterByIdentifier(identifier: Int): Cluster? = _clusters.firstOrNull { it.identifier == identifier } - override fun getCluster(x: Int, y: Int): Cluster? = + override fun getCluster( + x: Int, + y: Int, + ): Cluster? = _clusters.firstOrNull { cluster -> cluster.contains(x, y) && cluster.blocks.any { block -> block.contains(x, y) } } - override fun getBlock(x: Int, y: Int): Cluster.Block? { + override fun getBlock( + x: Int, + y: Int, + ): Cluster.Block? { for (cluster in _clusters) { if (cluster.contains(x, y)) { for (block in cluster.blocks) { @@ -81,7 +93,8 @@ private class SimpleClusterManager : ClusterManager { val existing = getCluster(block.x, block.y) if (existing != null) { error( - "The location is occupied by the cluster (x=${existing.x}, y=${existing.y}, w=${existing.w}, h=${existing.h})") + "The location is occupied by the cluster (x=${existing.x}, y=${existing.y}, w=${existing.w}, h=${existing.h})", + ) } val candidates = mutableListOf() @@ -114,7 +127,10 @@ private class SimpleClusterManager : ClusterManager { } } - override fun removeBlock(x: Int, y: Int): List { + override fun removeBlock( + x: Int, + y: Int, + ): List { var cIndex = -1 var bIndex = -1 for (i in _clusters.indices) { diff --git a/src/main/kotlin/com/xpdustry/nohorny/geometry/Rectangle.kt b/src/main/kotlin/com/xpdustry/nohorny/geometry/Rectangle.kt index 69e5949..71f9971 100644 --- a/src/main/kotlin/com/xpdustry/nohorny/geometry/Rectangle.kt +++ b/src/main/kotlin/com/xpdustry/nohorny/geometry/Rectangle.kt @@ -34,8 +34,10 @@ public interface Rectangle { public val w: Int public val h: Int - public fun contains(x: Int, y: Int): Boolean = - x in this.x until this.x + this.w && y in this.y until this.y + this.h + public fun contains( + x: Int, + y: Int, + ): Boolean = x in this.x until this.x + this.w && y in this.y until this.y + this.h /** * Returns whether the rectangle is adjacent or overlaps with the other. diff --git a/src/test/kotlin/com/xpdustry/nohorny/geometry/ClusterManagerTest.kt b/src/test/kotlin/com/xpdustry/nohorny/geometry/ClusterManagerTest.kt index d49c983..254499e 100644 --- a/src/test/kotlin/com/xpdustry/nohorny/geometry/ClusterManagerTest.kt +++ b/src/test/kotlin/com/xpdustry/nohorny/geometry/ClusterManagerTest.kt @@ -30,7 +30,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows class ClusterManagerTest { - @Test fun `test blocks that share a side`() { val manager = createManager() @@ -160,5 +159,9 @@ class ClusterManagerTest { private fun createManager() = ClusterManager.create() - private fun createBlock(x: Int, y: Int, size: Int) = Cluster.Block(x, y, size, Unit) + private fun createBlock( + x: Int, + y: Int, + size: Int, + ) = Cluster.Block(x, y, size, Unit) }