Move some logic from the base plugin to the views as callbacks

This commit is contained in:
downthecrop 2025-09-09 13:24:52 -07:00
parent fec060626b
commit 4a053d5698
7 changed files with 295 additions and 130 deletions

View file

@ -1,40 +1,18 @@
package KondoKit
import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.Helpers.formatNumber
import KondoKit.Helpers.getSpriteId
import KondoKit.Helpers.showAlert
import KondoKit.views.HiscoresView
import KondoKit.views.LootTrackerView
import KondoKit.views.ReflectiveEditorView
import KondoKit.views.*
import KondoKit.views.OnUpdateCallback
import KondoKit.views.OnDrawCallback
import KondoKit.views.OnXPUpdateCallback
import KondoKit.views.OnKillingBlowNPCCallback
import KondoKit.views.OnPostClientTickCallback
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.Themes.Theme
import KondoKit.Themes.ThemeType
import KondoKit.Themes.getTheme
import KondoKit.components.ScrollablePanel
import KondoKit.views.XPTrackerView
import KondoKit.plugin.StateManager.focusedView
import KondoKit.views.Constants.COMBAT_LVL_SPRITE
import KondoKit.views.HiscoresView.createHiscoreSearchView
import KondoKit.views.HiscoresView.hiScoreView
import KondoKit.views.LootTrackerView.BAG_ICON
import KondoKit.views.LootTrackerView.createLootTrackerView
import KondoKit.views.LootTrackerView.lootTrackerView
import KondoKit.views.LootTrackerView.npcDeathSnapshots
import KondoKit.views.LootTrackerView.onPostClientTick
import KondoKit.views.LootTrackerView.takeGroundSnapshot
import KondoKit.views.ReflectiveEditorView.addPlugins
import KondoKit.views.ReflectiveEditorView.createReflectiveEditorView
import KondoKit.views.ReflectiveEditorView.reflectiveEditorView
import KondoKit.views.XPTrackerView.createXPTrackerView
import KondoKit.views.XPTrackerView.createXPWidget
import KondoKit.views.XPTrackerView.initialXP
import KondoKit.views.XPTrackerView.resetXPTracker
import KondoKit.views.XPTrackerView.totalXPWidget
import KondoKit.views.XPTrackerView.updateWidget
import KondoKit.views.XPTrackerView.wrappedWidget
import KondoKit.views.XPTrackerView.xpTrackerView
import KondoKit.views.XPTrackerView.xpWidgets
import plugin.Plugin
import plugin.api.*
import plugin.api.API.*
@ -103,9 +81,9 @@ class plugin : Plugin() {
const val FIXED_HEIGHT = 503
private const val NAVBAR_WIDTH = 30
private const val MAIN_CONTENT_WIDTH = 242
private const val WRENCH_ICON = 907
private const val LOOT_ICON = 777
private const val MAG_SPRITE = 1423
const val WRENCH_ICON = 907
const val LOOT_ICON = 777
const val MAG_SPRITE = 1423
const val LVL_ICON = 898
private lateinit var cardLayout: CardLayout
private lateinit var mainContentPanel: JPanel
@ -123,12 +101,38 @@ class plugin : Plugin() {
private const val HIDDEN_VIEW = "HIDDEN"
private var altCanvas: AltCanvas? = null
private val drawActions = mutableListOf<() -> Unit>()
private val views = mutableListOf<View>()
private val updateCallbacks = mutableListOf<OnUpdateCallback>()
private val drawCallbacks = mutableListOf<OnDrawCallback>()
private val xpUpdateCallbacks = mutableListOf<OnXPUpdateCallback>()
private val killingBlowNPCCallbacks = mutableListOf<OnKillingBlowNPCCallback>()
private val postClientTickCallbacks = mutableListOf<OnPostClientTickCallback>()
fun registerDrawAction(action: () -> Unit) {
synchronized(drawActions) {
drawActions.add(action)
}
}
fun registerUpdateCallback(callback: OnUpdateCallback) {
updateCallbacks.add(callback)
}
fun registerDrawCallback(callback: OnDrawCallback) {
drawCallbacks.add(callback)
}
fun registerXPUpdateCallback(callback: OnXPUpdateCallback) {
xpUpdateCallbacks.add(callback)
}
fun registerKillingBlowNPCCallback(callback: OnKillingBlowNPCCallback) {
killingBlowNPCCallbacks.add(callback)
}
fun registerPostClientTickCallback(callback: OnPostClientTickCallback) {
postClientTickCallbacks.add(callback)
}
}
override fun Init() {
@ -142,7 +146,7 @@ class plugin : Plugin() {
if (lastLogin != "" && lastLogin != Player.usernameInput.toString()) {
// if we logged in with a new character
// we need to reset the trackers
xpTrackerView?.let { resetXPTracker(it) }
XPTrackerView.xpTrackerView?.let { XPTrackerView.resetXPTracker(it) }
}
lastLogin = Player.usernameInput.toString()
}
@ -177,6 +181,9 @@ class plugin : Plugin() {
rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) }
frame.revalidate()
frame.repaint()
// Rebuild the reflective editor UI on the EDT and in one batch
ReflectiveEditorView.addPlugins(ReflectiveEditorView.panel)
}
pluginsReloaded = true
reloadInterfaces = true
@ -184,52 +191,10 @@ class plugin : Plugin() {
}
override fun OnXPUpdate(skillId: Int, xp: Int) {
if (!initialXP.containsKey(skillId)) {
initialXP[skillId] = xp
return
}
val previousXpSnapshot = initialXP[skillId] ?: xp
if (xp == initialXP[skillId]) return
val ensureOnEdt = Runnable {
var xpWidget = xpWidgets[skillId]
if (xpWidget != null) {
updateWidget(xpWidget, xp)
} else {
xpWidget = createXPWidget(skillId, previousXpSnapshot)
xpWidgets[skillId] = xpWidget
val wrapped = wrappedWidget(xpWidget.container)
// Attach per-widget remove menu
val popupMenu = XPTrackerView.removeXPWidgetMenu(wrapped, skillId)
val rightClickListener = object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
if (e.isPopupTrigger) popupMenu.show(e.component, e.x, e.y)
}
override fun mouseReleased(e: MouseEvent) {
if (e.isPopupTrigger) popupMenu.show(e.component, e.x, e.y)
}
}
Helpers.addMouseListenerToAll(wrapped, rightClickListener)
wrapped.addMouseListener(rightClickListener)
xpTrackerView?.add(wrapped)
xpTrackerView?.add(Box.createVerticalStrut(5))
if(focusedView == XPTrackerView.VIEW_NAME) {
xpTrackerView?.revalidate()
xpTrackerView?.repaint()
}
updateWidget(xpWidget, xp)
}
}
if (SwingUtilities.isEventDispatchThread()) {
ensureOnEdt.run()
} else {
SwingUtilities.invokeLater(ensureOnEdt)
}
// Call registered XP update callbacks
xpUpdateCallbacks.forEach { callback ->
callback.onXPUpdate(skillId, xp)
}
}
override fun Draw(timeDelta: Long) {
@ -241,7 +206,7 @@ class plugin : Plugin() {
if (pluginsReloaded) {
// Rebuild the reflective editor UI on the EDT and in one batch
SwingUtilities.invokeLater {
reflectiveEditorView?.let { addPlugins(it) }
ReflectiveEditorView.addPlugins(ReflectiveEditorView.panel)
}
pluginsReloaded = false
}
@ -253,10 +218,18 @@ class plugin : Plugin() {
accumulatedTime += timeDelta
if (accumulatedTime >= TICK_INTERVAL) {
lootTrackerView?.let { onPostClientTick(it) }
// Call registered post client tick callbacks
postClientTickCallbacks.forEach { callback ->
callback.onPostClientTick()
}
accumulatedTime = 0L
}
// Call registered draw callbacks
drawCallbacks.forEach { callback ->
callback.onDraw(timeDelta)
}
// Draw synced actions (that require to be done between glBegin and glEnd)
if (drawActions.isNotEmpty()) {
synchronized(drawActions) {
@ -294,32 +267,17 @@ class plugin : Plugin() {
}
override fun Update() {
val widgets = xpWidgets.values
val totalXP = totalXPWidget
widgets.forEach { xpWidget ->
val elapsedTime = (System.currentTimeMillis() - xpWidget.startTime) / 1000.0 / 60.0 / 60.0
val xpPerHour = if (elapsedTime > 0) (xpWidget.totalXpGained / elapsedTime).toInt() else 0
val formattedXpPerHour = formatNumber(xpPerHour)
xpWidget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedXpPerHour, secondaryColor)
xpWidget.container.repaint()
}
totalXP?.let { totalXPWidget ->
val elapsedTime = (System.currentTimeMillis() - totalXPWidget.startTime) / 1000.0 / 60.0 / 60.0
val totalXPPerHour = if (elapsedTime > 0) (totalXPWidget.totalXpGained / elapsedTime).toInt() else 0
val formattedTotalXpPerHour = formatNumber(totalXPPerHour)
totalXPWidget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedTotalXpPerHour, secondaryColor)
totalXPWidget.container.repaint()
// Call registered update callbacks
updateCallbacks.forEach { callback ->
callback.onUpdate()
}
}
override fun OnKillingBlowNPC(npcID: Int, x: Int, z: Int) {
val preDeathSnapshot = takeGroundSnapshot(Pair(x,z))
npcDeathSnapshots[npcID] = LootTrackerView.GroundSnapshot(preDeathSnapshot, Pair(x, z), 0)
// Call registered killing blow NPC callbacks
killingBlowNPCCallbacks.forEach { callback ->
callback.onKillingBlowNPC(npcID, x, z)
}
}
private fun allSpritesLoaded() : Boolean {
@ -330,7 +288,7 @@ class plugin : Plugin() {
return false
}
}
val otherIcons = arrayOf(LVL_ICON, MAG_SPRITE, LOOT_ICON, WRENCH_ICON, COMBAT_LVL_SPRITE, BAG_ICON)
val otherIcons = arrayOf(LVL_ICON, MAG_SPRITE, LOOT_ICON, WRENCH_ICON, Constants.COMBAT_LVL_SPRITE, LootTrackerView.BAG_ICON)
for (icon in otherIcons) {
if(!js5Archive8.isFileReady(icon)){
return false
@ -443,7 +401,7 @@ class plugin : Plugin() {
private fun searchHiscore(username: String): Runnable {
return Runnable {
setActiveView(HiscoresView.VIEW_NAME)
val customSearchField = hiScoreView?.let { HiscoresView.CustomSearchField(it) }
val customSearchField = HiscoresView.hiScoreView?.let { HiscoresView.CustomSearchField(it) }
customSearchField?.searchPlayer(username) ?: run {
println("searchView is null or CustomSearchField creation failed.")
@ -481,15 +439,33 @@ class plugin : Plugin() {
}
// Register Views
createXPTrackerView()
createHiscoreSearchView()
createLootTrackerView()
createReflectiveEditorView()
val xpTrackerView = XPTrackerView
val hiscoresView = HiscoresView
val lootTrackerView = LootTrackerView
val reflectiveEditorView = ReflectiveEditorView
// Create views
xpTrackerView.createView()
hiscoresView.createView()
lootTrackerView.createView()
reflectiveEditorView.createView()
// Register views
views.add(xpTrackerView)
views.add(hiscoresView)
views.add(lootTrackerView)
views.add(reflectiveEditorView)
// Register view functions
xpTrackerView.registerFunctions()
hiscoresView.registerFunctions()
lootTrackerView.registerFunctions()
reflectiveEditorView.registerFunctions()
mainContentPanel.add(ScrollablePanel(xpTrackerView!!), XPTrackerView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(hiScoreView!!), HiscoresView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(lootTrackerView!!), LootTrackerView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(reflectiveEditorView!!), ReflectiveEditorView.VIEW_NAME)
mainContentPanel.add(ScrollablePanel(xpTrackerView.panel), xpTrackerView.name)
mainContentPanel.add(ScrollablePanel(hiscoresView.panel), hiscoresView.name)
mainContentPanel.add(ScrollablePanel(lootTrackerView.panel), lootTrackerView.name)
mainContentPanel.add(ScrollablePanel(reflectiveEditorView.panel), reflectiveEditorView.name)
val navPanel = Panel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS)
@ -497,10 +473,10 @@ class plugin : Plugin() {
preferredSize = Dimension(NAVBAR_WIDTH, frame.height)
}
navPanel.add(createNavButton(LVL_ICON, XPTrackerView.VIEW_NAME))
navPanel.add(createNavButton(MAG_SPRITE, HiscoresView.VIEW_NAME))
navPanel.add(createNavButton(LOOT_ICON, LootTrackerView.VIEW_NAME))
navPanel.add(createNavButton(WRENCH_ICON, ReflectiveEditorView.VIEW_NAME))
navPanel.add(createNavButton(xpTrackerView.iconSpriteId, xpTrackerView.name))
navPanel.add(createNavButton(hiscoresView.iconSpriteId, hiscoresView.name))
navPanel.add(createNavButton(lootTrackerView.iconSpriteId, lootTrackerView.name))
navPanel.add(createNavButton(reflectiveEditorView.iconSpriteId, reflectiveEditorView.name))
val rightPanel = Panel(BorderLayout()).apply {
add(mainContentPanel, BorderLayout.CENTER)
@ -515,7 +491,7 @@ class plugin : Plugin() {
verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_NEVER
}
val desiredView = if (launchMinimized) HIDDEN_VIEW else XPTrackerView.VIEW_NAME
val desiredView = if (launchMinimized) HIDDEN_VIEW else xpTrackerView.name
// Commit layout synchronously on the EDT to avoid initial misplacement
val commit = Runnable {
frame.layout = BorderLayout()
@ -580,8 +556,7 @@ class plugin : Plugin() {
} else {
mainContentPanel.repaint()
}
focusedView = viewName
StateManager.focusedView = viewName
}
if (SwingUtilities.isEventDispatchThread()) {
@ -604,7 +579,7 @@ class plugin : Plugin() {
}
lastClickTime = currentTime
if (focusedView == viewName) {
if (StateManager.focusedView == viewName) {
setActiveView("HIDDEN")
} else {
setActiveView(viewName)

View file

@ -57,11 +57,24 @@ object Constants {
val SKILL_DISPLAY_ORDER = arrayOf(0,3,14,2,16,13,1,15,10,4,17,7,5,12,11,6,9,8,20,18,19,22,21,23)
}
object HiscoresView {
object HiscoresView : View {
const val VIEW_NAME = "HISCORE_SEARCH_VIEW"
var hiScoreView: JPanel? = null
override val name: String = VIEW_NAME
override val iconSpriteId: Int = Constants.MAG_SPRITE
override val panel: JPanel
get() = hiScoreView ?: JPanel()
override fun createView() {
createHiscoreSearchView()
}
override fun registerFunctions() {
// Hiscores functions are handled within the view itself
}
class CustomSearchField(private val hiscoresPanel: JPanel) : SearchField(
onSearch = { _ -> }, // Placeholder, will be replaced
viewName = VIEW_NAME

View file

@ -17,6 +17,8 @@ import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.registerDrawAction
import KondoKit.plugin.Companion.secondaryColor
import KondoKit.plugin.StateManager.focusedView
import KondoKit.views.OnKillingBlowNPCCallback
import KondoKit.views.OnPostClientTickCallback
import plugin.api.API
import rt4.*
import java.awt.*
@ -31,7 +33,7 @@ import java.text.DecimalFormat
import javax.swing.*
import kotlin.math.ceil
object LootTrackerView {
object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallback {
private const val SNAPSHOT_LIFESPAN = 10
const val BAG_ICON = 900
val npcDeathSnapshots = mutableMapOf<Int, GroundSnapshot>()
@ -43,6 +45,30 @@ object LootTrackerView {
var lastConfirmedKillNpcId = -1
private var customToolTipWindow: JWindow? = null
var lootTrackerView: JPanel? = null
override val name: String = VIEW_NAME
override val iconSpriteId: Int = BAG_ICON
override val panel: JPanel
get() = lootTrackerView ?: JPanel()
override fun createView() {
createLootTrackerView()
}
override fun registerFunctions() {
// Register callbacks with the plugin
KondoKit.plugin.registerPostClientTickCallback(this)
KondoKit.plugin.registerKillingBlowNPCCallback(this)
}
override fun onPostClientTick() {
lootTrackerView?.let { onPostClientTick(it) }
}
override fun onKillingBlowNPC(npcID: Int, x: Int, z: Int) {
val preDeathSnapshot = takeGroundSnapshot(Pair(x,z))
npcDeathSnapshots[npcID] = GroundSnapshot(preDeathSnapshot, Pair(x, z), 0)
}
fun loadGEPrices(): Map<String, String> {
return if (useLiveGEPrices) {
@ -373,7 +399,8 @@ object LootTrackerView {
fun onPostClientTick(lootTrackerView: JPanel) {
val toRemove = mutableListOf<Int>()
npcDeathSnapshots.entries.forEach { (npcId, snapshot) ->
npcDeathSnapshots.entries.forEach { entry ->
val (npcId, snapshot) = entry
val postDeathSnapshot = takeGroundSnapshot(Pair(snapshot.location.first, snapshot.location.second))
val newDrops = postDeathSnapshot.subtract(snapshot.items)
@ -389,7 +416,9 @@ object LootTrackerView {
}
}
toRemove.forEach { npcDeathSnapshots.remove(it) }
toRemove.forEach { npcId ->
npcDeathSnapshots.remove(npcId)
}
}
@ -602,4 +631,18 @@ object LootTrackerView {
data class GroundSnapshot(val items: Set<Item>, val location: Pair<Int, Int>, var age: Int)
data class Item(val id: Int, val quantity: Int)
}
// XPWidget data class for loot tracking
data class XPWidget(
val container: Container,
val skillId: Int,
val xpGainedLabel: JLabel,
val xpLeftLabel: JLabel,
val xpPerHourLabel: JLabel,
val actionsRemainingLabel: JLabel,
val progressBar: ProgressBar,
var totalXpGained: Int = 0,
var startTime: Long = System.currentTimeMillis(),
var previousXp: Int = 0
)
}

View file

@ -31,7 +31,7 @@ import kotlin.math.ceil
*/
object ReflectiveEditorView {
object ReflectiveEditorView : View {
var reflectiveEditorView: JPanel? = null
private val loadedPlugins: MutableList<String> = mutableListOf()
const val VIEW_NAME = "REFLECTIVE_EDITOR_VIEW"
@ -72,6 +72,19 @@ object ReflectiveEditorView {
// Search text for filtering plugins
private var pluginSearchText: String = ""
override val name: String = VIEW_NAME
override val iconSpriteId: Int = KondoKit.plugin.WRENCH_ICON
override val panel: JPanel
get() = reflectiveEditorView ?: JPanel()
override fun createView() {
createReflectiveEditorView()
}
override fun registerFunctions() {
// Reflective editor functions are handled within the view itself
}
fun createReflectiveEditorView() {
// Load the cog icon once
loadCogIcon()
@ -749,4 +762,4 @@ object ReflectiveEditorView {
g2.fillRoundRect(x, y, width - 1, height - 1, radius, radius)
}
}
}
}

View file

@ -0,0 +1,12 @@
package KondoKit.views
import javax.swing.JPanel
interface View {
val name: String
val iconSpriteId: Int
val panel: JPanel
fun createView()
fun registerFunctions()
}

View file

@ -0,0 +1,21 @@
package KondoKit.views
interface OnUpdateCallback {
fun onUpdate()
}
interface OnDrawCallback {
fun onDraw(timeDelta: Long)
}
interface OnXPUpdateCallback {
fun onXPUpdate(skillId: Int, xp: Int)
}
interface OnKillingBlowNPCCallback {
fun onKillingBlowNPC(npcID: Int, x: Int, z: Int)
}
interface OnPostClientTickCallback {
fun onPostClientTick()
}

View file

@ -24,6 +24,8 @@ import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.secondaryColor
import KondoKit.plugin.StateManager.focusedView
import KondoKit.views.BaseView
import KondoKit.views.OnUpdateCallback
import KondoKit.views.OnXPUpdateCallback
import plugin.api.API
import java.awt.*
import java.awt.event.MouseAdapter
@ -33,13 +35,15 @@ import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import javax.swing.*
object XPTrackerView {
object XPTrackerView : View, OnUpdateCallback, OnXPUpdateCallback {
private val COMBAT_SKILLS = intArrayOf(0,1,2,3,4)
val xpWidgets: MutableMap<Int, XPWidget> = HashMap()
var totalXPWidget: XPWidget? = null
val initialXP: MutableMap<Int, Int> = HashMap()
var xpTrackerView: JPanel? = null
const val VIEW_NAME = "XP_TRACKER_VIEW"
override val name: String = VIEW_NAME
override val iconSpriteId: Int = KondoKit.plugin.LVL_ICON
private val skillIconCache: MutableMap<Int, java.awt.image.BufferedImage> = HashMap()
@ -64,6 +68,91 @@ object XPTrackerView {
emptyMap()
}
override val panel: JPanel
get() = xpTrackerView ?: JPanel()
override fun createView() {
createXPTrackerView()
}
override fun registerFunctions() {
// Register callbacks with the plugin
KondoKit.plugin.registerUpdateCallback(this)
KondoKit.plugin.registerXPUpdateCallback(this)
}
override fun onUpdate() {
val widgets = xpWidgets.values
val totalXP = totalXPWidget
widgets.forEach { xpWidget ->
val elapsedTime = (System.currentTimeMillis() - xpWidget.startTime) / 1000.0 / 60.0 / 60.0
val xpPerHour = if (elapsedTime > 0) (xpWidget.totalXpGained / elapsedTime).toInt() else 0
val formattedXpPerHour = formatNumber(xpPerHour)
xpWidget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedXpPerHour, secondaryColor)
xpWidget.container.repaint()
}
totalXP?.let { widget ->
val elapsedTime = (System.currentTimeMillis() - widget.startTime) / 1000.0 / 60.0 / 60.0
val totalXPPerHour = if (elapsedTime > 0) (widget.totalXpGained / elapsedTime).toInt() else 0
val formattedTotalXpPerHour = formatNumber(totalXPPerHour)
widget.xpPerHourLabel.text =
formatHtmlLabelText("XP /hr: ", primaryColor, formattedTotalXpPerHour, secondaryColor)
widget.container.repaint()
}
}
override fun onXPUpdate(skillId: Int, xp: Int) {
if (!initialXP.containsKey(skillId)) {
initialXP[skillId] = xp
return
}
val previousXpSnapshot = initialXP[skillId] ?: xp
if (xp == initialXP[skillId]) return
val ensureOnEdt = Runnable {
var xpWidget = xpWidgets[skillId]
if (xpWidget != null) {
updateWidget(xpWidget, xp)
} else {
xpWidget = createXPWidget(skillId, previousXpSnapshot)
xpWidgets[skillId] = xpWidget
val wrapped = wrappedWidget(xpWidget.container)
// Attach per-widget remove menu
val popupMenu = removeXPWidgetMenu(wrapped, skillId)
val rightClickListener = object : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
if (e.isPopupTrigger) popupMenu.show(e.component, e.x, e.y)
}
override fun mouseReleased(e: MouseEvent) {
if (e.isPopupTrigger) popupMenu.show(e.component, e.x, e.y)
}
}
addMouseListenerToAll(wrapped, rightClickListener)
wrapped.addMouseListener(rightClickListener)
xpTrackerView?.add(wrapped)
xpTrackerView?.add(Box.createVerticalStrut(5))
if(focusedView == VIEW_NAME) {
xpTrackerView?.revalidate()
xpTrackerView?.repaint()
}
updateWidget(xpWidget, xp)
}
}
if (SwingUtilities.isEventDispatchThread()) {
ensureOnEdt.run()
} else {
SwingUtilities.invokeLater(ensureOnEdt)
}
}
fun updateWidget(xpWidget: XPWidget, xp: Int) {
val (currentLevel, xpGainedSinceLastLevel) = XPTable.getLevelForXp(xp)
@ -489,7 +578,6 @@ object XPTrackerView {
}
data class XPWidget(
val container: Container,
val skillId: Int,
@ -501,4 +589,4 @@ data class XPWidget(
var totalXpGained: Int = 0,
var startTime: Long = System.currentTimeMillis(),
var previousXp: Int = 0
)
)