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 package KondoKit
import KondoKit.Helpers.formatHtmlLabelText
import KondoKit.Helpers.formatNumber
import KondoKit.Helpers.getSpriteId import KondoKit.Helpers.getSpriteId
import KondoKit.Helpers.showAlert import KondoKit.Helpers.showAlert
import KondoKit.views.HiscoresView import KondoKit.views.*
import KondoKit.views.LootTrackerView import KondoKit.views.OnUpdateCallback
import KondoKit.views.ReflectiveEditorView import KondoKit.views.OnDrawCallback
import KondoKit.views.OnXPUpdateCallback
import KondoKit.views.OnKillingBlowNPCCallback
import KondoKit.views.OnPostClientTickCallback
import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite import KondoKit.SpriteToBufferedImage.getBufferedImageFromSprite
import KondoKit.Themes.Theme import KondoKit.Themes.Theme
import KondoKit.Themes.ThemeType import KondoKit.Themes.ThemeType
import KondoKit.Themes.getTheme import KondoKit.Themes.getTheme
import KondoKit.components.ScrollablePanel 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.Plugin
import plugin.api.* import plugin.api.*
import plugin.api.API.* import plugin.api.API.*
@ -103,9 +81,9 @@ class plugin : Plugin() {
const val FIXED_HEIGHT = 503 const val FIXED_HEIGHT = 503
private const val NAVBAR_WIDTH = 30 private const val NAVBAR_WIDTH = 30
private const val MAIN_CONTENT_WIDTH = 242 private const val MAIN_CONTENT_WIDTH = 242
private const val WRENCH_ICON = 907 const val WRENCH_ICON = 907
private const val LOOT_ICON = 777 const val LOOT_ICON = 777
private const val MAG_SPRITE = 1423 const val MAG_SPRITE = 1423
const val LVL_ICON = 898 const val LVL_ICON = 898
private lateinit var cardLayout: CardLayout private lateinit var cardLayout: CardLayout
private lateinit var mainContentPanel: JPanel private lateinit var mainContentPanel: JPanel
@ -123,12 +101,38 @@ class plugin : Plugin() {
private const val HIDDEN_VIEW = "HIDDEN" private const val HIDDEN_VIEW = "HIDDEN"
private var altCanvas: AltCanvas? = null private var altCanvas: AltCanvas? = null
private val drawActions = mutableListOf<() -> Unit>() 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) { fun registerDrawAction(action: () -> Unit) {
synchronized(drawActions) { synchronized(drawActions) {
drawActions.add(action) 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() { override fun Init() {
@ -142,7 +146,7 @@ class plugin : Plugin() {
if (lastLogin != "" && lastLogin != Player.usernameInput.toString()) { if (lastLogin != "" && lastLogin != Player.usernameInput.toString()) {
// if we logged in with a new character // if we logged in with a new character
// we need to reset the trackers // we need to reset the trackers
xpTrackerView?.let { resetXPTracker(it) } XPTrackerView.xpTrackerView?.let { XPTrackerView.resetXPTracker(it) }
} }
lastLogin = Player.usernameInput.toString() lastLogin = Player.usernameInput.toString()
} }
@ -177,6 +181,9 @@ class plugin : Plugin() {
rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) } rightPanelWrapper?.let { frame.add(it, BorderLayout.EAST) }
frame.revalidate() frame.revalidate()
frame.repaint() frame.repaint()
// Rebuild the reflective editor UI on the EDT and in one batch
ReflectiveEditorView.addPlugins(ReflectiveEditorView.panel)
} }
pluginsReloaded = true pluginsReloaded = true
reloadInterfaces = true reloadInterfaces = true
@ -184,52 +191,10 @@ class plugin : Plugin() {
} }
override fun OnXPUpdate(skillId: Int, xp: Int) { override fun OnXPUpdate(skillId: Int, xp: Int) {
if (!initialXP.containsKey(skillId)) { // Call registered XP update callbacks
initialXP[skillId] = xp xpUpdateCallbacks.forEach { callback ->
return callback.onXPUpdate(skillId, xp)
} }
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)
}
} }
override fun Draw(timeDelta: Long) { override fun Draw(timeDelta: Long) {
@ -241,7 +206,7 @@ class plugin : Plugin() {
if (pluginsReloaded) { if (pluginsReloaded) {
// Rebuild the reflective editor UI on the EDT and in one batch // Rebuild the reflective editor UI on the EDT and in one batch
SwingUtilities.invokeLater { SwingUtilities.invokeLater {
reflectiveEditorView?.let { addPlugins(it) } ReflectiveEditorView.addPlugins(ReflectiveEditorView.panel)
} }
pluginsReloaded = false pluginsReloaded = false
} }
@ -253,10 +218,18 @@ class plugin : Plugin() {
accumulatedTime += timeDelta accumulatedTime += timeDelta
if (accumulatedTime >= TICK_INTERVAL) { if (accumulatedTime >= TICK_INTERVAL) {
lootTrackerView?.let { onPostClientTick(it) } // Call registered post client tick callbacks
postClientTickCallbacks.forEach { callback ->
callback.onPostClientTick()
}
accumulatedTime = 0L accumulatedTime = 0L
} }
// Call registered draw callbacks
drawCallbacks.forEach { callback ->
callback.onDraw(timeDelta)
}
// Draw synced actions (that require to be done between glBegin and glEnd) // Draw synced actions (that require to be done between glBegin and glEnd)
if (drawActions.isNotEmpty()) { if (drawActions.isNotEmpty()) {
synchronized(drawActions) { synchronized(drawActions) {
@ -294,32 +267,17 @@ class plugin : Plugin() {
} }
override fun Update() { override fun Update() {
// Call registered update callbacks
val widgets = xpWidgets.values updateCallbacks.forEach { callback ->
val totalXP = totalXPWidget callback.onUpdate()
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()
} }
} }
override fun OnKillingBlowNPC(npcID: Int, x: Int, z: Int) { override fun OnKillingBlowNPC(npcID: Int, x: Int, z: Int) {
val preDeathSnapshot = takeGroundSnapshot(Pair(x,z)) // Call registered killing blow NPC callbacks
npcDeathSnapshots[npcID] = LootTrackerView.GroundSnapshot(preDeathSnapshot, Pair(x, z), 0) killingBlowNPCCallbacks.forEach { callback ->
callback.onKillingBlowNPC(npcID, x, z)
}
} }
private fun allSpritesLoaded() : Boolean { private fun allSpritesLoaded() : Boolean {
@ -330,7 +288,7 @@ class plugin : Plugin() {
return false 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) { for (icon in otherIcons) {
if(!js5Archive8.isFileReady(icon)){ if(!js5Archive8.isFileReady(icon)){
return false return false
@ -443,7 +401,7 @@ class plugin : Plugin() {
private fun searchHiscore(username: String): Runnable { private fun searchHiscore(username: String): Runnable {
return Runnable { return Runnable {
setActiveView(HiscoresView.VIEW_NAME) setActiveView(HiscoresView.VIEW_NAME)
val customSearchField = hiScoreView?.let { HiscoresView.CustomSearchField(it) } val customSearchField = HiscoresView.hiScoreView?.let { HiscoresView.CustomSearchField(it) }
customSearchField?.searchPlayer(username) ?: run { customSearchField?.searchPlayer(username) ?: run {
println("searchView is null or CustomSearchField creation failed.") println("searchView is null or CustomSearchField creation failed.")
@ -481,15 +439,33 @@ class plugin : Plugin() {
} }
// Register Views // Register Views
createXPTrackerView() val xpTrackerView = XPTrackerView
createHiscoreSearchView() val hiscoresView = HiscoresView
createLootTrackerView() val lootTrackerView = LootTrackerView
createReflectiveEditorView() 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(xpTrackerView.panel), xpTrackerView.name)
mainContentPanel.add(ScrollablePanel(hiScoreView!!), HiscoresView.VIEW_NAME) mainContentPanel.add(ScrollablePanel(hiscoresView.panel), hiscoresView.name)
mainContentPanel.add(ScrollablePanel(lootTrackerView!!), LootTrackerView.VIEW_NAME) mainContentPanel.add(ScrollablePanel(lootTrackerView.panel), lootTrackerView.name)
mainContentPanel.add(ScrollablePanel(reflectiveEditorView!!), ReflectiveEditorView.VIEW_NAME) mainContentPanel.add(ScrollablePanel(reflectiveEditorView.panel), reflectiveEditorView.name)
val navPanel = Panel().apply { val navPanel = Panel().apply {
layout = BoxLayout(this, BoxLayout.Y_AXIS) layout = BoxLayout(this, BoxLayout.Y_AXIS)
@ -497,10 +473,10 @@ class plugin : Plugin() {
preferredSize = Dimension(NAVBAR_WIDTH, frame.height) preferredSize = Dimension(NAVBAR_WIDTH, frame.height)
} }
navPanel.add(createNavButton(LVL_ICON, XPTrackerView.VIEW_NAME)) navPanel.add(createNavButton(xpTrackerView.iconSpriteId, xpTrackerView.name))
navPanel.add(createNavButton(MAG_SPRITE, HiscoresView.VIEW_NAME)) navPanel.add(createNavButton(hiscoresView.iconSpriteId, hiscoresView.name))
navPanel.add(createNavButton(LOOT_ICON, LootTrackerView.VIEW_NAME)) navPanel.add(createNavButton(lootTrackerView.iconSpriteId, lootTrackerView.name))
navPanel.add(createNavButton(WRENCH_ICON, ReflectiveEditorView.VIEW_NAME)) navPanel.add(createNavButton(reflectiveEditorView.iconSpriteId, reflectiveEditorView.name))
val rightPanel = Panel(BorderLayout()).apply { val rightPanel = Panel(BorderLayout()).apply {
add(mainContentPanel, BorderLayout.CENTER) add(mainContentPanel, BorderLayout.CENTER)
@ -515,7 +491,7 @@ class plugin : Plugin() {
verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_NEVER 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 // Commit layout synchronously on the EDT to avoid initial misplacement
val commit = Runnable { val commit = Runnable {
frame.layout = BorderLayout() frame.layout = BorderLayout()
@ -580,8 +556,7 @@ class plugin : Plugin() {
} else { } else {
mainContentPanel.repaint() mainContentPanel.repaint()
} }
StateManager.focusedView = viewName
focusedView = viewName
} }
if (SwingUtilities.isEventDispatchThread()) { if (SwingUtilities.isEventDispatchThread()) {
@ -604,7 +579,7 @@ class plugin : Plugin() {
} }
lastClickTime = currentTime lastClickTime = currentTime
if (focusedView == viewName) { if (StateManager.focusedView == viewName) {
setActiveView("HIDDEN") setActiveView("HIDDEN")
} else { } else {
setActiveView(viewName) 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) 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" const val VIEW_NAME = "HISCORE_SEARCH_VIEW"
var hiScoreView: JPanel? = null 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( class CustomSearchField(private val hiscoresPanel: JPanel) : SearchField(
onSearch = { _ -> }, // Placeholder, will be replaced onSearch = { _ -> }, // Placeholder, will be replaced
viewName = VIEW_NAME viewName = VIEW_NAME

View file

@ -17,6 +17,8 @@ import KondoKit.plugin.Companion.primaryColor
import KondoKit.plugin.Companion.registerDrawAction import KondoKit.plugin.Companion.registerDrawAction
import KondoKit.plugin.Companion.secondaryColor import KondoKit.plugin.Companion.secondaryColor
import KondoKit.plugin.StateManager.focusedView import KondoKit.plugin.StateManager.focusedView
import KondoKit.views.OnKillingBlowNPCCallback
import KondoKit.views.OnPostClientTickCallback
import plugin.api.API import plugin.api.API
import rt4.* import rt4.*
import java.awt.* import java.awt.*
@ -31,7 +33,7 @@ import java.text.DecimalFormat
import javax.swing.* import javax.swing.*
import kotlin.math.ceil import kotlin.math.ceil
object LootTrackerView { object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallback {
private const val SNAPSHOT_LIFESPAN = 10 private const val SNAPSHOT_LIFESPAN = 10
const val BAG_ICON = 900 const val BAG_ICON = 900
val npcDeathSnapshots = mutableMapOf<Int, GroundSnapshot>() val npcDeathSnapshots = mutableMapOf<Int, GroundSnapshot>()
@ -43,6 +45,30 @@ object LootTrackerView {
var lastConfirmedKillNpcId = -1 var lastConfirmedKillNpcId = -1
private var customToolTipWindow: JWindow? = null private var customToolTipWindow: JWindow? = null
var lootTrackerView: JPanel? = 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> { fun loadGEPrices(): Map<String, String> {
return if (useLiveGEPrices) { return if (useLiveGEPrices) {
@ -373,7 +399,8 @@ object LootTrackerView {
fun onPostClientTick(lootTrackerView: JPanel) { fun onPostClientTick(lootTrackerView: JPanel) {
val toRemove = mutableListOf<Int>() 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 postDeathSnapshot = takeGroundSnapshot(Pair(snapshot.location.first, snapshot.location.second))
val newDrops = postDeathSnapshot.subtract(snapshot.items) 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 GroundSnapshot(val items: Set<Item>, val location: Pair<Int, Int>, var age: Int)
data class Item(val id: Int, val quantity: 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 var reflectiveEditorView: JPanel? = null
private val loadedPlugins: MutableList<String> = mutableListOf() private val loadedPlugins: MutableList<String> = mutableListOf()
const val VIEW_NAME = "REFLECTIVE_EDITOR_VIEW" const val VIEW_NAME = "REFLECTIVE_EDITOR_VIEW"
@ -72,6 +72,19 @@ object ReflectiveEditorView {
// Search text for filtering plugins // Search text for filtering plugins
private var pluginSearchText: String = "" 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() { fun createReflectiveEditorView() {
// Load the cog icon once // Load the cog icon once
loadCogIcon() loadCogIcon()
@ -749,4 +762,4 @@ object ReflectiveEditorView {
g2.fillRoundRect(x, y, width - 1, height - 1, radius, radius) 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.Companion.secondaryColor
import KondoKit.plugin.StateManager.focusedView import KondoKit.plugin.StateManager.focusedView
import KondoKit.views.BaseView import KondoKit.views.BaseView
import KondoKit.views.OnUpdateCallback
import KondoKit.views.OnXPUpdateCallback
import plugin.api.API import plugin.api.API
import java.awt.* import java.awt.*
import java.awt.event.MouseAdapter import java.awt.event.MouseAdapter
@ -33,13 +35,15 @@ import java.io.InputStreamReader
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.swing.* import javax.swing.*
object XPTrackerView { object XPTrackerView : View, OnUpdateCallback, OnXPUpdateCallback {
private val COMBAT_SKILLS = intArrayOf(0,1,2,3,4) private val COMBAT_SKILLS = intArrayOf(0,1,2,3,4)
val xpWidgets: MutableMap<Int, XPWidget> = HashMap() val xpWidgets: MutableMap<Int, XPWidget> = HashMap()
var totalXPWidget: XPWidget? = null var totalXPWidget: XPWidget? = null
val initialXP: MutableMap<Int, Int> = HashMap() val initialXP: MutableMap<Int, Int> = HashMap()
var xpTrackerView: JPanel? = null var xpTrackerView: JPanel? = null
const val VIEW_NAME = "XP_TRACKER_VIEW" 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() private val skillIconCache: MutableMap<Int, java.awt.image.BufferedImage> = HashMap()
@ -64,6 +68,91 @@ object XPTrackerView {
emptyMap() 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) { fun updateWidget(xpWidget: XPWidget, xp: Int) {
val (currentLevel, xpGainedSinceLastLevel) = XPTable.getLevelForXp(xp) val (currentLevel, xpGainedSinceLastLevel) = XPTable.getLevelForXp(xp)
@ -489,7 +578,6 @@ object XPTrackerView {
} }
data class XPWidget( data class XPWidget(
val container: Container, val container: Container,
val skillId: Int, val skillId: Int,
@ -501,4 +589,4 @@ data class XPWidget(
var totalXpGained: Int = 0, var totalXpGained: Int = 0,
var startTime: Long = System.currentTimeMillis(), var startTime: Long = System.currentTimeMillis(),
var previousXp: Int = 0 var previousXp: Int = 0
) )