From bc2d3e28ae3f4971c42c84023c4c4b3688669e7c Mon Sep 17 00:00:00 2001 From: Kenneth Endfinger Date: Fri, 7 Jan 2022 06:15:26 -0500 Subject: [PATCH] Gjallarhorn: Timelapse Mode --- README.md | 16 ++- build.gradle.kts | 7 +- .../foundation/gjallarhorn/BlockOffset.kt | 17 --- .../foundation/gjallarhorn/BlockPosition.kt | 19 --- .../gjallarhorn/commands/BlockLogReplay.kt | 81 ----------- .../foundation/gjallarhorn/util/ImageTools.kt | 7 - .../foundation/heimdall/event/BlockPlace.kt | 19 ++- settings.gradle.kts | 2 +- .../build.gradle.kts | 0 .../gjallarhorn/GjallarhornCommand.kt | 2 +- .../gjallarhorn/commands/BlockLogReplay.kt | 130 ++++++++++++++++++ .../commands/PlayerPositionExport.kt | 0 .../commands/PlayerSessionExport.kt | 3 +- .../kubelet/foundation/gjallarhorn/main.kt | 1 + .../gjallarhorn/render/BlockExpanse.kt | 13 ++ .../gjallarhorn/render/BlockPosition.kt | 37 +++++ .../gjallarhorn/render}/BlockState.kt | 2 +- .../gjallarhorn/render}/BlockStateImage.kt | 26 ++-- .../gjallarhorn/render}/BlockStateTracker.kt | 19 ++- .../gjallarhorn/render}/BlockTrackMode.kt | 2 +- .../gjallarhorn/util/ColorGradient.kt | 0 .../foundation/gjallarhorn/util/FloatClamp.kt | 0 .../foundation/gjallarhorn/util/ImageTools.kt | 11 ++ .../gjallarhorn/util/RandomColorKey.kt | 0 .../foundation/gjallarhorn/util}/compose.kt | 0 .../queries/player_positions_aggregates.sql | 0 26 files changed, 251 insertions(+), 163 deletions(-) delete mode 100644 foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockOffset.kt delete mode 100644 foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockPosition.kt delete mode 100644 foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt delete mode 100644 foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt rename {foundation-gjallarhorn => tool-gjallarhorn}/build.gradle.kts (100%) rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/GjallarhornCommand.kt (95%) create mode 100644 tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerPositionExport.kt (100%) rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerSessionExport.kt (93%) rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/main.kt (84%) create mode 100644 tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockExpanse.kt create mode 100644 tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockPosition.kt rename {foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn => tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render}/BlockState.kt (65%) rename {foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn => tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render}/BlockStateImage.kt (69%) rename {foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn => tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render}/BlockStateTracker.kt (60%) rename {foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn => tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render}/BlockTrackMode.kt (54%) rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ColorGradient.kt (100%) rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/FloatClamp.kt (100%) create mode 100644 tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/RandomColorKey.kt (100%) rename {foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn => tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util}/compose.kt (100%) rename {foundation-gjallarhorn => tool-gjallarhorn}/src/main/resources/queries/player_positions_aggregates.sql (100%) diff --git a/README.md b/README.md index 518096c..66a2976 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,25 @@ # Foundation + Foundation is a set of plugins that implement the core functionality for a small community Minecraft server. ## Plugins -* foundation-core - Core functionality -* foundation-bifrost - Discord chat bridge -* foundation-heimdall - Event tracking + +* foundation-core: Core functionality +* foundation-bifrost: Discord chat bridge +* foundation-heimdall: Event tracking + +## Tools + +* tool-gjallarhorn - Heimdall swiss army knife ## Installation + The following command downloads and runs a script that will fetch the latest update manifest, and install all plugins available. It can also be used to update plugins to the latest version available. -``` + +```bash # Always validate the contents of a script from the internet! bash -c "$(curl -sL https://git.gorence.io/lgorence/foundation/-/raw/main/install.sh)" ``` diff --git a/build.gradle.kts b/build.gradle.kts index 94ecb30..e57ff0f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,9 @@ plugins { id("com.github.johnrengelman.shadow") version "7.1.1" apply false } +fun Project.isFoundationPlugin() = name.startsWith("foundation-") +fun Project.isFoundationTool() = !isFoundationPlugin() + // Disable the JAR task for the root project. tasks["jar"].enabled = false @@ -39,7 +42,7 @@ tasks.create("updateManifests") { writer.use { val rootPath = rootProject.rootDir.toPath() val updateManifest = subprojects.mapNotNull { project -> - if (project.name == "foundation-gjallarhorn") { + if (project.isFoundationTool()) { return@mapNotNull null } val files = project.tasks.getByName("shadowJar").outputs @@ -122,7 +125,7 @@ subprojects { } } - if (project.name != "foundation-gjallarhorn") { + if (project.isFoundationTool()) { tasks.withType { archiveClassifier.set("plugin") } diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockOffset.kt b/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockOffset.kt deleted file mode 100644 index 79c41e3..0000000 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockOffset.kt +++ /dev/null @@ -1,17 +0,0 @@ -package cloud.kubelet.foundation.gjallarhorn - -data class BlockOffset( - val x: Long, - val y: Long, - val z: Long -) { - fun apply(position: BlockPosition) = position.copy( - x = position.x + x, - y = position.y + y, - z = position.z + z - ) - - companion object { - val none = BlockOffset(0, 0, 0) - } -} diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockPosition.kt b/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockPosition.kt deleted file mode 100644 index 105fc9c..0000000 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockPosition.kt +++ /dev/null @@ -1,19 +0,0 @@ -package cloud.kubelet.foundation.gjallarhorn - -import java.util.* - -data class BlockPosition( - val x: Long, - val y: Long, - val z: Long -) { - override fun equals(other: Any?): Boolean { - if (other !is BlockPosition) { - return false - } - - return other.x == x && other.y == y && other.z == z - } - - override fun hashCode(): Int = Objects.hash(x, y, z) -} \ No newline at end of file diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt b/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt deleted file mode 100644 index a3a1adc..0000000 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt +++ /dev/null @@ -1,81 +0,0 @@ -package cloud.kubelet.foundation.gjallarhorn.commands - -import cloud.kubelet.foundation.gjallarhorn.* -import cloud.kubelet.foundation.gjallarhorn.util.savePngFile -import cloud.kubelet.foundation.heimdall.view.BlockChangeView -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.core.requireObject -import com.github.ajalt.clikt.parameters.options.flag -import com.github.ajalt.clikt.parameters.options.option -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.select -import org.jetbrains.exposed.sql.transactions.transaction -import java.time.Instant -import java.util.concurrent.atomic.AtomicLong - -class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") { - private val db by requireObject() - private val timeAsString by option("--time", help = "Replay Time") - private val renderTopDown by option("--render-top-down", help = "Render TOp Down Image").flag() - private val renderHeightMap by option("--render-height-map", help = "Render Height Map Image").flag() - - private val considerAirBlocks by option("--consider-air-blocks", help = "Enable Air Block Consideration").flag() - - override fun run() { - val filter = compose( - combine = { a, b -> a and b }, - { timeAsString != null } to { BlockChangeView.time lessEq Instant.parse(timeAsString) } - ) - val tracker = - BlockStateTracker(if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete) - - val blockChangeCounter = AtomicLong() - transaction(db) { - BlockChangeView.select(filter).orderBy(BlockChangeView.time).forEach { row -> - val changeIsBreak = row[BlockChangeView.isBreak] - val x = row[BlockChangeView.x] - val y = row[BlockChangeView.y] - val z = row[BlockChangeView.z] - val block = row[BlockChangeView.block] - - val location = BlockPosition(x.toLong(), y.toLong(), z.toLong()) - if (changeIsBreak) { - tracker.delete(location) - } else { - tracker.place(location, BlockState(block)) - } - - val count = blockChangeCounter.addAndGet(1) - if (count % 1000L == 0L) { - System.err.println("Calculating Block Changes... $count") - } - } - } - System.err.println("Total Block Changes... ${blockChangeCounter.get()}") - - val uniqueBlockPositions = tracker.blocks.size - System.err.println("Unique Block Positions... $uniqueBlockPositions") - - val blockZeroOffset = tracker.calculateZeroBlockOffset() - System.err.println("Zero Block Offset... $blockZeroOffset") - - if (renderTopDown) { - val image = BlockStateImage() - tracker.populate(image, offset = blockZeroOffset) - val bufferedImage = image.buildTopDownImage() - bufferedImage.savePngFile("top-down.png") - } else if (renderHeightMap) { - val image = BlockStateImage() - tracker.populate(image, offset = blockZeroOffset) - val bufferedImage = image.buildHeightMapImage() - bufferedImage.savePngFile("height-map.png") - } else { - println("x,y,z,block") - for ((position, block) in tracker.blocks) { - println("${position.x},${position.y},${position.z},${block.type}") - } - } - } -} diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt b/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt deleted file mode 100644 index e3f6fb9..0000000 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt +++ /dev/null @@ -1,7 +0,0 @@ -package cloud.kubelet.foundation.gjallarhorn.util - -import java.awt.image.BufferedImage -import java.io.File -import javax.imageio.ImageIO - -fun BufferedImage.savePngFile(path: String) = ImageIO.write(this, "png", File(path)) diff --git a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt index 31b483c..501bc07 100644 --- a/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt +++ b/foundation-heimdall/src/main/kotlin/cloud/kubelet/foundation/heimdall/event/BlockPlace.kt @@ -1,7 +1,6 @@ package cloud.kubelet.foundation.heimdall.event import cloud.kubelet.foundation.heimdall.storageBlockName -import cloud.kubelet.foundation.heimdall.table.BlockBreakTable import cloud.kubelet.foundation.heimdall.table.BlockPlaceTable import org.bukkit.Location import org.bukkit.Material @@ -22,15 +21,15 @@ class BlockPlace( override fun store(transaction: Transaction) { transaction.apply { BlockPlaceTable.insert { - it[BlockBreakTable.time] = timestamp - it[BlockBreakTable.player] = playerUniqueIdentity - it[BlockBreakTable.world] = location.world.uid - it[BlockBreakTable.x] = location.x - it[BlockBreakTable.y] = location.y - it[BlockBreakTable.z] = location.z - it[BlockBreakTable.pitch] = location.pitch.toDouble() - it[BlockBreakTable.yaw] = location.yaw.toDouble() - it[BlockBreakTable.block] = material.storageBlockName + it[time] = timestamp + it[player] = playerUniqueIdentity + it[world] = location.world.uid + it[x] = location.x + it[y] = location.y + it[z] = location.z + it[pitch] = location.pitch.toDouble() + it[yaw] = location.yaw.toDouble() + it[block] = material.storageBlockName } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index ea3e077..1147d32 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,5 +4,5 @@ include( ":foundation-core", ":foundation-bifrost", ":foundation-heimdall", - ":foundation-gjallarhorn", + ":tool-gjallarhorn", ) diff --git a/foundation-gjallarhorn/build.gradle.kts b/tool-gjallarhorn/build.gradle.kts similarity index 100% rename from foundation-gjallarhorn/build.gradle.kts rename to tool-gjallarhorn/build.gradle.kts diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/GjallarhornCommand.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/GjallarhornCommand.kt similarity index 95% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/GjallarhornCommand.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/GjallarhornCommand.kt index e1f69db..718cdab 100644 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/GjallarhornCommand.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/GjallarhornCommand.kt @@ -12,7 +12,7 @@ class GjallarhornCommand : CliktCommand(invokeWithoutSubcommand = true) { private val jdbcConnectionUsername by option("-u", "--connection-username", help = "JDBC Connection Username") .default("jdbc:postgresql://localhost/foundation") - private val jdbcConnectionPassword by option("-p", "--connection-password", help = "JDBC Connection Passowrd") + private val jdbcConnectionPassword by option("-p", "--connection-password", help = "JDBC Connection Password") .default("jdbc:postgresql://localhost/foundation") override fun run() { diff --git a/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt new file mode 100644 index 0000000..3e3ca79 --- /dev/null +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/BlockLogReplay.kt @@ -0,0 +1,130 @@ +package cloud.kubelet.foundation.gjallarhorn.commands + +import cloud.kubelet.foundation.gjallarhorn.compose +import cloud.kubelet.foundation.gjallarhorn.render.* +import cloud.kubelet.foundation.gjallarhorn.util.savePngFile +import cloud.kubelet.foundation.heimdall.view.BlockChangeView +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.requireObject +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.types.enum +import jetbrains.exodus.kotlin.notNull +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq +import org.jetbrains.exposed.sql.transactions.transaction +import org.slf4j.LoggerFactory +import java.awt.image.BufferedImage +import java.time.Duration +import java.time.Instant +import java.util.concurrent.atomic.AtomicLong + +class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") { + private val db by requireObject() + private val exactTimeAsString by option("--time", help = "Replay Time") + private val timeLapseMode by option("--timelapse", help = "Timelapse Mode").enum { it.id } + private val render by option("--render", help = "Render Top Down Image").enum { it.id }.required() + + private val considerAirBlocks by option("--consider-air-blocks", help = "Enable Air Block Consideration").flag() + + private val logger = LoggerFactory.getLogger(BlockLogReplay::class.java) + + override fun run() { + if (timeLapseMode != null) { + val (start, end) = transaction(db) { + val minTimeColumn = BlockChangeView.time.min().notNull + val maxTimeColumn = BlockChangeView.time.max().notNull + val row = BlockChangeView.slice(minTimeColumn, maxTimeColumn).selectAll().single() + row[minTimeColumn]!! to row[maxTimeColumn]!! + } + + val intervals = mutableListOf() + var current = start + while (!current.isAfter(end)) { + intervals.add(current) + current = current.plus(timeLapseMode!!.interval) + } + + val trackers = mutableMapOf() + for (time in intervals) { + val index = intervals.indexOf(time) + 1 + val tracker = buildTrackerState(time, "Timelapse-${index}") + if (tracker.isEmpty()) { + continue + } + trackers[index] = tracker + } + + val allBlockOffsets = trackers.map { it.value.calculateZeroBlockOffset() } + val globalBlockOffset = BlockPosition.maxOf(allBlockOffsets.asSequence()) + val allBlockMaxes = trackers.map { it.value.calculateZeroBlockOffset() } + val globalBlockMax = BlockPosition.maxOf(allBlockMaxes.asSequence()) + val globalBlockExpanse = BlockExpanse.offsetAndMax(globalBlockOffset, globalBlockMax) + + for ((i, tracker) in trackers.entries) { + saveRenderImage(tracker, globalBlockExpanse, "-${i}") + } + } else { + val time = if (exactTimeAsString != null) Instant.parse(exactTimeAsString) else null + val tracker = buildTrackerState(time, "Single-Time") + val expanse = BlockExpanse.offsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock()) + saveRenderImage(tracker, expanse) + } + } + + fun saveRenderImage(tracker: BlockStateTracker, expanse: BlockExpanse, suffix: String = "") { + val state = BlockStateImage() + tracker.populateStateImage(state, expanse.offset) + val image = render.renderBufferedImage(state, expanse) + image.savePngFile("${render.id}${suffix}.png") + } + + fun buildTrackerState(time: Instant?, job: String): BlockStateTracker { + val filter = compose( + combine = { a, b -> a and b }, + { time != null } to { BlockChangeView.time lessEq time!! } + ) + + val tracker = + BlockStateTracker(if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete) + + val blockChangeCounter = AtomicLong() + transaction(db) { + BlockChangeView.select(filter).orderBy(BlockChangeView.time).forEach { row -> + val changeIsBreak = row[BlockChangeView.isBreak] + val x = row[BlockChangeView.x] + val y = row[BlockChangeView.y] + val z = row[BlockChangeView.z] + val block = row[BlockChangeView.block] + + val location = BlockPosition(x.toLong(), y.toLong(), z.toLong()) + if (changeIsBreak) { + tracker.delete(location) + } else { + tracker.place(location, BlockState(block)) + } + + val count = blockChangeCounter.addAndGet(1) + if (count % 1000L == 0L) { + logger.info("Job $job Calculating Block Changes... $count") + } + } + } + logger.info("Job $job Total Block Changes... ${blockChangeCounter.get()}") + + val uniqueBlockPositions = tracker.blocks.size + logger.info("Job $job Unique Block Positions... $uniqueBlockPositions") + return tracker + } + + enum class RenderType(val id: String, val renderBufferedImage: (BlockStateImage, BlockExpanse) -> BufferedImage) { + TopDown("top-down", { image, expanse -> image.buildTopDownImage(expanse) }), + HeightMap("height-map", { image, expanse -> image.buildHeightMapImage(expanse) }) + } + + enum class TimeLapseMode(val id: String, val interval: Duration) { + ByHour("hours", Duration.ofHours(1)), + ByDay("days", Duration.ofDays(1)) + } +} diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerPositionExport.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerPositionExport.kt similarity index 100% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerPositionExport.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerPositionExport.kt diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerSessionExport.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerSessionExport.kt similarity index 93% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerSessionExport.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerSessionExport.kt index e9b66c7..f8acba6 100644 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerSessionExport.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/commands/PlayerSessionExport.kt @@ -1,5 +1,6 @@ -package cloud.kubelet.foundation.gjallarhorn +package cloud.kubelet.foundation.gjallarhorn.commands +import cloud.kubelet.foundation.gjallarhorn.compose import cloud.kubelet.foundation.heimdall.table.PlayerSessionTable import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.requireObject diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/main.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/main.kt similarity index 84% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/main.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/main.kt index c9e2b46..3a8a693 100644 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/main.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/main.kt @@ -2,6 +2,7 @@ package cloud.kubelet.foundation.gjallarhorn import cloud.kubelet.foundation.gjallarhorn.commands.BlockLogReplay import cloud.kubelet.foundation.gjallarhorn.commands.PlayerPositionExport +import cloud.kubelet.foundation.gjallarhorn.commands.PlayerSessionExport import com.github.ajalt.clikt.core.subcommands fun main(args: Array) = GjallarhornCommand().subcommands( diff --git a/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockExpanse.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockExpanse.kt new file mode 100644 index 0000000..b4676fc --- /dev/null +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockExpanse.kt @@ -0,0 +1,13 @@ +package cloud.kubelet.foundation.gjallarhorn.render + +class BlockExpanse( + val offset: BlockPosition, + val size: BlockPosition +) { + companion object { + fun offsetAndMax(offset: BlockPosition, max: BlockPosition) = BlockExpanse( + offset, + offset.applyAsOffset(max) + ) + } +} diff --git a/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockPosition.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockPosition.kt new file mode 100644 index 0000000..0ef225a --- /dev/null +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockPosition.kt @@ -0,0 +1,37 @@ +package cloud.kubelet.foundation.gjallarhorn.render + +import java.util.* + +data class BlockPosition( + val x: Long, + val y: Long, + val z: Long +) { + override fun equals(other: Any?): Boolean { + if (other !is BlockPosition) { + return false + } + + return other.x == x && other.y == y && other.z == z + } + + override fun hashCode(): Int = Objects.hash(x, y, z) + + fun applyAsOffset(position: BlockPosition) = position.copy( + x = position.x + x, + y = position.y + y, + z = position.z + z + ) + + companion object { + val zero = BlockPosition(0, 0, 0) + + fun maxOf(positions: Sequence): BlockPosition { + val x = positions.maxOf { it.x } + val y = positions.maxOf { it.y } + val z = positions.maxOf { it.z } + + return BlockPosition(x, y, z) + } + } +} diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockState.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockState.kt similarity index 65% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockState.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockState.kt index 405852c..c2c9c0a 100644 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockState.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockState.kt @@ -1,4 +1,4 @@ -package cloud.kubelet.foundation.gjallarhorn +package cloud.kubelet.foundation.gjallarhorn.render import kotlinx.serialization.Serializable diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockStateImage.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockStateImage.kt similarity index 69% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockStateImage.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockStateImage.kt index bf22282..52537f6 100644 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockStateImage.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockStateImage.kt @@ -1,4 +1,4 @@ -package cloud.kubelet.foundation.gjallarhorn +package cloud.kubelet.foundation.gjallarhorn.render import cloud.kubelet.foundation.gjallarhorn.util.ColorGradient import cloud.kubelet.foundation.gjallarhorn.util.FloatClamp @@ -18,9 +18,9 @@ class BlockStateImage { }[position.y] = state } - fun buildTopDownImage(): BufferedImage { + fun buildTopDownImage(expanse: BlockExpanse): BufferedImage { val colorKey = RandomColorKey() - return buildPixelQuadImage { x, z -> + return buildPixelQuadImage(expanse) { x, z -> val maybeYBlocks = blocks[x]?.get(z) if (maybeYBlocks == null) { setPixelQuad(x, z, Color.white.rgb) @@ -37,16 +37,16 @@ class BlockStateImage { } } - fun buildHeightMapImage(): BufferedImage { + fun buildHeightMapImage(expanse: BlockExpanse): BufferedImage { val yMin = blocks.minOf { xSection -> xSection.value.minOf { zSection -> zSection.value.minOf { it.key } } } val yMax = blocks.maxOf { xSection -> xSection.value.maxOf { zSection -> zSection.value.maxOf { it.key } } } val clamp = FloatClamp(yMin, yMax) - return buildHeatMapImage(clamp) { x, z -> blocks[x]?.get(z)?.maxOf { it.key } } + return buildHeatMapImage(expanse, clamp) { x, z -> blocks[x]?.get(z)?.maxOf { it.key } } } - fun buildHeatMapImage(clamp: FloatClamp, calculate: (Long, Long) -> Long?): BufferedImage = - buildPixelQuadImage { x, z -> + fun buildHeatMapImage(expanse: BlockExpanse, clamp: FloatClamp, calculate: (Long, Long) -> Long?): BufferedImage = + buildPixelQuadImage(expanse) { x, z -> val value = calculate(x, z) val color = if (value != null) { val floatValue = clamp.convert(value) @@ -65,13 +65,13 @@ class BlockStateImage { setRGB((x.toInt() * 2) + 1, (z.toInt() * 2) + 1, rgb) } - private fun buildPixelQuadImage(callback: BufferedImage.(Long, Long) -> Unit): BufferedImage { - val xMax = blocks.keys.maxOf { it } - val zMax = blocks.maxOf { xSection -> xSection.value.maxOf { zSection -> zSection.key } } - val bufferedImage = BufferedImage(xMax.toInt() * 2, zMax.toInt() * 2, BufferedImage.TYPE_4BYTE_ABGR) + private fun buildPixelQuadImage(expanse: BlockExpanse, callback: BufferedImage.(Long, Long) -> Unit): BufferedImage { + val width = expanse.size.x + val height = expanse.size.z + val bufferedImage = BufferedImage(width.toInt() * 2, height.toInt() * 2, BufferedImage.TYPE_4BYTE_ABGR) - for (x in 0 until xMax) { - for (z in 0 until zMax) { + for (x in 0 until width) { + for (z in 0 until height) { callback(bufferedImage, x, z) } } diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockStateTracker.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockStateTracker.kt similarity index 60% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockStateTracker.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockStateTracker.kt index 9bccb59..28b9b92 100644 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockStateTracker.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockStateTracker.kt @@ -1,4 +1,4 @@ -package cloud.kubelet.foundation.gjallarhorn +package cloud.kubelet.foundation.gjallarhorn.render import kotlin.math.absoluteValue @@ -17,7 +17,7 @@ class BlockStateTracker(private val mode: BlockTrackMode = BlockTrackMode.Remove } } - fun calculateZeroBlockOffset(): BlockOffset { + fun calculateZeroBlockOffset(): BlockPosition { val x = blocks.keys.minOf { it.x } val y = blocks.keys.minOf { it.y } val z = blocks.keys.minOf { it.z } @@ -26,12 +26,21 @@ class BlockStateTracker(private val mode: BlockTrackMode = BlockTrackMode.Remove val yOffset = if (y < 0) y.absoluteValue else 0 val zOffset = if (z < 0) z.absoluteValue else 0 - return BlockOffset(xOffset, yOffset, zOffset) + return BlockPosition(xOffset, yOffset, zOffset) } - fun populate(image: BlockStateImage, offset: BlockOffset = BlockOffset.none) { + fun calculateMaxBlock(): BlockPosition { + val x = blocks.keys.maxOf { it.x } + val y = blocks.keys.maxOf { it.y } + val z = blocks.keys.maxOf { it.z } + return BlockPosition(x, y, z) + } + + fun isEmpty() = blocks.isEmpty() + + fun populateStateImage(image: BlockStateImage, offset: BlockPosition = BlockPosition.zero) { blocks.forEach { (position, state) -> - val realPosition = offset.apply(position) + val realPosition = offset.applyAsOffset(position) image.put(realPosition, state) } } diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockTrackMode.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockTrackMode.kt similarity index 54% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockTrackMode.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockTrackMode.kt index 2ddd48f..0c757a5 100644 --- a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/BlockTrackMode.kt +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/render/BlockTrackMode.kt @@ -1,4 +1,4 @@ -package cloud.kubelet.foundation.gjallarhorn +package cloud.kubelet.foundation.gjallarhorn.render enum class BlockTrackMode { RemoveOnDelete, diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ColorGradient.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ColorGradient.kt similarity index 100% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ColorGradient.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ColorGradient.kt diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/FloatClamp.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/FloatClamp.kt similarity index 100% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/FloatClamp.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/FloatClamp.kt diff --git a/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt new file mode 100644 index 0000000..0a0dc54 --- /dev/null +++ b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/ImageTools.kt @@ -0,0 +1,11 @@ +package cloud.kubelet.foundation.gjallarhorn.util + +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO + +fun BufferedImage.savePngFile(path: String) { + if (!ImageIO.write(this, "png", File(path))) { + throw RuntimeException("Unable to write PNG.") + } +} diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/RandomColorKey.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/RandomColorKey.kt similarity index 100% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/RandomColorKey.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/RandomColorKey.kt diff --git a/foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/compose.kt b/tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/compose.kt similarity index 100% rename from foundation-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/compose.kt rename to tool-gjallarhorn/src/main/kotlin/cloud/kubelet/foundation/gjallarhorn/util/compose.kt diff --git a/foundation-gjallarhorn/src/main/resources/queries/player_positions_aggregates.sql b/tool-gjallarhorn/src/main/resources/queries/player_positions_aggregates.sql similarity index 100% rename from foundation-gjallarhorn/src/main/resources/queries/player_positions_aggregates.sql rename to tool-gjallarhorn/src/main/resources/queries/player_positions_aggregates.sql