mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-02 21:20:55 +00:00
Initial Rough Cut of Heimdall Tracking System
This commit is contained in:
parent
78566d08ad
commit
e0183127b4
@ -13,18 +13,18 @@ import net.dv8tion.jda.api.entities.TextChannel
|
|||||||
import net.dv8tion.jda.api.events.GenericEvent
|
import net.dv8tion.jda.api.events.GenericEvent
|
||||||
import net.dv8tion.jda.api.events.ReadyEvent
|
import net.dv8tion.jda.api.events.ReadyEvent
|
||||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
|
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
|
||||||
import net.dv8tion.jda.api.hooks.EventListener
|
import net.dv8tion.jda.api.hooks.EventListener as DiscordEventListener
|
||||||
import net.kyori.adventure.text.Component
|
import net.kyori.adventure.text.Component
|
||||||
import net.kyori.adventure.text.TextComponent
|
import net.kyori.adventure.text.TextComponent
|
||||||
import org.bukkit.event.EventHandler
|
import org.bukkit.event.EventHandler
|
||||||
import org.bukkit.event.Listener
|
import org.bukkit.event.Listener as BukkitEventListener
|
||||||
import org.bukkit.event.player.PlayerJoinEvent
|
import org.bukkit.event.player.PlayerJoinEvent
|
||||||
import org.bukkit.event.player.PlayerQuitEvent
|
import org.bukkit.event.player.PlayerQuitEvent
|
||||||
import org.bukkit.plugin.java.JavaPlugin
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import kotlin.io.path.inputStream
|
import kotlin.io.path.inputStream
|
||||||
|
|
||||||
class FoundationBifrostPlugin : JavaPlugin(), EventListener, Listener {
|
class FoundationBifrostPlugin : JavaPlugin(), DiscordEventListener, BukkitEventListener {
|
||||||
private lateinit var config: BifrostConfig
|
private lateinit var config: BifrostConfig
|
||||||
private lateinit var jda: JDA
|
private lateinit var jda: JDA
|
||||||
private var isDev = false
|
private var isDev = false
|
||||||
|
7
foundation-heimdall/build.gradle.kts
Normal file
7
foundation-heimdall/build.gradle.kts
Normal file
@ -0,0 +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")
|
||||||
|
compileOnly(project(":foundation-core"))
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall
|
||||||
|
|
||||||
|
fun String.sqlSplitStatements(): List<String> {
|
||||||
|
val statements = mutableListOf<String>()
|
||||||
|
val buffer = StringBuilder()
|
||||||
|
fun flush() {
|
||||||
|
val trimmed = buffer.toString().trim()
|
||||||
|
if (trimmed.isNotEmpty()) {
|
||||||
|
statements.add(trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (line in lines()) {
|
||||||
|
if (line.trim() == "--") {
|
||||||
|
flush()
|
||||||
|
} else {
|
||||||
|
buffer.append(line).append("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
return statements
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.core.FoundationCorePlugin
|
||||||
|
import cloud.kubelet.foundation.core.Util
|
||||||
|
import cloud.kubelet.foundation.heimdall.buffer.BufferFlushThread
|
||||||
|
import cloud.kubelet.foundation.heimdall.buffer.EventBuffer
|
||||||
|
import cloud.kubelet.foundation.heimdall.event.PlayerPositionEvent
|
||||||
|
import cloud.kubelet.foundation.heimdall.model.HeimdallConfig
|
||||||
|
import cloud.kubelet.foundation.heimdall.table.PlayerPositionTable
|
||||||
|
import com.charleskorn.kaml.Yaml
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.insert
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import org.postgresql.Driver
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.io.path.inputStream
|
||||||
|
|
||||||
|
class FoundationHeimdallPlugin : JavaPlugin(), Listener {
|
||||||
|
private lateinit var config: HeimdallConfig
|
||||||
|
private lateinit var pool: HikariDataSource
|
||||||
|
internal lateinit var db: Database
|
||||||
|
|
||||||
|
private val buffer = EventBuffer()
|
||||||
|
private val bufferFlushThread = BufferFlushThread(this, buffer)
|
||||||
|
|
||||||
|
override fun onEnable() {
|
||||||
|
val foundation = server.pluginManager.getPlugin("Foundation") as FoundationCorePlugin
|
||||||
|
|
||||||
|
val configPath = Util.copyDefaultConfig<FoundationHeimdallPlugin>(
|
||||||
|
slF4JLogger,
|
||||||
|
foundation.pluginDataPath,
|
||||||
|
"heimdall.yaml"
|
||||||
|
)
|
||||||
|
config = Yaml.default.decodeFromStream(HeimdallConfig.serializer(), configPath.inputStream())
|
||||||
|
if (!config.enabled) {
|
||||||
|
slF4JLogger.info("Heimdall is not enabled.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slF4JLogger.info("Heimdall is enabled.")
|
||||||
|
if (!Driver.isRegistered()) {
|
||||||
|
Driver.register()
|
||||||
|
}
|
||||||
|
pool = HikariDataSource(HikariConfig().apply {
|
||||||
|
jdbcUrl = config.db.url
|
||||||
|
username = config.db.username
|
||||||
|
password = config.db.password
|
||||||
|
schema = "heimdall"
|
||||||
|
})
|
||||||
|
val initMigrationContent = FoundationHeimdallPlugin::class.java.getResourceAsStream(
|
||||||
|
"/init.sql"
|
||||||
|
)?.readAllBytes()?.decodeToString() ?: throw RuntimeException("Unable to find Heimdall init.sql")
|
||||||
|
|
||||||
|
val statements = initMigrationContent.sqlSplitStatements()
|
||||||
|
|
||||||
|
pool.connection.use { conn ->
|
||||||
|
conn.autoCommit = false
|
||||||
|
try {
|
||||||
|
for (statementAsString in statements) {
|
||||||
|
conn.prepareStatement(statementAsString).use {
|
||||||
|
it.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.commit()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
conn.rollback()
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
conn.autoCommit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db = Database.connect(pool)
|
||||||
|
server.pluginManager.registerEvents(this, this)
|
||||||
|
bufferFlushThread.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
fun onPlayerMove(event: PlayerMoveEvent) = buffer.push(PlayerPositionEvent(event))
|
||||||
|
|
||||||
|
override fun onDisable() {
|
||||||
|
bufferFlushThread.stop()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall.buffer
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.heimdall.FoundationHeimdallPlugin
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
class BufferFlushThread(val plugin: FoundationHeimdallPlugin, val buffer: EventBuffer) {
|
||||||
|
private val running = AtomicBoolean(false)
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
running.set(true)
|
||||||
|
val thread = Thread {
|
||||||
|
plugin.slF4JLogger.info("Buffer Flusher Started")
|
||||||
|
while (running.get()) {
|
||||||
|
try {
|
||||||
|
transaction(plugin.db) {
|
||||||
|
val count = buffer.flush(this)
|
||||||
|
if (count > 0) {
|
||||||
|
plugin.slF4JLogger.info("Flushed $count Events")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
plugin.slF4JLogger.warn("Failed to flush buffer.", e)
|
||||||
|
}
|
||||||
|
Thread.sleep(5000)
|
||||||
|
}
|
||||||
|
plugin.slF4JLogger.info("Buffer Flusher Stopped")
|
||||||
|
}
|
||||||
|
thread.name = "Heimdall Buffer Flush"
|
||||||
|
thread.isDaemon = false
|
||||||
|
thread.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
running.set(false)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall.buffer
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.heimdall.event.HeimdallEvent
|
||||||
|
import org.jetbrains.exposed.sql.Transaction
|
||||||
|
|
||||||
|
class EventBuffer {
|
||||||
|
private var events = mutableListOf<HeimdallEvent>()
|
||||||
|
|
||||||
|
fun flush(transaction: Transaction): Long {
|
||||||
|
val referenceOfEvents = events
|
||||||
|
this.events = mutableListOf()
|
||||||
|
var count = 0L
|
||||||
|
while (referenceOfEvents.isNotEmpty()) {
|
||||||
|
val event = referenceOfEvents.removeAt(0)
|
||||||
|
event.store(transaction)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun push(event: HeimdallEvent) {
|
||||||
|
events.add(event)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall.event
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.sql.Transaction
|
||||||
|
|
||||||
|
abstract class HeimdallEvent {
|
||||||
|
abstract fun store(transaction: Transaction)
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall.event
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.heimdall.table.PlayerPositionTable
|
||||||
|
import org.bukkit.Location
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent
|
||||||
|
import org.jetbrains.exposed.sql.Transaction
|
||||||
|
import org.jetbrains.exposed.sql.insert
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class PlayerPositionEvent(
|
||||||
|
val playerUniqueIdentity: UUID,
|
||||||
|
val location: Location
|
||||||
|
) : HeimdallEvent() {
|
||||||
|
constructor(event: PlayerMoveEvent) : this(event.player.uniqueId, event.to)
|
||||||
|
|
||||||
|
override fun store(transaction: Transaction) {
|
||||||
|
transaction.apply {
|
||||||
|
PlayerPositionTable.insert {
|
||||||
|
it[time] = Instant.now()
|
||||||
|
it[player] = playerUniqueIdentity
|
||||||
|
it[world] = location.world.uid
|
||||||
|
it[x] = location.x
|
||||||
|
it[y] = location.y
|
||||||
|
it[z] = location.z
|
||||||
|
it[pitch] = location.pitch.toDouble()
|
||||||
|
it[yaw] = location.yaw.toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HeimdallConfig(
|
||||||
|
val enabled: Boolean = false,
|
||||||
|
val db: DbConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DbConfig(
|
||||||
|
val url: String,
|
||||||
|
val username: String,
|
||||||
|
val password: String
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package cloud.kubelet.foundation.heimdall.table
|
||||||
|
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||||
|
|
||||||
|
object PlayerPositionTable : Table("player_positions") {
|
||||||
|
val time = timestamp("time")
|
||||||
|
val world = uuid("world")
|
||||||
|
val player = uuid("player")
|
||||||
|
val x = double("x")
|
||||||
|
val y = double("y")
|
||||||
|
val z = double("z")
|
||||||
|
val pitch = double("pitch")
|
||||||
|
val yaw = double("yaw")
|
||||||
|
}
|
11
foundation-heimdall/src/main/resources/heimdall.yaml
Normal file
11
foundation-heimdall/src/main/resources/heimdall.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Whether Heimdall should be enabled for tracking events.
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Database connection information.
|
||||||
|
db:
|
||||||
|
# JDBC URL
|
||||||
|
url: "jdbc:postgresql://localhost/foundation"
|
||||||
|
# JDBC Username
|
||||||
|
username: "foundation"
|
||||||
|
# JDBC Password
|
||||||
|
password: "foundation"
|
20
foundation-heimdall/src/main/resources/init.sql
Normal file
20
foundation-heimdall/src/main/resources/init.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
--
|
||||||
|
create extension if not exists "uuid-ossp";
|
||||||
|
--
|
||||||
|
create extension if not exists timescaledb;
|
||||||
|
--
|
||||||
|
create schema if not exists heimdall;
|
||||||
|
--
|
||||||
|
create table if not exists heimdall.player_positions (
|
||||||
|
time timestamp not null,
|
||||||
|
player uuid not null,
|
||||||
|
world uuid not null,
|
||||||
|
x double precision not null,
|
||||||
|
y double precision not null,
|
||||||
|
z double precision not null,
|
||||||
|
pitch double precision not null,
|
||||||
|
yaw double precision not null,
|
||||||
|
PRIMARY KEY (time, player, world)
|
||||||
|
);
|
||||||
|
--
|
||||||
|
select create_hypertable('heimdall.player_positions', 'time', 'player', 4, if_not_exists => TRUE);
|
10
foundation-heimdall/src/main/resources/plugin.yml
Normal file
10
foundation-heimdall/src/main/resources/plugin.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name: Foundation-Heimdall
|
||||||
|
version: '${version}'
|
||||||
|
main: cloud.kubelet.foundation.heimdall.FoundationHeimdallPlugin
|
||||||
|
api-version: 1.18
|
||||||
|
prefix: Foundation-Heimdall
|
||||||
|
load: STARTUP
|
||||||
|
depend:
|
||||||
|
- Foundation
|
||||||
|
authors:
|
||||||
|
- kubelet
|
@ -3,4 +3,5 @@ rootProject.name = "foundation"
|
|||||||
include(
|
include(
|
||||||
":foundation-core",
|
":foundation-core",
|
||||||
":foundation-bifrost",
|
":foundation-bifrost",
|
||||||
|
":foundation-heimdall",
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user