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 {
 | 
					    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",
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user