mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-02 13:10:55 +00:00
Heimdall: Player Names Table, Gjallarhorn: Block State Image Rendering
This commit is contained in:
parent
9386dc7c56
commit
e681df1e65
@ -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
|
||||
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class BlockStateTracker {
|
||||
val blocks = HashMap<BlockPosition, BlockState>()
|
||||
@ -14,21 +14,22 @@ class BlockStateTracker {
|
||||
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 x: Long,
|
||||
val y: Long,
|
||||
val z: Long
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is BlockPosition) {
|
||||
return false
|
||||
}
|
||||
val xOffset = if (x < 0) x.absoluteValue else 0
|
||||
val yOffset = if (y < 0) y.absoluteValue else 0
|
||||
val zOffset = if (z < 0) z.absoluteValue else 0
|
||||
|
||||
return other.x == x && other.y == y && other.z == z
|
||||
return BlockOffset(xOffset, yOffset, zOffset)
|
||||
}
|
||||
|
||||
fun populate(image: BlockStateImage, offset: BlockOffset = BlockOffset.none) {
|
||||
blocks.forEach { (position, state) ->
|
||||
val realPosition = offset.apply(position)
|
||||
image.put(realPosition, state)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(x, y, z)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.commands
|
||||
|
||||
import cloud.kubelet.foundation.gjallarhorn.BlockStateTracker
|
||||
import cloud.kubelet.foundation.gjallarhorn.compose
|
||||
import cloud.kubelet.foundation.gjallarhorn.*
|
||||
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.io.File
|
||||
import java.time.Instant
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
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")
|
||||
private val render by option("--render", help = "Enable Render Mode").flag()
|
||||
|
||||
override fun run() {
|
||||
val filter = compose(
|
||||
@ -32,18 +35,25 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
||||
val z = row[BlockChangeView.z]
|
||||
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) {
|
||||
tracker.delete(location)
|
||||
} else {
|
||||
tracker.place(location, BlockStateTracker.BlockState(block))
|
||||
tracker.place(location, BlockState(block))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("x,y,z,block")
|
||||
for ((position, block) in tracker.blocks) {
|
||||
println("${position.x},${position.y},${position.z},${block.type}")
|
||||
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")
|
||||
for ((position, block) in tracker.blocks) {
|
||||
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
|
||||
fun onEntityDeath(event: EntityDeathEvent) {
|
||||
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() {
|
||||
@ -145,7 +152,12 @@ class FoundationHeimdallPlugin : JavaPlugin(), Listener {
|
||||
val endTime = Instant.now()
|
||||
for (playerId in playerJoinTimes.keys().toList()) {
|
||||
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()
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
--
|
||||
create extension if not exists "uuid-ossp";
|
||||
--
|
||||
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);
|
||||
--
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user