mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Initial Commit of Gjallarhorn: A Heimdall Analytics Tool
This commit is contained in:
		@ -39,6 +39,9 @@ tasks.create("updateManifests") {
 | 
			
		||||
    writer.use {
 | 
			
		||||
      val rootPath = rootProject.rootDir.toPath()
 | 
			
		||||
      val updateManifest = subprojects.mapNotNull { project ->
 | 
			
		||||
        if (project.name == "foundation-gjallarhorn") {
 | 
			
		||||
          return@mapNotNull null
 | 
			
		||||
        }
 | 
			
		||||
        val files = project.tasks.getByName("shadowJar").outputs
 | 
			
		||||
        val paths = files.files.map { rootPath.relativize(it.toPath()).toString() }
 | 
			
		||||
 | 
			
		||||
@ -119,9 +122,11 @@ subprojects {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (project.name != "foundation-gjallarhorn") {
 | 
			
		||||
    tasks.withType<ShadowJar> {
 | 
			
		||||
      archiveClassifier.set("plugin")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  tasks.assemble {
 | 
			
		||||
    dependsOn("shadowJar")
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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 {
 | 
			
		||||
  implementation("org.postgresql:postgresql:42.3.1")
 | 
			
		||||
  implementation("org.jetbrains.exposed:exposed-jdbc:0.36.2")
 | 
			
		||||
  implementation("org.jetbrains.exposed:exposed-java-time:0.36.2")
 | 
			
		||||
  implementation("com.zaxxer:HikariCP:5.0.0")
 | 
			
		||||
  api("org.postgresql:postgresql:42.3.1")
 | 
			
		||||
  api("org.jetbrains.exposed:exposed-jdbc:0.36.2")
 | 
			
		||||
  api("org.jetbrains.exposed:exposed-java-time:0.36.2")
 | 
			
		||||
  api("com.zaxxer:HikariCP:5.0.0")
 | 
			
		||||
  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);
 | 
			
		||||
--
 | 
			
		||||
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-bifrost",
 | 
			
		||||
  ":foundation-heimdall",
 | 
			
		||||
  ":foundation-gjallarhorn",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user