Skip to content

Commit

Permalink
feat: Add new indexer
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Oct 19, 2024
1 parent 9fe87be commit 5fd3e3a
Showing 5 changed files with 281 additions and 1 deletion.
15 changes: 14 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -43,13 +43,20 @@ dependencies {
compileOnly(libs.kotlinx.coroutines.core)
compileOnly(libs.kotlinx.coroutines.jdk8)
compileOnly(libs.kotlinx.serialization.json)

compileOnly(toxopid.dependencies.arcCore)
testImplementation(toxopid.dependencies.arcCore)
compileOnly(toxopid.dependencies.mindustryCore)
testImplementation(toxopid.dependencies.mindustryCore)

compileOnly(libs.slf4j.api)
testImplementation(libs.slf4j.simple)
compileOnly(libs.okhttp)

implementation(libs.okhttp)
implementation(libs.hoplite.core)
implementation(libs.hoplite.yaml)
implementation(libs.guava)

testImplementation(libs.junit.api)
testRuntimeOnly(libs.junit.engine)
}
@@ -119,11 +126,17 @@ val generateMetadataFile by tasks.registering {
tasks.shadowJar {
archiveFileName = "${metadata.name}.jar"
archiveClassifier = "plugin"

from(generateMetadataFile)
from(rootProject.file("LICENSE.md")) { into("META-INF") }

val shadowPackage = "$rootPackage.shadow"
kotlinRelocate("com.sksamuel.hoplite", "$shadowPackage.hoplite")
kotlinRelocate("okhttp3", "$shadowPackage.okhttp3")
kotlinRelocate("okio", "$shadowPackage.okio")
relocate("org.yaml.snakeyaml", "$shadowPackage.snakeyaml")
relocate("com.google.common", "$shadowPackage.guava")

mergeServiceFiles()
minimize {
exclude(dependency("com.sksamuel.hoplite:hoplite-.*:.*"))
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ slf4md = "1.0.1"
slf4j = "2.0.16"
hoplite = "2.8.2"
okhttp = "4.12.0"
guava = "33.3.1-jre"

# testing
junit = "5.11.2"
@@ -34,6 +35,7 @@ slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
hoplite-core = { module = "com.sksamuel.hoplite:hoplite-core", version.ref = "hoplite" }
hoplite-yaml = { module = "com.sksamuel.hoplite:hoplite-yaml", version.ref = "hoplite" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }

# testing
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
1 change: 1 addition & 0 deletions src/main/kotlin/com/xpdustry/nohorny/NoHornyLogger.kt
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ import arc.util.Log
import mindustry.Vars
import org.slf4j.LoggerFactory

@Deprecated("no")
internal interface NoHornyLogger {
fun error(
text: String,
135 changes: 135 additions & 0 deletions src/main/kotlin/com/xpdustry/nohorny/geometry/AdjacencyIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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.geometry

import arc.math.geom.Point2
import arc.struct.IntMap
import arc.struct.IntQueue
import arc.struct.IntSet
import com.google.common.graph.ElementOrder
import com.google.common.graph.GraphBuilder
import kotlin.math.max
import kotlin.math.min

internal data class IndexBlock<T : Any>(
override val x: Int,
override val y: Int,
val size: Int,
val data: T,
) : Rectangle {
override val w: Int get() = size
override val h: Int get() = size
}

internal data class IndexCluster<T : Any>(
override val x: Int,
override val y: Int,
override val w: Int,
override val h: Int,
val blocks: List<IndexBlock<T>>,
) : Rectangle

@Suppress("UnstableApiUsage")
internal class AdjacencyIndex<T : Any> {
private val index = IntMap<IndexBlock<T>>()
private val links =
GraphBuilder.undirected()
.nodeOrder(ElementOrder.unordered<Int>())
.build<Int>()

fun upsert(block: IndexBlock<T>) {
if (select(block.x, block.y) != null) {
remove(block.x, block.y)
}
for (x in block.x until block.x + block.size) {
for (y in block.y until block.y + block.size) {
val packed = Point2.pack(x, y)
index.put(packed, block)

if (x == block.x && select(x - 1, y) != null) {
links.putEdge(packed, Point2.pack(x - 1, y))
} else if (x == block.x + block.size - 1 && select(x + 1, y) != null) {
links.putEdge(packed, Point2.pack(x + 1, y))
}

if (y == block.y && select(x, y - 1) != null) {
links.putEdge(packed, Point2.pack(x, y - 1))
} else if (y == block.y + block.size - 1 && select(x, y + 1) != null) {
links.putEdge(packed, Point2.pack(x, y + 1))
}
}
}
}

fun select(
x: Int,
y: Int,
): IndexBlock<T>? = index[Point2.pack(x, y)]

fun remove(
x: Int,
y: Int,
) {
val packed = Point2.pack(x, y)
val block = select(x, y) ?: return
for (i in block.x until block.x + block.size) {
for (j in block.y until block.y + block.size) {
index.remove(Point2.pack(i, j))
}
}
links.removeNode(packed)
}

fun adjacents(): List<IndexCluster<T>> {
val clusters = mutableListOf<IndexCluster<T>>()
val visited = IntSet()
for (node in links.nodes()) {
if (node in visited) continue
var x = Point2.x(node).toInt()
var y = Point2.y(node).toInt()
var w = index[node]!!.size
var h = w
val blocks = mutableListOf<IndexBlock<T>>()
val queue = IntQueue()
queue.addLast(node)
while (!queue.isEmpty) {
val current = queue.removeFirst()
if (!visited.add(current)) continue
val data = index[current]!!
blocks += data
x = min(x, data.x)
y = min(y, data.y)
w = max(w, data.x + data.size - x)
h = max(h, data.y + data.size - y)
for (neighbor in links.adjacentNodes(current)) {
queue.addLast(neighbor)
}
}
clusters += IndexCluster(x, y, w, h, blocks)
}
return clusters
}
}
129 changes: 129 additions & 0 deletions src/main/kotlin/com/xpdustry/nohorny/tracker/LogicDisplayTracker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* 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.tracker

import com.xpdustry.nohorny.NoHornyImage
import com.xpdustry.nohorny.NoHornyListener
import com.xpdustry.nohorny.extension.onBuildingLifecycleEvent
import com.xpdustry.nohorny.extension.rx
import com.xpdustry.nohorny.extension.ry
import com.xpdustry.nohorny.geometry.AdjacencyIndex
import com.xpdustry.nohorny.geometry.ImmutablePoint
import com.xpdustry.nohorny.geometry.IndexBlock
import mindustry.Vars
import mindustry.logic.LExecutor
import mindustry.world.blocks.logic.LogicDisplay

private typealias ProcessorWithLinks = Pair<NoHornyImage.Processor, List<ImmutablePoint>>

internal data class LogicDisplayConfig(
val processorSearchRadius: Int = 10,
)

internal class LogicDisplayTracker : NoHornyListener {
private val config = LogicDisplayConfig()
private val processors = mutableMapOf<ImmutablePoint, ProcessorWithLinks>()
private val displays = AdjacencyIndex<NoHornyImage.Display>()

override fun onInit() {
onBuildingLifecycleEvent<LogicDisplay.LogicDisplayBuild>(
insert = { display, _, _ ->
val resolution = (display.block as LogicDisplay).displaySize
val map = mutableMapOf<ImmutablePoint, NoHornyImage.Processor>()
val block =
IndexBlock(
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,
config.processorSearchRadius.toFloat() * Vars.tilesize,
)
) {
val (processor, links) = data
for (link in links) {
if (block.contains(link.x, link.y)) {
map[position] = processor
}
}
}
}

displays.upsert(block)
},
remove = { x, y ->
displays.remove(x, y)
},
)
}

private fun readInstructions(executor: LExecutor): List<NoHornyImage.Instruction> {
val instructions = mutableListOf<NoHornyImage.Instruction>()
for (instruction in executor.instructions) {
if (instruction !is LExecutor.DrawI) {
continue
}
instructions +=
when (instruction.type) {
LogicDisplay.commandColor -> {
val r = normalizeColorValue(executor.numi(instruction.x))
val g = normalizeColorValue(executor.numi(instruction.y))
val b = normalizeColorValue(executor.numi(instruction.p1))
val a = normalizeColorValue(executor.numi(instruction.p2))
NoHornyImage.Instruction.Color(r, g, b, a)
}
LogicDisplay.commandRect -> {
val x = executor.numi(instruction.x)
val y = executor.numi(instruction.y)
val w = executor.numi(instruction.p1)
val h = executor.numi(instruction.p2)
NoHornyImage.Instruction.Rect(x, y, w, h)
}
LogicDisplay.commandTriangle -> {
val x1 = executor.numi(instruction.x)
val y1 = executor.numi(instruction.y)
val x2 = executor.numi(instruction.p1)
val y2 = executor.numi(instruction.p2)
val x3 = executor.numi(instruction.p3)
val y3 = executor.numi(instruction.p4)
NoHornyImage.Instruction.Triangle(x1, y1, x2, y2, x3, y3)
}
else -> continue
}
}
return instructions
}

private fun normalizeColorValue(value: Int): Int {
val result = value % 256
return if (result < 0) result + 256 else result
}
}

0 comments on commit 5fd3e3a

Please sign in to comment.