mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Heimdall: Player Names Table, Gjallarhorn: Block State Image Rendering
This commit is contained in:
		@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					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)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.gjallarhorn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Serializable
 | 
				
			||||||
 | 
					data class BlockState(val type: String)
 | 
				
			||||||
@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.gjallarhorn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey
 | 
				
			||||||
 | 
					import java.awt.Color
 | 
				
			||||||
 | 
					import java.awt.image.BufferedImage
 | 
				
			||||||
 | 
					import java.util.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BlockStateImage {
 | 
				
			||||||
 | 
					  val blocks = TreeMap<Long, TreeMap<Long, TreeMap<Long, BlockState>>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun put(position: BlockPosition, state: BlockState) {
 | 
				
			||||||
 | 
					    blocks.getOrPut(position.x) {
 | 
				
			||||||
 | 
					      TreeMap()
 | 
				
			||||||
 | 
					    }.getOrPut(position.z) {
 | 
				
			||||||
 | 
					      TreeMap()
 | 
				
			||||||
 | 
					    }[position.y] = state
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun buildBufferedImage(): BufferedImage {
 | 
				
			||||||
 | 
					    val colorKey = RandomColorKey()
 | 
				
			||||||
 | 
					    val xMax = blocks.keys.maxOf { it }
 | 
				
			||||||
 | 
					    val zMax = blocks.maxOf { it.value.maxOf { it.key } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val bufferedImage = BufferedImage(xMax.toInt() * 2, zMax.toInt() * 2, BufferedImage.TYPE_4BYTE_ABGR)
 | 
				
			||||||
 | 
					    for (x in 0 until xMax) {
 | 
				
			||||||
 | 
					      for (z in 0 until zMax) {
 | 
				
			||||||
 | 
					        fun set(rgb: Int) {
 | 
				
			||||||
 | 
					          bufferedImage.setRGB(x.toInt() * 2, z.toInt() * 2, rgb)
 | 
				
			||||||
 | 
					          bufferedImage.setRGB((x.toInt() * 2) + 1, z.toInt() * 2, rgb)
 | 
				
			||||||
 | 
					          bufferedImage.setRGB(x.toInt() * 2, (z.toInt() * 2) + 1, rgb)
 | 
				
			||||||
 | 
					          bufferedImage.setRGB((x.toInt() * 2) + 1, (z.toInt() * 2) + 1, rgb)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val maybeYBlocks = blocks[x]?.get(z)
 | 
				
			||||||
 | 
					        if (maybeYBlocks == null) {
 | 
				
			||||||
 | 
					          set(Color.white.rgb)
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val maxBlockState = maybeYBlocks.maxByOrNull { it.key }?.value
 | 
				
			||||||
 | 
					        if (maxBlockState == null) {
 | 
				
			||||||
 | 
					          set(Color.white.rgb)
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val color = colorKey.map(maxBlockState.type)
 | 
				
			||||||
 | 
					        set(color.rgb)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return bufferedImage
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.gjallarhorn
 | 
					package cloud.kubelet.foundation.gjallarhorn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
import kotlin.collections.HashMap
 | 
					import kotlin.collections.HashMap
 | 
				
			||||||
 | 
					import kotlin.math.absoluteValue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlockStateTracker {
 | 
					class BlockStateTracker {
 | 
				
			||||||
  val blocks = HashMap<BlockPosition, BlockState>()
 | 
					  val blocks = HashMap<BlockPosition, BlockState>()
 | 
				
			||||||
@ -14,21 +14,22 @@ class BlockStateTracker {
 | 
				
			|||||||
    blocks.remove(position)
 | 
					    blocks.remove(position)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  data class BlockState(val type: String)
 | 
					  fun calculateZeroBlockOffset(): BlockOffset {
 | 
				
			||||||
 | 
					    val x = blocks.keys.minOf { it.x }
 | 
				
			||||||
 | 
					    val y = blocks.keys.minOf { it.y }
 | 
				
			||||||
 | 
					    val z = blocks.keys.minOf { it.z }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  data class BlockPosition(
 | 
					    val xOffset = if (x < 0) x.absoluteValue else 0
 | 
				
			||||||
    val x: Long,
 | 
					    val yOffset = if (y < 0) y.absoluteValue else 0
 | 
				
			||||||
    val y: Long,
 | 
					    val zOffset = if (z < 0) z.absoluteValue else 0
 | 
				
			||||||
    val z: Long
 | 
					
 | 
				
			||||||
  ) {
 | 
					    return BlockOffset(xOffset, yOffset, zOffset)
 | 
				
			||||||
    override fun equals(other: Any?): Boolean {
 | 
					 | 
				
			||||||
      if (other !is BlockPosition) {
 | 
					 | 
				
			||||||
        return false
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return other.x == x && other.y == y && other.z == z
 | 
					  fun populate(image: BlockStateImage, offset: BlockOffset = BlockOffset.none) {
 | 
				
			||||||
    }
 | 
					    blocks.forEach { (position, state) ->
 | 
				
			||||||
 | 
					      val realPosition = offset.apply(position)
 | 
				
			||||||
    override fun hashCode(): Int = Objects.hash(x, y, z)
 | 
					      image.put(realPosition, state)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,21 +1,24 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.gjallarhorn.commands
 | 
					package cloud.kubelet.foundation.gjallarhorn.commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cloud.kubelet.foundation.gjallarhorn.BlockStateTracker
 | 
					import cloud.kubelet.foundation.gjallarhorn.*
 | 
				
			||||||
import cloud.kubelet.foundation.gjallarhorn.compose
 | 
					 | 
				
			||||||
import cloud.kubelet.foundation.heimdall.view.BlockChangeView
 | 
					import cloud.kubelet.foundation.heimdall.view.BlockChangeView
 | 
				
			||||||
import com.github.ajalt.clikt.core.CliktCommand
 | 
					import com.github.ajalt.clikt.core.CliktCommand
 | 
				
			||||||
import com.github.ajalt.clikt.core.requireObject
 | 
					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.option
 | 
				
			||||||
import org.jetbrains.exposed.sql.Database
 | 
					import org.jetbrains.exposed.sql.Database
 | 
				
			||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq
 | 
					import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq
 | 
				
			||||||
import org.jetbrains.exposed.sql.and
 | 
					import org.jetbrains.exposed.sql.and
 | 
				
			||||||
import org.jetbrains.exposed.sql.select
 | 
					import org.jetbrains.exposed.sql.select
 | 
				
			||||||
import org.jetbrains.exposed.sql.transactions.transaction
 | 
					import org.jetbrains.exposed.sql.transactions.transaction
 | 
				
			||||||
 | 
					import java.io.File
 | 
				
			||||||
import java.time.Instant
 | 
					import java.time.Instant
 | 
				
			||||||
 | 
					import javax.imageio.ImageIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") {
 | 
					class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-log") {
 | 
				
			||||||
  private val db by requireObject<Database>()
 | 
					  private val db by requireObject<Database>()
 | 
				
			||||||
  private val timeAsString by option("--time", help = "Replay Time")
 | 
					  private val timeAsString by option("--time", help = "Replay Time")
 | 
				
			||||||
 | 
					  private val render by option("--render", help = "Enable Render Mode").flag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun run() {
 | 
					  override fun run() {
 | 
				
			||||||
    val filter = compose(
 | 
					    val filter = compose(
 | 
				
			||||||
@ -32,18 +35,25 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
 | 
				
			|||||||
        val z = row[BlockChangeView.z]
 | 
					        val z = row[BlockChangeView.z]
 | 
				
			||||||
        val block = row[BlockChangeView.block]
 | 
					        val block = row[BlockChangeView.block]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val location = BlockStateTracker.BlockPosition(x.toLong(), y.toLong(), z.toLong())
 | 
					        val location = BlockPosition(x.toLong(), y.toLong(), z.toLong())
 | 
				
			||||||
        if (changeIsBreak) {
 | 
					        if (changeIsBreak) {
 | 
				
			||||||
          tracker.delete(location)
 | 
					          tracker.delete(location)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          tracker.place(location, BlockStateTracker.BlockState(block))
 | 
					          tracker.place(location, BlockState(block))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (render) {
 | 
				
			||||||
 | 
					      val image = BlockStateImage()
 | 
				
			||||||
 | 
					      tracker.populate(image, offset = tracker.calculateZeroBlockOffset())
 | 
				
			||||||
 | 
					      val bufferedImage = image.buildBufferedImage()
 | 
				
			||||||
 | 
					      ImageIO.write(bufferedImage, "png", File("top-down.png"))
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
      println("x,y,z,block")
 | 
					      println("x,y,z,block")
 | 
				
			||||||
      for ((position, block) in tracker.blocks) {
 | 
					      for ((position, block) in tracker.blocks) {
 | 
				
			||||||
        println("${position.x},${position.y},${position.z},${block.type}")
 | 
					        println("${position.x},${position.y},${position.z},${block.type}")
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.gjallarhorn.util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.Color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RandomColorKey {
 | 
				
			||||||
 | 
					  private val colors = mutableMapOf<String, Color>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun map(key: String) = colors.getOrPut(key) { findUniqueColor() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun findUniqueColor(): Color {
 | 
				
			||||||
 | 
					    var random = randomColor()
 | 
				
			||||||
 | 
					    while (colors.values.any { it.rgb == random.rgb }) {
 | 
				
			||||||
 | 
					      random = randomColor()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return random
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun randomColor() = Color((Math.random() * 0x1000000).toInt())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -137,7 +137,14 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener {
 | 
				
			|||||||
  @EventHandler
 | 
					  @EventHandler
 | 
				
			||||||
  fun onEntityDeath(event: EntityDeathEvent) {
 | 
					  fun onEntityDeath(event: EntityDeathEvent) {
 | 
				
			||||||
    val killer = event.entity.killer ?: return
 | 
					    val killer = event.entity.killer ?: return
 | 
				
			||||||
    buffer.push(EntityKill(killer.uniqueId, killer.location, event.entity.uniqueId, event.entityType.key.toString()))
 | 
					    buffer.push(
 | 
				
			||||||
 | 
					      EntityKill(
 | 
				
			||||||
 | 
					        killer.uniqueId,
 | 
				
			||||||
 | 
					        killer.location,
 | 
				
			||||||
 | 
					        event.entity.uniqueId,
 | 
				
			||||||
 | 
					        event.entityType.key.toString()
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun onDisable() {
 | 
					  override fun onDisable() {
 | 
				
			||||||
@ -145,7 +152,12 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener {
 | 
				
			|||||||
    val endTime = Instant.now()
 | 
					    val endTime = Instant.now()
 | 
				
			||||||
    for (playerId in playerJoinTimes.keys().toList()) {
 | 
					    for (playerId in playerJoinTimes.keys().toList()) {
 | 
				
			||||||
      val startTime = playerJoinTimes.remove(playerId) ?: continue
 | 
					      val startTime = playerJoinTimes.remove(playerId) ?: continue
 | 
				
			||||||
      buffer.push(PlayerSession(playerId, server.getPlayer(playerId)?.name ?: "__unknown__", startTime, endTime))
 | 
					      buffer.push(PlayerSession(
 | 
				
			||||||
 | 
					        playerId,
 | 
				
			||||||
 | 
					        server.getPlayer(playerId)?.name ?: "__unknown__",
 | 
				
			||||||
 | 
					        startTime,
 | 
				
			||||||
 | 
					        endTime
 | 
				
			||||||
 | 
					      ))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    bufferFlushThread.flush()
 | 
					    bufferFlushThread.flush()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
--
 | 
					 | 
				
			||||||
create extension if not exists "uuid-ossp";
 | 
					create extension if not exists "uuid-ossp";
 | 
				
			||||||
--
 | 
					--
 | 
				
			||||||
create extension if not exists timescaledb;
 | 
					create extension if not exists timescaledb;
 | 
				
			||||||
@ -119,4 +118,22 @@ 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;
 | 
					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;
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					create or replace view heimdall.player_names as
 | 
				
			||||||
 | 
					    with unique_player_ids as (
 | 
				
			||||||
 | 
					        select distinct player
 | 
				
			||||||
 | 
					        from heimdall.player_sessions
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    select player, (
 | 
				
			||||||
 | 
					        select name
 | 
				
			||||||
 | 
					        from heimdall.player_sessions
 | 
				
			||||||
 | 
					        where player = unique_player_ids.player
 | 
				
			||||||
 | 
					        order by "end" desc
 | 
				
			||||||
 | 
					        limit 1
 | 
				
			||||||
 | 
					    ) as name
 | 
				
			||||||
 | 
					    from unique_player_ids;
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user