mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-02 21:20:55 +00:00
Initial Commit of Gjallarhorn: A Heimdall Analytics Tool
This commit is contained in:
parent
cbbefc94a2
commit
ff665c27f5
@ -39,6 +39,9 @@ tasks.create("updateManifests") {
|
|||||||
writer.use {
|
writer.use {
|
||||||
val rootPath = rootProject.rootDir.toPath()
|
val rootPath = rootProject.rootDir.toPath()
|
||||||
val updateManifest = subprojects.mapNotNull { project ->
|
val updateManifest = subprojects.mapNotNull { project ->
|
||||||
|
if (project.name == "foundation-gjallarhorn") {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
val files = project.tasks.getByName("shadowJar").outputs
|
val files = project.tasks.getByName("shadowJar").outputs
|
||||||
val paths = files.files.map { rootPath.relativize(it.toPath()).toString() }
|
val paths = files.files.map { rootPath.relativize(it.toPath()).toString() }
|
||||||
|
|
||||||
@ -119,8 +122,10 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<ShadowJar> {
|
if (project.name != "foundation-gjallarhorn") {
|
||||||
archiveClassifier.set("plugin")
|
tasks.withType<ShadowJar> {
|
||||||
|
archiveClassifier.set("plugin")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.assemble {
|
tasks.assemble {
|
||||||
|
10
foundation-gjallarhorn/build.gradle.kts
Normal file
10
foundation-gjallarhorn/build.gradle.kts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
dependencies {
|
||||||
|
implementation(project(":foundation-core"))
|
||||||
|
implementation(project(":foundation-heimdall"))
|
||||||
|
implementation("org.slf4j:slf4j-simple:1.7.32")
|
||||||
|
implementation("com.github.ajalt.clikt:clikt:3.3.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf(tasks.jar, tasks.shadowJar).map { it.get() }.forEach { task ->
|
||||||
|
task.manifest.attributes["Main-Class"] = "cloud.kubelet.foundation.gjallarhorn.MainKt"
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
class BlockStateTracker {
|
||||||
|
val blocks = HashMap<BlockPosition, BlockState>()
|
||||||
|
|
||||||
|
fun place(position: BlockPosition, state: BlockState) {
|
||||||
|
blocks[position] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(position: BlockPosition) {
|
||||||
|
blocks.remove(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BlockState(val type: String)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn
|
||||||
|
|
||||||
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
|
import com.github.ajalt.clikt.parameters.options.default
|
||||||
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
|
||||||
|
class GjallarhornCommand : CliktCommand(invokeWithoutSubcommand = true) {
|
||||||
|
private val jdbcConnectionUrl by option("-c", "--connection-url", help = "JDBC Connection URL")
|
||||||
|
.default("jdbc:postgresql://localhost/foundation")
|
||||||
|
|
||||||
|
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")
|
||||||
|
.default("jdbc:postgresql://localhost/foundation")
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val db = Database.connect(jdbcConnectionUrl, user = jdbcConnectionUsername, password = jdbcConnectionPassword)
|
||||||
|
currentContext.findOrSetObject { db }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.commands
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.BlockStateTracker
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.compose
|
||||||
|
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.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
|
||||||
|
|
||||||
|
class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") {
|
||||||
|
private val db by requireObject<Database>()
|
||||||
|
private val timeAsString by option("--time", help = "Replay Time")
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val filter = compose(
|
||||||
|
combine = { a, b -> a and b },
|
||||||
|
{ timeAsString != null } to { BlockChangeView.time lessEq Instant.parse(timeAsString) }
|
||||||
|
)
|
||||||
|
val tracker = BlockStateTracker()
|
||||||
|
|
||||||
|
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 = BlockStateTracker.BlockPosition(x.toLong(), y.toLong(), z.toLong())
|
||||||
|
if (changeIsBreak) {
|
||||||
|
tracker.delete(location)
|
||||||
|
} else {
|
||||||
|
tracker.place(location, BlockStateTracker.BlockState(block))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("x,y,z,block")
|
||||||
|
for ((position, block) in tracker.blocks) {
|
||||||
|
println("${position.x},${position.y},${position.z},${block.type}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.commands
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.compose
|
||||||
|
import cloud.kubelet.foundation.heimdall.table.PlayerPositionTable
|
||||||
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
|
import com.github.ajalt.clikt.core.requireObject
|
||||||
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.greaterEq
|
||||||
|
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.*
|
||||||
|
|
||||||
|
class PlayerPositionExport : CliktCommand(name = "export-player-positions", help = "Export Player Positions") {
|
||||||
|
private val db by requireObject<Database>()
|
||||||
|
|
||||||
|
private val playerIdString by option("--player", help = "Player ID")
|
||||||
|
private val startTimeString by option("--start-time", help = "Start Time")
|
||||||
|
private val endTimeString by option("--end-time", help = "End Time")
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val filter = compose(
|
||||||
|
combine = { a, b -> a and b },
|
||||||
|
{ startTimeString != null } to { PlayerPositionTable.time greaterEq Instant.parse(startTimeString) },
|
||||||
|
{ endTimeString != null } to { PlayerPositionTable.time lessEq Instant.parse(endTimeString) },
|
||||||
|
{ playerIdString != null } to { PlayerPositionTable.player eq UUID.fromString(playerIdString) }
|
||||||
|
)
|
||||||
|
|
||||||
|
println("time,player,world,x,y,z,pitch,yaw")
|
||||||
|
transaction(db) {
|
||||||
|
PlayerPositionTable.select(filter).orderBy(PlayerPositionTable.time).forEach { row ->
|
||||||
|
val time = row[PlayerPositionTable.time]
|
||||||
|
val player = row[PlayerPositionTable.player]
|
||||||
|
val world = row[PlayerPositionTable.world]
|
||||||
|
val x = row[PlayerPositionTable.x]
|
||||||
|
val y = row[PlayerPositionTable.y]
|
||||||
|
val z = row[PlayerPositionTable.z]
|
||||||
|
val pitch = row[PlayerPositionTable.pitch]
|
||||||
|
val yaw = row[PlayerPositionTable.yaw]
|
||||||
|
|
||||||
|
println("${time},${player},${world},${x},${y},${z},${pitch},${yaw}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.heimdall.table.PlayerSessionTable
|
||||||
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
|
import com.github.ajalt.clikt.core.requireObject
|
||||||
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.jetbrains.exposed.sql.and
|
||||||
|
import org.jetbrains.exposed.sql.select
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class PlayerSessionExport : CliktCommand(name = "export-player-sessions", help = "Export Player Sessions") {
|
||||||
|
private val db by requireObject<Database>()
|
||||||
|
|
||||||
|
private val playerIdString by option("--player-id", help = "Player ID")
|
||||||
|
private val playerNameString by option("--player-name", help = "Player Name")
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val filter = compose(
|
||||||
|
combine = { a, b -> a and b },
|
||||||
|
{ playerIdString != null } to { PlayerSessionTable.player eq UUID.fromString(playerIdString) },
|
||||||
|
{ playerNameString != null } to { PlayerSessionTable.name eq playerNameString!! }
|
||||||
|
)
|
||||||
|
|
||||||
|
println("id,player,name,start,end")
|
||||||
|
transaction(db) {
|
||||||
|
PlayerSessionTable.select(filter).orderBy(PlayerSessionTable.endTime).forEach { row ->
|
||||||
|
val id = row[PlayerSessionTable.id]
|
||||||
|
val player = row[PlayerSessionTable.player]
|
||||||
|
val name = row[PlayerSessionTable.name]
|
||||||
|
val start = row[PlayerSessionTable.startTime]
|
||||||
|
val end = row[PlayerSessionTable.endTime]
|
||||||
|
|
||||||
|
println("${id},${player},${name},${start},${end}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.sql.Op
|
||||||
|
|
||||||
|
fun compose(
|
||||||
|
combine: (Op<Boolean>, Op<Boolean>) -> Op<Boolean>,
|
||||||
|
vararg filters: Pair<() -> Boolean, () -> Op<Boolean>>
|
||||||
|
): Op<Boolean> = filters.toMap().entries
|
||||||
|
.filter { it.key() }
|
||||||
|
.map { it.value() }
|
||||||
|
.fold(Op.TRUE as Op<Boolean>, combine)
|
@ -0,0 +1,11 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.commands.BlockLogReplay
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.commands.PlayerPositionExport
|
||||||
|
import com.github.ajalt.clikt.core.subcommands
|
||||||
|
|
||||||
|
fun main(args: Array<String>) = GjallarhornCommand().subcommands(
|
||||||
|
BlockLogReplay(),
|
||||||
|
PlayerSessionExport(),
|
||||||
|
PlayerPositionExport()
|
||||||
|
).main(args)
|
@ -1,7 +1,7 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.postgresql:postgresql:42.3.1")
|
api("org.postgresql:postgresql:42.3.1")
|
||||||
implementation("org.jetbrains.exposed:exposed-jdbc:0.36.2")
|
api("org.jetbrains.exposed:exposed-jdbc:0.36.2")
|
||||||
implementation("org.jetbrains.exposed:exposed-java-time:0.36.2")
|
api("org.jetbrains.exposed:exposed-java-time:0.36.2")
|
||||||
implementation("com.zaxxer:HikariCP:5.0.0")
|
api("com.zaxxer:HikariCP:5.0.0")
|
||||||
compileOnly(project(":foundation-core"))
|
compileOnly(project(":foundation-core"))
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall.view
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.sql.Table
|
||||||
|
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||||
|
|
||||||
|
object BlockChangeView : Table("block_changes") {
|
||||||
|
val isBreak = bool("break")
|
||||||
|
val time = timestamp("time")
|
||||||
|
val player = uuid("player")
|
||||||
|
val world = uuid("world")
|
||||||
|
val x = double("x")
|
||||||
|
val y = double("y")
|
||||||
|
val z = double("z")
|
||||||
|
val pitch = double("pitch")
|
||||||
|
val yaw = double("yaw")
|
||||||
|
val block = text("block")
|
||||||
|
}
|
@ -118,3 +118,5 @@ create table if not exists heimdall.entity_kills (
|
|||||||
);
|
);
|
||||||
--
|
--
|
||||||
select create_hypertable('heimdall.entity_kills', 'time', 'player', 4, if_not_exists => TRUE);
|
select create_hypertable('heimdall.entity_kills', 'time', 'player', 4, if_not_exists => TRUE);
|
||||||
|
--
|
||||||
|
create or replace view heimdall.block_changes as select true as break, * from heimdall.block_breaks union all select false as break, * from heimdall.block_places;
|
||||||
|
@ -4,4 +4,5 @@ include(
|
|||||||
":foundation-core",
|
":foundation-core",
|
||||||
":foundation-bifrost",
|
":foundation-bifrost",
|
||||||
":foundation-heimdall",
|
":foundation-heimdall",
|
||||||
|
":foundation-gjallarhorn",
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user